diff --git a/.earthlyignore b/.earthlyignore index 9b694cb7..37c45b4d 100644 --- a/.earthlyignore +++ b/.earthlyignore @@ -7,4 +7,9 @@ node_modules/ desktop/angular/node_modules desktop/angular/dist desktop/angular/dist-lib -desktop/angular/dist-extension \ No newline at end of file +desktop/angular/dist-extension +desktop/angular/.angular + +# Assets are ignored here because the symlink wouldn't work in +# the buildkit container so we copy the assets directly in Earthfile. +desktop/angular/assets \ No newline at end of file diff --git a/Earthfile b/Earthfile index 1ca151dc..3638f0d6 100644 --- a/Earthfile +++ b/Earthfile @@ -52,7 +52,7 @@ build-go: ARG GOOS=linux ARG GOARCH=amd64 ARG GOARM - ARG CMDS=portmaster-start portmaster-core hub + ARG CMDS=portmaster-start portmaster-core hub notifier CACHE --sharing shared "$GOCACHE" CACHE --sharing shared "$GOMODCACHE" @@ -112,7 +112,7 @@ test-go-all-platforms: BUILD +test-go --GOARCH=amd64 --GOOS=windows BUILD +test-go --GOARCH=arm64 --GOOS=windows -# Builds portmaster-start and portmaster-core for all supported platforms +# Builds portmaster-start, portmaster-core, hub and notifier for all supported platforms build-go-release: # Linux platforms: BUILD +build-go --GOARCH=amd64 --GOOS=linux @@ -131,46 +131,61 @@ build-utils: BUILD +build-go --CMDS="" --GOARCH=amd64 --GOOS=linux BUILD +build-go --CMDS="" --GOARCH=amd64 --GOOS=windows -# Prepares the angular project +# Prepares the angular project by installing dependencies angular-deps: FROM node:${node_version} WORKDIR /app/ui RUN apt update && apt install zip - CACHE --sharing shared "/app/ui/node_modules" - COPY desktop/angular/package.json . COPY desktop/angular/package-lock.json . + COPY assets/ ./assets + RUN npm install - +# Copies the UI folder into the working container +# and builds the shared libraries in the specified configuration (production or development) angular-base: FROM +angular-deps + ARG configuration="production" COPY desktop/angular/ . -# Build the Portmaster UI (angular) in release mode + IF [ "${configuration}" = "production" ] + RUN npm run build-libs + ELSE + RUN npm run build-libs:dev + END + +# Build an angualr project, zip it and save artifacts locally +angular-project: + ARG --required project + ARG --required dist + ARG configuration="production" + ARG baseHref="/" + + FROM +angular-base --configuration="${configuration}" + + IF [ "${configuration}" = "production" ] + ENV NODE_ENV="production" + END + + RUN ./node_modules/.bin/ng build --configuration ${configuration} --base-href ${baseHref} "${project}" + + RUN zip -r "./${project}.zip" "${dist}" + SAVE ARTIFACT "./${project}.zip" AS LOCAL ${outputDir}/${project}.zip + SAVE ARTIFACT "./dist" AS LOCAL ${outputDir}/${project} + +# Build the angular projects (portmaster-UI and tauri-builtin) in production mode angular-release: - FROM +angular-base + BUILD +angular-project --project=portmaster --dist=./dist --configuration=production --baseHref=/ui/modules/portmaster + BUILD +angular-project --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=production --baseHref="/" - CACHE --sharing shared "/app/ui/node_modules" - - RUN npm run build - RUN zip -r ./angular.zip ./dist - SAVE ARTIFACT "./angular.zip" AS LOCAL ${outputDir}/angular.zip - SAVE ARTIFACT "./dist" AS LOCAL ${outputDir}/angular - - -# Build the Portmaster UI (angular) in dev mode +# Build the angular projects (portmaster-UI and tauri-builtin) in dev mode angular-dev: - FROM +angular-base - - CACHE --sharing shared "/app/ui/node_modules" - - RUN npm run build:dev - SAVE ARTIFACT ./dist AS LOCAL ${outputDir}/angular - + BUILD +angular-project --project=portmaster --dist=./dist --configuration=development --baseHref=/ui/modules/portmaster + BUILD +angular-project --project=tauri-builtin --dist=./dist/tauri-builtin --configuration=development --baseHref="/" release: BUILD +build-go-release diff --git a/assets/fonts/Roboto-300/LICENSE.txt b/assets/fonts/Roboto-300/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-300/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-300/Roboto-300.eot b/assets/fonts/Roboto-300/Roboto-300.eot new file mode 100644 index 00000000..826acfda Binary files /dev/null and b/assets/fonts/Roboto-300/Roboto-300.eot differ diff --git a/assets/fonts/Roboto-300/Roboto-300.svg b/assets/fonts/Roboto-300/Roboto-300.svg new file mode 100644 index 00000000..52b28327 --- /dev/null +++ b/assets/fonts/Roboto-300/Roboto-300.svg @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-300/Roboto-300.ttf b/assets/fonts/Roboto-300/Roboto-300.ttf new file mode 100644 index 00000000..66bc5ab8 Binary files /dev/null and b/assets/fonts/Roboto-300/Roboto-300.ttf differ diff --git a/assets/fonts/Roboto-300/Roboto-300.woff b/assets/fonts/Roboto-300/Roboto-300.woff new file mode 100644 index 00000000..1bff3ec4 Binary files /dev/null and b/assets/fonts/Roboto-300/Roboto-300.woff differ diff --git a/assets/fonts/Roboto-300/Roboto-300.woff2 b/assets/fonts/Roboto-300/Roboto-300.woff2 new file mode 100644 index 00000000..4411cbc8 Binary files /dev/null and b/assets/fonts/Roboto-300/Roboto-300.woff2 differ diff --git a/assets/fonts/Roboto-300italic/LICENSE.txt b/assets/fonts/Roboto-300italic/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-300italic/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.eot b/assets/fonts/Roboto-300italic/Roboto-300italic.eot new file mode 100644 index 00000000..c47c43ec Binary files /dev/null and b/assets/fonts/Roboto-300italic/Roboto-300italic.eot differ diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.svg b/assets/fonts/Roboto-300italic/Roboto-300italic.svg new file mode 100644 index 00000000..ea86b201 --- /dev/null +++ b/assets/fonts/Roboto-300italic/Roboto-300italic.svg @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.ttf b/assets/fonts/Roboto-300italic/Roboto-300italic.ttf new file mode 100644 index 00000000..ef1d13ce Binary files /dev/null and b/assets/fonts/Roboto-300italic/Roboto-300italic.ttf differ diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.woff b/assets/fonts/Roboto-300italic/Roboto-300italic.woff new file mode 100644 index 00000000..fc4a8b5a Binary files /dev/null and b/assets/fonts/Roboto-300italic/Roboto-300italic.woff differ diff --git a/assets/fonts/Roboto-300italic/Roboto-300italic.woff2 b/assets/fonts/Roboto-300italic/Roboto-300italic.woff2 new file mode 100644 index 00000000..05fdb0ae Binary files /dev/null and b/assets/fonts/Roboto-300italic/Roboto-300italic.woff2 differ diff --git a/assets/fonts/Roboto-500/LICENSE.txt b/assets/fonts/Roboto-500/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-500/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-500/Roboto-500.eot b/assets/fonts/Roboto-500/Roboto-500.eot new file mode 100644 index 00000000..8c06caa2 Binary files /dev/null and b/assets/fonts/Roboto-500/Roboto-500.eot differ diff --git a/assets/fonts/Roboto-500/Roboto-500.svg b/assets/fonts/Roboto-500/Roboto-500.svg new file mode 100644 index 00000000..2b989161 --- /dev/null +++ b/assets/fonts/Roboto-500/Roboto-500.svg @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-500/Roboto-500.ttf b/assets/fonts/Roboto-500/Roboto-500.ttf new file mode 100644 index 00000000..8d6fa924 Binary files /dev/null and b/assets/fonts/Roboto-500/Roboto-500.ttf differ diff --git a/assets/fonts/Roboto-500/Roboto-500.woff b/assets/fonts/Roboto-500/Roboto-500.woff new file mode 100644 index 00000000..d3c82e18 Binary files /dev/null and b/assets/fonts/Roboto-500/Roboto-500.woff differ diff --git a/assets/fonts/Roboto-500/Roboto-500.woff2 b/assets/fonts/Roboto-500/Roboto-500.woff2 new file mode 100644 index 00000000..6be92c71 Binary files /dev/null and b/assets/fonts/Roboto-500/Roboto-500.woff2 differ diff --git a/assets/fonts/Roboto-500italic/LICENSE.txt b/assets/fonts/Roboto-500italic/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-500italic/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.eot b/assets/fonts/Roboto-500italic/Roboto-500italic.eot new file mode 100644 index 00000000..2b253af0 Binary files /dev/null and b/assets/fonts/Roboto-500italic/Roboto-500italic.eot differ diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.svg b/assets/fonts/Roboto-500italic/Roboto-500italic.svg new file mode 100644 index 00000000..43c3be61 --- /dev/null +++ b/assets/fonts/Roboto-500italic/Roboto-500italic.svg @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.ttf b/assets/fonts/Roboto-500italic/Roboto-500italic.ttf new file mode 100644 index 00000000..28d03db9 Binary files /dev/null and b/assets/fonts/Roboto-500italic/Roboto-500italic.ttf differ diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.woff b/assets/fonts/Roboto-500italic/Roboto-500italic.woff new file mode 100644 index 00000000..072ca9b2 Binary files /dev/null and b/assets/fonts/Roboto-500italic/Roboto-500italic.woff differ diff --git a/assets/fonts/Roboto-500italic/Roboto-500italic.woff2 b/assets/fonts/Roboto-500italic/Roboto-500italic.woff2 new file mode 100644 index 00000000..382866ae Binary files /dev/null and b/assets/fonts/Roboto-500italic/Roboto-500italic.woff2 differ diff --git a/assets/fonts/Roboto-700/LICENSE.txt b/assets/fonts/Roboto-700/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-700/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-700/Roboto-700.eot b/assets/fonts/Roboto-700/Roboto-700.eot new file mode 100644 index 00000000..f89cad7b Binary files /dev/null and b/assets/fonts/Roboto-700/Roboto-700.eot differ diff --git a/assets/fonts/Roboto-700/Roboto-700.svg b/assets/fonts/Roboto-700/Roboto-700.svg new file mode 100644 index 00000000..fc8d42f9 --- /dev/null +++ b/assets/fonts/Roboto-700/Roboto-700.svg @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-700/Roboto-700.ttf b/assets/fonts/Roboto-700/Roboto-700.ttf new file mode 100644 index 00000000..19090afb Binary files /dev/null and b/assets/fonts/Roboto-700/Roboto-700.ttf differ diff --git a/assets/fonts/Roboto-700/Roboto-700.woff b/assets/fonts/Roboto-700/Roboto-700.woff new file mode 100644 index 00000000..3143de29 Binary files /dev/null and b/assets/fonts/Roboto-700/Roboto-700.woff differ diff --git a/assets/fonts/Roboto-700/Roboto-700.woff2 b/assets/fonts/Roboto-700/Roboto-700.woff2 new file mode 100644 index 00000000..3b2dd4e2 Binary files /dev/null and b/assets/fonts/Roboto-700/Roboto-700.woff2 differ diff --git a/assets/fonts/Roboto-700italic/LICENSE.txt b/assets/fonts/Roboto-700italic/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-700italic/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.eot b/assets/fonts/Roboto-700italic/Roboto-700italic.eot new file mode 100644 index 00000000..b8bbdf22 Binary files /dev/null and b/assets/fonts/Roboto-700italic/Roboto-700italic.eot differ diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.svg b/assets/fonts/Roboto-700italic/Roboto-700italic.svg new file mode 100644 index 00000000..c71c29ec --- /dev/null +++ b/assets/fonts/Roboto-700italic/Roboto-700italic.svg @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.ttf b/assets/fonts/Roboto-700italic/Roboto-700italic.ttf new file mode 100644 index 00000000..a20e3889 Binary files /dev/null and b/assets/fonts/Roboto-700italic/Roboto-700italic.ttf differ diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.woff b/assets/fonts/Roboto-700italic/Roboto-700italic.woff new file mode 100644 index 00000000..7a0ae05e Binary files /dev/null and b/assets/fonts/Roboto-700italic/Roboto-700italic.woff differ diff --git a/assets/fonts/Roboto-700italic/Roboto-700italic.woff2 b/assets/fonts/Roboto-700italic/Roboto-700italic.woff2 new file mode 100644 index 00000000..91d2aa6a Binary files /dev/null and b/assets/fonts/Roboto-700italic/Roboto-700italic.woff2 differ diff --git a/assets/fonts/Roboto-italic/LICENSE.txt b/assets/fonts/Roboto-italic/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-italic/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-italic/Roboto-italic.eot b/assets/fonts/Roboto-italic/Roboto-italic.eot new file mode 100644 index 00000000..f2d020a8 Binary files /dev/null and b/assets/fonts/Roboto-italic/Roboto-italic.eot differ diff --git a/assets/fonts/Roboto-italic/Roboto-italic.svg b/assets/fonts/Roboto-italic/Roboto-italic.svg new file mode 100644 index 00000000..738b8295 --- /dev/null +++ b/assets/fonts/Roboto-italic/Roboto-italic.svg @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-italic/Roboto-italic.ttf b/assets/fonts/Roboto-italic/Roboto-italic.ttf new file mode 100644 index 00000000..b0dd4a1e Binary files /dev/null and b/assets/fonts/Roboto-italic/Roboto-italic.ttf differ diff --git a/assets/fonts/Roboto-italic/Roboto-italic.woff b/assets/fonts/Roboto-italic/Roboto-italic.woff new file mode 100644 index 00000000..dcfeb008 Binary files /dev/null and b/assets/fonts/Roboto-italic/Roboto-italic.woff differ diff --git a/assets/fonts/Roboto-italic/Roboto-italic.woff2 b/assets/fonts/Roboto-italic/Roboto-italic.woff2 new file mode 100644 index 00000000..1bb77f9d Binary files /dev/null and b/assets/fonts/Roboto-italic/Roboto-italic.woff2 differ diff --git a/assets/fonts/Roboto-regular/LICENSE.txt b/assets/fonts/Roboto-regular/LICENSE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/assets/fonts/Roboto-regular/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/assets/fonts/Roboto-regular/Roboto-regular.eot b/assets/fonts/Roboto-regular/Roboto-regular.eot new file mode 100644 index 00000000..d26bc8f5 Binary files /dev/null and b/assets/fonts/Roboto-regular/Roboto-regular.eot differ diff --git a/assets/fonts/Roboto-regular/Roboto-regular.svg b/assets/fonts/Roboto-regular/Roboto-regular.svg new file mode 100644 index 00000000..ed55c105 --- /dev/null +++ b/assets/fonts/Roboto-regular/Roboto-regular.svg @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/Roboto-regular/Roboto-regular.ttf b/assets/fonts/Roboto-regular/Roboto-regular.ttf new file mode 100644 index 00000000..7b25f3ce Binary files /dev/null and b/assets/fonts/Roboto-regular/Roboto-regular.ttf differ diff --git a/assets/fonts/Roboto-regular/Roboto-regular.woff b/assets/fonts/Roboto-regular/Roboto-regular.woff new file mode 100644 index 00000000..5e353cf4 Binary files /dev/null and b/assets/fonts/Roboto-regular/Roboto-regular.woff differ diff --git a/assets/fonts/Roboto-regular/Roboto-regular.woff2 b/assets/fonts/Roboto-regular/Roboto-regular.woff2 new file mode 100644 index 00000000..d1035f9a Binary files /dev/null and b/assets/fonts/Roboto-regular/Roboto-regular.woff2 differ diff --git a/assets/fonts/roboto-slimfix.css b/assets/fonts/roboto-slimfix.css new file mode 100644 index 00000000..d2dd8a11 --- /dev/null +++ b/assets/fonts/roboto-slimfix.css @@ -0,0 +1,111 @@ +@font-face { + font-family: 'Roboto'; + font-weight: 400; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-300/Roboto-300.eot'); + src: url('/assets/vendor/fonts/Roboto-300/Roboto-300.eot?#iefix') format('embedded-opentype'), + local('Roboto Light'), + local('Roboto-300'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 500; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.eot'); + src: url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.eot?#iefix') format('embedded-opentype'), + local('Roboto'), + local('Roboto-regular'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 700; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-500/Roboto-500.eot'); + src: url('/assets/vendor/fonts/Roboto-500/Roboto-500.eot?#iefix') format('embedded-opentype'), + local('Roboto Medium'), + local('Roboto-500'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 900; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-700/Roboto-700.eot'); + src: url('/assets/vendor/fonts/Roboto-700/Roboto-700.eot?#iefix') format('embedded-opentype'), + local('Roboto Bold'), + local('Roboto-700'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 400; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.eot'); + src: url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Light Italic'), + local('Roboto-300italic'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 500; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.eot'); + src: url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Italic'), + local('Roboto-italic'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 700; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.eot'); + src: url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Medium Italic'), + local('Roboto-500italic'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 900; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.eot'); + src: url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Bold Italic'), + local('Roboto-700italic'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.svg#Roboto') format('svg'); +} diff --git a/assets/fonts/roboto.css b/assets/fonts/roboto.css new file mode 100644 index 00000000..cae1c904 --- /dev/null +++ b/assets/fonts/roboto.css @@ -0,0 +1,111 @@ +@font-face { + font-family: 'Roboto'; + font-weight: 300; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-300/Roboto-300.eot'); + src: url('/assets/vendor/fonts/Roboto-300/Roboto-300.eot?#iefix') format('embedded-opentype'), + local('Roboto Light'), + local('Roboto-300'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-300/Roboto-300.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 400; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.eot'); + src: url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.eot?#iefix') format('embedded-opentype'), + local('Roboto'), + local('Roboto-regular'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-regular/Roboto-regular.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 500; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-500/Roboto-500.eot'); + src: url('/assets/vendor/fonts/Roboto-500/Roboto-500.eot?#iefix') format('embedded-opentype'), + local('Roboto Medium'), + local('Roboto-500'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-500/Roboto-500.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 700; + font-style: normal; + src: url('/assets/vendor/fonts/Roboto-700/Roboto-700.eot'); + src: url('/assets/vendor/fonts/Roboto-700/Roboto-700.eot?#iefix') format('embedded-opentype'), + local('Roboto Bold'), + local('Roboto-700'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-700/Roboto-700.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 300; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.eot'); + src: url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Light Italic'), + local('Roboto-300italic'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-300italic/Roboto-300italic.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 400; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.eot'); + src: url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Italic'), + local('Roboto-italic'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-italic/Roboto-italic.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 500; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.eot'); + src: url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Medium Italic'), + local('Roboto-500italic'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-500italic/Roboto-500italic.svg#Roboto') format('svg'); +} + +@font-face { + font-family: 'Roboto'; + font-weight: 700; + font-style: italic; + src: url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.eot'); + src: url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.eot?#iefix') format('embedded-opentype'), + local('Roboto Bold Italic'), + local('Roboto-700italic'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.woff2') format('woff2'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.woff') format('woff'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.ttf') format('truetype'), + url('/assets/vendor/fonts/Roboto-700italic/Roboto-700italic.svg#Roboto') format('svg'); +} diff --git a/assets/icons/README.md b/assets/icons/README.md new file mode 100644 index 00000000..bf72d16b --- /dev/null +++ b/assets/icons/README.md @@ -0,0 +1,3 @@ +# .ICOs + +converted using https://www.icoconverter.com/ diff --git a/assets/icons/pm_dark_128.png b/assets/icons/pm_dark_128.png new file mode 100644 index 00000000..99193f24 Binary files /dev/null and b/assets/icons/pm_dark_128.png differ diff --git a/assets/icons/pm_dark_256.png b/assets/icons/pm_dark_256.png new file mode 100644 index 00000000..fe9d6acb Binary files /dev/null and b/assets/icons/pm_dark_256.png differ diff --git a/assets/icons/pm_dark_512.ico b/assets/icons/pm_dark_512.ico new file mode 100644 index 00000000..fdb10a67 Binary files /dev/null and b/assets/icons/pm_dark_512.ico differ diff --git a/assets/icons/pm_dark_512.png b/assets/icons/pm_dark_512.png new file mode 100644 index 00000000..05f504cb Binary files /dev/null and b/assets/icons/pm_dark_512.png differ diff --git a/assets/icons/pm_dark_blue_128.png b/assets/icons/pm_dark_blue_128.png new file mode 100644 index 00000000..e248b771 Binary files /dev/null and b/assets/icons/pm_dark_blue_128.png differ diff --git a/assets/icons/pm_dark_blue_256.png b/assets/icons/pm_dark_blue_256.png new file mode 100644 index 00000000..f1537946 Binary files /dev/null and b/assets/icons/pm_dark_blue_256.png differ diff --git a/assets/icons/pm_dark_blue_512.ico b/assets/icons/pm_dark_blue_512.ico new file mode 100644 index 00000000..d684c237 Binary files /dev/null and b/assets/icons/pm_dark_blue_512.ico differ diff --git a/assets/icons/pm_dark_blue_512.png b/assets/icons/pm_dark_blue_512.png new file mode 100644 index 00000000..92a860cc Binary files /dev/null and b/assets/icons/pm_dark_blue_512.png differ diff --git a/assets/icons/pm_dark_green_128.png b/assets/icons/pm_dark_green_128.png new file mode 100644 index 00000000..179064cd Binary files /dev/null and b/assets/icons/pm_dark_green_128.png differ diff --git a/assets/icons/pm_dark_green_256.png b/assets/icons/pm_dark_green_256.png new file mode 100644 index 00000000..3c219697 Binary files /dev/null and b/assets/icons/pm_dark_green_256.png differ diff --git a/assets/icons/pm_dark_green_512.ico b/assets/icons/pm_dark_green_512.ico new file mode 100644 index 00000000..6463edb3 Binary files /dev/null and b/assets/icons/pm_dark_green_512.ico differ diff --git a/assets/icons/pm_dark_green_512.png b/assets/icons/pm_dark_green_512.png new file mode 100644 index 00000000..08e96d93 Binary files /dev/null and b/assets/icons/pm_dark_green_512.png differ diff --git a/assets/icons/pm_dark_red_128.png b/assets/icons/pm_dark_red_128.png new file mode 100644 index 00000000..7f7449a8 Binary files /dev/null and b/assets/icons/pm_dark_red_128.png differ diff --git a/assets/icons/pm_dark_red_256.png b/assets/icons/pm_dark_red_256.png new file mode 100644 index 00000000..f77a8f4c Binary files /dev/null and b/assets/icons/pm_dark_red_256.png differ diff --git a/assets/icons/pm_dark_red_512.ico b/assets/icons/pm_dark_red_512.ico new file mode 100644 index 00000000..e12fbbcc Binary files /dev/null and b/assets/icons/pm_dark_red_512.ico differ diff --git a/assets/icons/pm_dark_red_512.png b/assets/icons/pm_dark_red_512.png new file mode 100644 index 00000000..77e05fc2 Binary files /dev/null and b/assets/icons/pm_dark_red_512.png differ diff --git a/assets/icons/pm_dark_yellow_128.png b/assets/icons/pm_dark_yellow_128.png new file mode 100644 index 00000000..d16ff38e Binary files /dev/null and b/assets/icons/pm_dark_yellow_128.png differ diff --git a/assets/icons/pm_dark_yellow_256.png b/assets/icons/pm_dark_yellow_256.png new file mode 100644 index 00000000..9c98b26f Binary files /dev/null and b/assets/icons/pm_dark_yellow_256.png differ diff --git a/assets/icons/pm_dark_yellow_512.ico b/assets/icons/pm_dark_yellow_512.ico new file mode 100644 index 00000000..a047a14c Binary files /dev/null and b/assets/icons/pm_dark_yellow_512.ico differ diff --git a/assets/icons/pm_dark_yellow_512.png b/assets/icons/pm_dark_yellow_512.png new file mode 100644 index 00000000..dc5697a2 Binary files /dev/null and b/assets/icons/pm_dark_yellow_512.png differ diff --git a/assets/icons/pm_light_128.png b/assets/icons/pm_light_128.png new file mode 100644 index 00000000..063948f1 Binary files /dev/null and b/assets/icons/pm_light_128.png differ diff --git a/assets/icons/pm_light_256.png b/assets/icons/pm_light_256.png new file mode 100644 index 00000000..681b0c0a Binary files /dev/null and b/assets/icons/pm_light_256.png differ diff --git a/assets/icons/pm_light_512.ico b/assets/icons/pm_light_512.ico new file mode 100644 index 00000000..b82c736b Binary files /dev/null and b/assets/icons/pm_light_512.ico differ diff --git a/assets/icons/pm_light_512.png b/assets/icons/pm_light_512.png new file mode 100644 index 00000000..35706673 Binary files /dev/null and b/assets/icons/pm_light_512.png differ diff --git a/assets/icons/pm_light_blue_128.png b/assets/icons/pm_light_blue_128.png new file mode 100644 index 00000000..c088b3cd Binary files /dev/null and b/assets/icons/pm_light_blue_128.png differ diff --git a/assets/icons/pm_light_blue_256.png b/assets/icons/pm_light_blue_256.png new file mode 100644 index 00000000..616c9336 Binary files /dev/null and b/assets/icons/pm_light_blue_256.png differ diff --git a/assets/icons/pm_light_blue_512.ico b/assets/icons/pm_light_blue_512.ico new file mode 100644 index 00000000..72ba2747 Binary files /dev/null and b/assets/icons/pm_light_blue_512.ico differ diff --git a/assets/icons/pm_light_blue_512.png b/assets/icons/pm_light_blue_512.png new file mode 100644 index 00000000..d7eaa1fc Binary files /dev/null and b/assets/icons/pm_light_blue_512.png differ diff --git a/assets/icons/pm_light_green_128.png b/assets/icons/pm_light_green_128.png new file mode 100644 index 00000000..c499b717 Binary files /dev/null and b/assets/icons/pm_light_green_128.png differ diff --git a/assets/icons/pm_light_green_256.png b/assets/icons/pm_light_green_256.png new file mode 100644 index 00000000..e78254bd Binary files /dev/null and b/assets/icons/pm_light_green_256.png differ diff --git a/assets/icons/pm_light_green_512.ico b/assets/icons/pm_light_green_512.ico new file mode 100644 index 00000000..6ccef48c Binary files /dev/null and b/assets/icons/pm_light_green_512.ico differ diff --git a/assets/icons/pm_light_green_512.png b/assets/icons/pm_light_green_512.png new file mode 100644 index 00000000..7377fb6d Binary files /dev/null and b/assets/icons/pm_light_green_512.png differ diff --git a/assets/icons/pm_light_red_128.png b/assets/icons/pm_light_red_128.png new file mode 100644 index 00000000..e2767f75 Binary files /dev/null and b/assets/icons/pm_light_red_128.png differ diff --git a/assets/icons/pm_light_red_256.png b/assets/icons/pm_light_red_256.png new file mode 100644 index 00000000..286e65fc Binary files /dev/null and b/assets/icons/pm_light_red_256.png differ diff --git a/assets/icons/pm_light_red_512.ico b/assets/icons/pm_light_red_512.ico new file mode 100644 index 00000000..8e0bc68a Binary files /dev/null and b/assets/icons/pm_light_red_512.ico differ diff --git a/assets/icons/pm_light_red_512.png b/assets/icons/pm_light_red_512.png new file mode 100644 index 00000000..9eb31896 Binary files /dev/null and b/assets/icons/pm_light_red_512.png differ diff --git a/assets/icons/pm_light_yellow_128.png b/assets/icons/pm_light_yellow_128.png new file mode 100644 index 00000000..e0f89794 Binary files /dev/null and b/assets/icons/pm_light_yellow_128.png differ diff --git a/assets/icons/pm_light_yellow_256.png b/assets/icons/pm_light_yellow_256.png new file mode 100644 index 00000000..eda4336f Binary files /dev/null and b/assets/icons/pm_light_yellow_256.png differ diff --git a/assets/icons/pm_light_yellow_512.ico b/assets/icons/pm_light_yellow_512.ico new file mode 100644 index 00000000..5a70125e Binary files /dev/null and b/assets/icons/pm_light_yellow_512.ico differ diff --git a/assets/icons/pm_light_yellow_512.png b/assets/icons/pm_light_yellow_512.png new file mode 100644 index 00000000..be87851a Binary files /dev/null and b/assets/icons/pm_light_yellow_512.png differ diff --git a/assets/img/Mobile.svg b/assets/img/Mobile.svg new file mode 100644 index 00000000..a7b773b9 --- /dev/null +++ b/assets/img/Mobile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/flags/AD.png b/assets/img/flags/AD.png new file mode 100644 index 00000000..d965a794 Binary files /dev/null and b/assets/img/flags/AD.png differ diff --git a/assets/img/flags/AE.png b/assets/img/flags/AE.png new file mode 100644 index 00000000..f429cc47 Binary files /dev/null and b/assets/img/flags/AE.png differ diff --git a/assets/img/flags/AF.png b/assets/img/flags/AF.png new file mode 100644 index 00000000..482779b5 Binary files /dev/null and b/assets/img/flags/AF.png differ diff --git a/assets/img/flags/AG.png b/assets/img/flags/AG.png new file mode 100644 index 00000000..6470e12b Binary files /dev/null and b/assets/img/flags/AG.png differ diff --git a/assets/img/flags/AI.png b/assets/img/flags/AI.png new file mode 100644 index 00000000..6c8ce550 Binary files /dev/null and b/assets/img/flags/AI.png differ diff --git a/assets/img/flags/AL.png b/assets/img/flags/AL.png new file mode 100644 index 00000000..69ba464d Binary files /dev/null and b/assets/img/flags/AL.png differ diff --git a/assets/img/flags/AM.png b/assets/img/flags/AM.png new file mode 100644 index 00000000..5b222d90 Binary files /dev/null and b/assets/img/flags/AM.png differ diff --git a/assets/img/flags/AN.png b/assets/img/flags/AN.png new file mode 100644 index 00000000..2c9e769b Binary files /dev/null and b/assets/img/flags/AN.png differ diff --git a/assets/img/flags/AO.png b/assets/img/flags/AO.png new file mode 100644 index 00000000..129a2d9e Binary files /dev/null and b/assets/img/flags/AO.png differ diff --git a/assets/img/flags/AQ.png b/assets/img/flags/AQ.png new file mode 100644 index 00000000..565eba0f Binary files /dev/null and b/assets/img/flags/AQ.png differ diff --git a/assets/img/flags/AR.png b/assets/img/flags/AR.png new file mode 100644 index 00000000..aa5049b3 Binary files /dev/null and b/assets/img/flags/AR.png differ diff --git a/assets/img/flags/AS.png b/assets/img/flags/AS.png new file mode 100644 index 00000000..f959e3ac Binary files /dev/null and b/assets/img/flags/AS.png differ diff --git a/assets/img/flags/AT.png b/assets/img/flags/AT.png new file mode 100644 index 00000000..aa8d102b Binary files /dev/null and b/assets/img/flags/AT.png differ diff --git a/assets/img/flags/AU.png b/assets/img/flags/AU.png new file mode 100644 index 00000000..f2fc59c8 Binary files /dev/null and b/assets/img/flags/AU.png differ diff --git a/assets/img/flags/AW.png b/assets/img/flags/AW.png new file mode 100644 index 00000000..6ef2467b Binary files /dev/null and b/assets/img/flags/AW.png differ diff --git a/assets/img/flags/AX.png b/assets/img/flags/AX.png new file mode 100644 index 00000000..21a5e1c0 Binary files /dev/null and b/assets/img/flags/AX.png differ diff --git a/assets/img/flags/AZ.png b/assets/img/flags/AZ.png new file mode 100644 index 00000000..b6ea7c71 Binary files /dev/null and b/assets/img/flags/AZ.png differ diff --git a/assets/img/flags/BA.png b/assets/img/flags/BA.png new file mode 100644 index 00000000..570594bb Binary files /dev/null and b/assets/img/flags/BA.png differ diff --git a/assets/img/flags/BB.png b/assets/img/flags/BB.png new file mode 100644 index 00000000..3e86dbbb Binary files /dev/null and b/assets/img/flags/BB.png differ diff --git a/assets/img/flags/BD.png b/assets/img/flags/BD.png new file mode 100644 index 00000000..fc7affbf Binary files /dev/null and b/assets/img/flags/BD.png differ diff --git a/assets/img/flags/BE.png b/assets/img/flags/BE.png new file mode 100644 index 00000000..182e9add Binary files /dev/null and b/assets/img/flags/BE.png differ diff --git a/assets/img/flags/BF.png b/assets/img/flags/BF.png new file mode 100644 index 00000000..2a861b5f Binary files /dev/null and b/assets/img/flags/BF.png differ diff --git a/assets/img/flags/BG.png b/assets/img/flags/BG.png new file mode 100644 index 00000000..903ed4f0 Binary files /dev/null and b/assets/img/flags/BG.png differ diff --git a/assets/img/flags/BH.png b/assets/img/flags/BH.png new file mode 100644 index 00000000..e2514bb9 Binary files /dev/null and b/assets/img/flags/BH.png differ diff --git a/assets/img/flags/BI.png b/assets/img/flags/BI.png new file mode 100644 index 00000000..82dc6c5b Binary files /dev/null and b/assets/img/flags/BI.png differ diff --git a/assets/img/flags/BJ.png b/assets/img/flags/BJ.png new file mode 100644 index 00000000..e9f24b0b Binary files /dev/null and b/assets/img/flags/BJ.png differ diff --git a/assets/img/flags/BL.png b/assets/img/flags/BL.png new file mode 100644 index 00000000..533cce91 Binary files /dev/null and b/assets/img/flags/BL.png differ diff --git a/assets/img/flags/BM.png b/assets/img/flags/BM.png new file mode 100644 index 00000000..5b66e1f6 Binary files /dev/null and b/assets/img/flags/BM.png differ diff --git a/assets/img/flags/BN.png b/assets/img/flags/BN.png new file mode 100644 index 00000000..64cfbb9f Binary files /dev/null and b/assets/img/flags/BN.png differ diff --git a/assets/img/flags/BO.png b/assets/img/flags/BO.png new file mode 100644 index 00000000..3f0c41f7 Binary files /dev/null and b/assets/img/flags/BO.png differ diff --git a/assets/img/flags/BR.png b/assets/img/flags/BR.png new file mode 100644 index 00000000..f97b96a2 Binary files /dev/null and b/assets/img/flags/BR.png differ diff --git a/assets/img/flags/BS.png b/assets/img/flags/BS.png new file mode 100644 index 00000000..10a987f1 Binary files /dev/null and b/assets/img/flags/BS.png differ diff --git a/assets/img/flags/BT.png b/assets/img/flags/BT.png new file mode 100644 index 00000000..fe52b872 Binary files /dev/null and b/assets/img/flags/BT.png differ diff --git a/assets/img/flags/BW.png b/assets/img/flags/BW.png new file mode 100644 index 00000000..8da822f1 Binary files /dev/null and b/assets/img/flags/BW.png differ diff --git a/assets/img/flags/BY.png b/assets/img/flags/BY.png new file mode 100644 index 00000000..772539f8 Binary files /dev/null and b/assets/img/flags/BY.png differ diff --git a/assets/img/flags/BZ.png b/assets/img/flags/BZ.png new file mode 100644 index 00000000..9ae67155 Binary files /dev/null and b/assets/img/flags/BZ.png differ diff --git a/assets/img/flags/CA.png b/assets/img/flags/CA.png new file mode 100644 index 00000000..3153c20f Binary files /dev/null and b/assets/img/flags/CA.png differ diff --git a/assets/img/flags/CC.png b/assets/img/flags/CC.png new file mode 100644 index 00000000..7e5d0df2 Binary files /dev/null and b/assets/img/flags/CC.png differ diff --git a/assets/img/flags/CD.png b/assets/img/flags/CD.png new file mode 100644 index 00000000..afebbaa7 Binary files /dev/null and b/assets/img/flags/CD.png differ diff --git a/assets/img/flags/CF.png b/assets/img/flags/CF.png new file mode 100644 index 00000000..60fadb29 Binary files /dev/null and b/assets/img/flags/CF.png differ diff --git a/assets/img/flags/CG.png b/assets/img/flags/CG.png new file mode 100644 index 00000000..7a7dc51d Binary files /dev/null and b/assets/img/flags/CG.png differ diff --git a/assets/img/flags/CH.png b/assets/img/flags/CH.png new file mode 100644 index 00000000..dcdb068e Binary files /dev/null and b/assets/img/flags/CH.png differ diff --git a/assets/img/flags/CI.png b/assets/img/flags/CI.png new file mode 100644 index 00000000..25a99ef2 Binary files /dev/null and b/assets/img/flags/CI.png differ diff --git a/assets/img/flags/CK.png b/assets/img/flags/CK.png new file mode 100644 index 00000000..c8eba169 Binary files /dev/null and b/assets/img/flags/CK.png differ diff --git a/assets/img/flags/CL.png b/assets/img/flags/CL.png new file mode 100644 index 00000000..1a7c983f Binary files /dev/null and b/assets/img/flags/CL.png differ diff --git a/assets/img/flags/CM.png b/assets/img/flags/CM.png new file mode 100644 index 00000000..2b4cea9a Binary files /dev/null and b/assets/img/flags/CM.png differ diff --git a/assets/img/flags/CN.png b/assets/img/flags/CN.png new file mode 100644 index 00000000..edd5f1de Binary files /dev/null and b/assets/img/flags/CN.png differ diff --git a/assets/img/flags/CO.png b/assets/img/flags/CO.png new file mode 100644 index 00000000..ad276d07 Binary files /dev/null and b/assets/img/flags/CO.png differ diff --git a/assets/img/flags/CR.png b/assets/img/flags/CR.png new file mode 100644 index 00000000..a102ffa4 Binary files /dev/null and b/assets/img/flags/CR.png differ diff --git a/assets/img/flags/CT.png b/assets/img/flags/CT.png new file mode 100644 index 00000000..c9fafe74 Binary files /dev/null and b/assets/img/flags/CT.png differ diff --git a/assets/img/flags/CU.png b/assets/img/flags/CU.png new file mode 100644 index 00000000..99f7118e Binary files /dev/null and b/assets/img/flags/CU.png differ diff --git a/assets/img/flags/CV.png b/assets/img/flags/CV.png new file mode 100644 index 00000000..7736ea1f Binary files /dev/null and b/assets/img/flags/CV.png differ diff --git a/assets/img/flags/CW.png b/assets/img/flags/CW.png new file mode 100644 index 00000000..3f65fa78 Binary files /dev/null and b/assets/img/flags/CW.png differ diff --git a/assets/img/flags/CX.png b/assets/img/flags/CX.png new file mode 100644 index 00000000..0f383db4 Binary files /dev/null and b/assets/img/flags/CX.png differ diff --git a/assets/img/flags/CY.png b/assets/img/flags/CY.png new file mode 100644 index 00000000..a1b08de3 Binary files /dev/null and b/assets/img/flags/CY.png differ diff --git a/assets/img/flags/CZ.png b/assets/img/flags/CZ.png new file mode 100644 index 00000000..95ffbf62 Binary files /dev/null and b/assets/img/flags/CZ.png differ diff --git a/assets/img/flags/DE.png b/assets/img/flags/DE.png new file mode 100644 index 00000000..f2f6175a Binary files /dev/null and b/assets/img/flags/DE.png differ diff --git a/assets/img/flags/DJ.png b/assets/img/flags/DJ.png new file mode 100644 index 00000000..a08f8e11 Binary files /dev/null and b/assets/img/flags/DJ.png differ diff --git a/assets/img/flags/DK.png b/assets/img/flags/DK.png new file mode 100644 index 00000000..349cb415 Binary files /dev/null and b/assets/img/flags/DK.png differ diff --git a/assets/img/flags/DM.png b/assets/img/flags/DM.png new file mode 100644 index 00000000..117e74d3 Binary files /dev/null and b/assets/img/flags/DM.png differ diff --git a/assets/img/flags/DO.png b/assets/img/flags/DO.png new file mode 100644 index 00000000..892e2e2a Binary files /dev/null and b/assets/img/flags/DO.png differ diff --git a/assets/img/flags/DZ.png b/assets/img/flags/DZ.png new file mode 100644 index 00000000..5e97662f Binary files /dev/null and b/assets/img/flags/DZ.png differ diff --git a/assets/img/flags/EC.png b/assets/img/flags/EC.png new file mode 100644 index 00000000..57410880 Binary files /dev/null and b/assets/img/flags/EC.png differ diff --git a/assets/img/flags/EE.png b/assets/img/flags/EE.png new file mode 100644 index 00000000..1f118992 Binary files /dev/null and b/assets/img/flags/EE.png differ diff --git a/assets/img/flags/EG.png b/assets/img/flags/EG.png new file mode 100644 index 00000000..0e873beb Binary files /dev/null and b/assets/img/flags/EG.png differ diff --git a/assets/img/flags/EH.png b/assets/img/flags/EH.png new file mode 100644 index 00000000..a5b3b1cc Binary files /dev/null and b/assets/img/flags/EH.png differ diff --git a/assets/img/flags/ER.png b/assets/img/flags/ER.png new file mode 100644 index 00000000..50781ce5 Binary files /dev/null and b/assets/img/flags/ER.png differ diff --git a/assets/img/flags/ES.png b/assets/img/flags/ES.png new file mode 100644 index 00000000..b89db685 Binary files /dev/null and b/assets/img/flags/ES.png differ diff --git a/assets/img/flags/ET.png b/assets/img/flags/ET.png new file mode 100644 index 00000000..aa147235 Binary files /dev/null and b/assets/img/flags/ET.png differ diff --git a/assets/img/flags/EU.png b/assets/img/flags/EU.png new file mode 100644 index 00000000..2bfaf108 Binary files /dev/null and b/assets/img/flags/EU.png differ diff --git a/assets/img/flags/FI.png b/assets/img/flags/FI.png new file mode 100644 index 00000000..b5a380c5 Binary files /dev/null and b/assets/img/flags/FI.png differ diff --git a/assets/img/flags/FJ.png b/assets/img/flags/FJ.png new file mode 100644 index 00000000..1cb520c5 Binary files /dev/null and b/assets/img/flags/FJ.png differ diff --git a/assets/img/flags/FK.png b/assets/img/flags/FK.png new file mode 100644 index 00000000..a7cadb77 Binary files /dev/null and b/assets/img/flags/FK.png differ diff --git a/assets/img/flags/FM.png b/assets/img/flags/FM.png new file mode 100644 index 00000000..5a9b85cc Binary files /dev/null and b/assets/img/flags/FM.png differ diff --git a/assets/img/flags/FO.png b/assets/img/flags/FO.png new file mode 100644 index 00000000..4a49e30c Binary files /dev/null and b/assets/img/flags/FO.png differ diff --git a/assets/img/flags/FR.png b/assets/img/flags/FR.png new file mode 100644 index 00000000..0706dcc0 Binary files /dev/null and b/assets/img/flags/FR.png differ diff --git a/assets/img/flags/GA.png b/assets/img/flags/GA.png new file mode 100644 index 00000000..38899c4a Binary files /dev/null and b/assets/img/flags/GA.png differ diff --git a/assets/img/flags/GB.png b/assets/img/flags/GB.png new file mode 100644 index 00000000..43ebed3b Binary files /dev/null and b/assets/img/flags/GB.png differ diff --git a/assets/img/flags/GD.png b/assets/img/flags/GD.png new file mode 100644 index 00000000..2d33bbbd Binary files /dev/null and b/assets/img/flags/GD.png differ diff --git a/assets/img/flags/GE.png b/assets/img/flags/GE.png new file mode 100644 index 00000000..7aff2749 Binary files /dev/null and b/assets/img/flags/GE.png differ diff --git a/assets/img/flags/GG.png b/assets/img/flags/GG.png new file mode 100644 index 00000000..c0c3a78f Binary files /dev/null and b/assets/img/flags/GG.png differ diff --git a/assets/img/flags/GH.png b/assets/img/flags/GH.png new file mode 100644 index 00000000..e9b79a6d Binary files /dev/null and b/assets/img/flags/GH.png differ diff --git a/assets/img/flags/GI.png b/assets/img/flags/GI.png new file mode 100644 index 00000000..e14ebe59 Binary files /dev/null and b/assets/img/flags/GI.png differ diff --git a/assets/img/flags/GL.png b/assets/img/flags/GL.png new file mode 100644 index 00000000..6b995ff1 Binary files /dev/null and b/assets/img/flags/GL.png differ diff --git a/assets/img/flags/GM.png b/assets/img/flags/GM.png new file mode 100644 index 00000000..72c170aa Binary files /dev/null and b/assets/img/flags/GM.png differ diff --git a/assets/img/flags/GN.png b/assets/img/flags/GN.png new file mode 100644 index 00000000..99830391 Binary files /dev/null and b/assets/img/flags/GN.png differ diff --git a/assets/img/flags/GQ.png b/assets/img/flags/GQ.png new file mode 100644 index 00000000..9b020456 Binary files /dev/null and b/assets/img/flags/GQ.png differ diff --git a/assets/img/flags/GR.png b/assets/img/flags/GR.png new file mode 100644 index 00000000..dc34d191 Binary files /dev/null and b/assets/img/flags/GR.png differ diff --git a/assets/img/flags/GS.png b/assets/img/flags/GS.png new file mode 100644 index 00000000..55392f92 Binary files /dev/null and b/assets/img/flags/GS.png differ diff --git a/assets/img/flags/GT.png b/assets/img/flags/GT.png new file mode 100644 index 00000000..0b4b8b4f Binary files /dev/null and b/assets/img/flags/GT.png differ diff --git a/assets/img/flags/GU.png b/assets/img/flags/GU.png new file mode 100644 index 00000000..31e9cc57 Binary files /dev/null and b/assets/img/flags/GU.png differ diff --git a/assets/img/flags/GW.png b/assets/img/flags/GW.png new file mode 100644 index 00000000..98c66331 Binary files /dev/null and b/assets/img/flags/GW.png differ diff --git a/assets/img/flags/GY.png b/assets/img/flags/GY.png new file mode 100644 index 00000000..8cc6d9cf Binary files /dev/null and b/assets/img/flags/GY.png differ diff --git a/assets/img/flags/HK.png b/assets/img/flags/HK.png new file mode 100644 index 00000000..89c38aa1 Binary files /dev/null and b/assets/img/flags/HK.png differ diff --git a/assets/img/flags/HN.png b/assets/img/flags/HN.png new file mode 100644 index 00000000..e794c437 Binary files /dev/null and b/assets/img/flags/HN.png differ diff --git a/assets/img/flags/HR.png b/assets/img/flags/HR.png new file mode 100644 index 00000000..6f845d5d Binary files /dev/null and b/assets/img/flags/HR.png differ diff --git a/assets/img/flags/HT.png b/assets/img/flags/HT.png new file mode 100644 index 00000000..da4dc3b1 Binary files /dev/null and b/assets/img/flags/HT.png differ diff --git a/assets/img/flags/HU.png b/assets/img/flags/HU.png new file mode 100644 index 00000000..98de28af Binary files /dev/null and b/assets/img/flags/HU.png differ diff --git a/assets/img/flags/IC.png b/assets/img/flags/IC.png new file mode 100644 index 00000000..500d9dbe Binary files /dev/null and b/assets/img/flags/IC.png differ diff --git a/assets/img/flags/ID.png b/assets/img/flags/ID.png new file mode 100644 index 00000000..a14683d7 Binary files /dev/null and b/assets/img/flags/ID.png differ diff --git a/assets/img/flags/IE.png b/assets/img/flags/IE.png new file mode 100644 index 00000000..105c26b8 Binary files /dev/null and b/assets/img/flags/IE.png differ diff --git a/assets/img/flags/IL.png b/assets/img/flags/IL.png new file mode 100644 index 00000000..9ad54c5d Binary files /dev/null and b/assets/img/flags/IL.png differ diff --git a/assets/img/flags/IM.png b/assets/img/flags/IM.png new file mode 100644 index 00000000..f0ff4665 Binary files /dev/null and b/assets/img/flags/IM.png differ diff --git a/assets/img/flags/IN.png b/assets/img/flags/IN.png new file mode 100644 index 00000000..f1c32fac Binary files /dev/null and b/assets/img/flags/IN.png differ diff --git a/assets/img/flags/IQ.png b/assets/img/flags/IQ.png new file mode 100644 index 00000000..8d5a3236 Binary files /dev/null and b/assets/img/flags/IQ.png differ diff --git a/assets/img/flags/IR.png b/assets/img/flags/IR.png new file mode 100644 index 00000000..354a3ac5 Binary files /dev/null and b/assets/img/flags/IR.png differ diff --git a/assets/img/flags/IS.png b/assets/img/flags/IS.png new file mode 100644 index 00000000..87253cdb Binary files /dev/null and b/assets/img/flags/IS.png differ diff --git a/assets/img/flags/IT.png b/assets/img/flags/IT.png new file mode 100644 index 00000000..ce11f1f8 Binary files /dev/null and b/assets/img/flags/IT.png differ diff --git a/assets/img/flags/JE.png b/assets/img/flags/JE.png new file mode 100644 index 00000000..904b6101 Binary files /dev/null and b/assets/img/flags/JE.png differ diff --git a/assets/img/flags/JM.png b/assets/img/flags/JM.png new file mode 100644 index 00000000..378f70d0 Binary files /dev/null and b/assets/img/flags/JM.png differ diff --git a/assets/img/flags/JO.png b/assets/img/flags/JO.png new file mode 100644 index 00000000..270e5248 Binary files /dev/null and b/assets/img/flags/JO.png differ diff --git a/assets/img/flags/JP.png b/assets/img/flags/JP.png new file mode 100644 index 00000000..78c159ac Binary files /dev/null and b/assets/img/flags/JP.png differ diff --git a/assets/img/flags/KE.png b/assets/img/flags/KE.png new file mode 100644 index 00000000..ecbeb5db Binary files /dev/null and b/assets/img/flags/KE.png differ diff --git a/assets/img/flags/KG.png b/assets/img/flags/KG.png new file mode 100644 index 00000000..12b0dadd Binary files /dev/null and b/assets/img/flags/KG.png differ diff --git a/assets/img/flags/KH.png b/assets/img/flags/KH.png new file mode 100644 index 00000000..6fb7f578 Binary files /dev/null and b/assets/img/flags/KH.png differ diff --git a/assets/img/flags/KI.png b/assets/img/flags/KI.png new file mode 100644 index 00000000..e2762a67 Binary files /dev/null and b/assets/img/flags/KI.png differ diff --git a/assets/img/flags/KM.png b/assets/img/flags/KM.png new file mode 100644 index 00000000..43d8a75a Binary files /dev/null and b/assets/img/flags/KM.png differ diff --git a/assets/img/flags/KN.png b/assets/img/flags/KN.png new file mode 100644 index 00000000..5decf8da Binary files /dev/null and b/assets/img/flags/KN.png differ diff --git a/assets/img/flags/KP.png b/assets/img/flags/KP.png new file mode 100644 index 00000000..b303f8e7 Binary files /dev/null and b/assets/img/flags/KP.png differ diff --git a/assets/img/flags/KR.png b/assets/img/flags/KR.png new file mode 100644 index 00000000..d21bef98 Binary files /dev/null and b/assets/img/flags/KR.png differ diff --git a/assets/img/flags/KW.png b/assets/img/flags/KW.png new file mode 100644 index 00000000..6f7010b8 Binary files /dev/null and b/assets/img/flags/KW.png differ diff --git a/assets/img/flags/KY.png b/assets/img/flags/KY.png new file mode 100644 index 00000000..c4bfbd99 Binary files /dev/null and b/assets/img/flags/KY.png differ diff --git a/assets/img/flags/KZ.png b/assets/img/flags/KZ.png new file mode 100644 index 00000000..1a0ca4fd Binary files /dev/null and b/assets/img/flags/KZ.png differ diff --git a/assets/img/flags/LA.png b/assets/img/flags/LA.png new file mode 100644 index 00000000..f78e67f6 Binary files /dev/null and b/assets/img/flags/LA.png differ diff --git a/assets/img/flags/LB.png b/assets/img/flags/LB.png new file mode 100644 index 00000000..a9643c34 Binary files /dev/null and b/assets/img/flags/LB.png differ diff --git a/assets/img/flags/LC.png b/assets/img/flags/LC.png new file mode 100644 index 00000000..ab5916ba Binary files /dev/null and b/assets/img/flags/LC.png differ diff --git a/assets/img/flags/LI.png b/assets/img/flags/LI.png new file mode 100644 index 00000000..cf7bbe49 Binary files /dev/null and b/assets/img/flags/LI.png differ diff --git a/assets/img/flags/LICENSE.txt b/assets/img/flags/LICENSE.txt new file mode 100644 index 00000000..0836cb40 --- /dev/null +++ b/assets/img/flags/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (c) 2017 Go Squared Ltd. http://www.gosquared.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/assets/img/flags/LK.png b/assets/img/flags/LK.png new file mode 100644 index 00000000..a60c8edc Binary files /dev/null and b/assets/img/flags/LK.png differ diff --git a/assets/img/flags/LR.png b/assets/img/flags/LR.png new file mode 100644 index 00000000..dd3a57f7 Binary files /dev/null and b/assets/img/flags/LR.png differ diff --git a/assets/img/flags/LS.png b/assets/img/flags/LS.png new file mode 100644 index 00000000..ad2aa4a2 Binary files /dev/null and b/assets/img/flags/LS.png differ diff --git a/assets/img/flags/LT.png b/assets/img/flags/LT.png new file mode 100644 index 00000000..f40f2e28 Binary files /dev/null and b/assets/img/flags/LT.png differ diff --git a/assets/img/flags/LU.png b/assets/img/flags/LU.png new file mode 100644 index 00000000..92e72f9d Binary files /dev/null and b/assets/img/flags/LU.png differ diff --git a/assets/img/flags/LV.png b/assets/img/flags/LV.png new file mode 100644 index 00000000..3966acfc Binary files /dev/null and b/assets/img/flags/LV.png differ diff --git a/assets/img/flags/LY.png b/assets/img/flags/LY.png new file mode 100644 index 00000000..4db08453 Binary files /dev/null and b/assets/img/flags/LY.png differ diff --git a/assets/img/flags/MA.png b/assets/img/flags/MA.png new file mode 100644 index 00000000..69424d59 Binary files /dev/null and b/assets/img/flags/MA.png differ diff --git a/assets/img/flags/MC.png b/assets/img/flags/MC.png new file mode 100644 index 00000000..a14683d7 Binary files /dev/null and b/assets/img/flags/MC.png differ diff --git a/assets/img/flags/MD.png b/assets/img/flags/MD.png new file mode 100644 index 00000000..21fd6eca Binary files /dev/null and b/assets/img/flags/MD.png differ diff --git a/assets/img/flags/ME.png b/assets/img/flags/ME.png new file mode 100644 index 00000000..0ca932d9 Binary files /dev/null and b/assets/img/flags/ME.png differ diff --git a/assets/img/flags/MF.png b/assets/img/flags/MF.png new file mode 100644 index 00000000..16692f71 Binary files /dev/null and b/assets/img/flags/MF.png differ diff --git a/assets/img/flags/MG.png b/assets/img/flags/MG.png new file mode 100644 index 00000000..09f2469a Binary files /dev/null and b/assets/img/flags/MG.png differ diff --git a/assets/img/flags/MH.png b/assets/img/flags/MH.png new file mode 100644 index 00000000..3ffcf013 Binary files /dev/null and b/assets/img/flags/MH.png differ diff --git a/assets/img/flags/MK.png b/assets/img/flags/MK.png new file mode 100644 index 00000000..a6765095 Binary files /dev/null and b/assets/img/flags/MK.png differ diff --git a/assets/img/flags/ML.png b/assets/img/flags/ML.png new file mode 100644 index 00000000..bd238418 Binary files /dev/null and b/assets/img/flags/ML.png differ diff --git a/assets/img/flags/MM.png b/assets/img/flags/MM.png new file mode 100644 index 00000000..1bf0d5bb Binary files /dev/null and b/assets/img/flags/MM.png differ diff --git a/assets/img/flags/MN.png b/assets/img/flags/MN.png new file mode 100644 index 00000000..67a53355 Binary files /dev/null and b/assets/img/flags/MN.png differ diff --git a/assets/img/flags/MO.png b/assets/img/flags/MO.png new file mode 100644 index 00000000..2dc29c86 Binary files /dev/null and b/assets/img/flags/MO.png differ diff --git a/assets/img/flags/MP.png b/assets/img/flags/MP.png new file mode 100644 index 00000000..b5057540 Binary files /dev/null and b/assets/img/flags/MP.png differ diff --git a/assets/img/flags/MQ.png b/assets/img/flags/MQ.png new file mode 100644 index 00000000..4e9f76b6 Binary files /dev/null and b/assets/img/flags/MQ.png differ diff --git a/assets/img/flags/MR.png b/assets/img/flags/MR.png new file mode 100644 index 00000000..6bda8613 Binary files /dev/null and b/assets/img/flags/MR.png differ diff --git a/assets/img/flags/MS.png b/assets/img/flags/MS.png new file mode 100644 index 00000000..a860c6fe Binary files /dev/null and b/assets/img/flags/MS.png differ diff --git a/assets/img/flags/MT.png b/assets/img/flags/MT.png new file mode 100644 index 00000000..93d502bd Binary files /dev/null and b/assets/img/flags/MT.png differ diff --git a/assets/img/flags/MU.png b/assets/img/flags/MU.png new file mode 100644 index 00000000..6bf52359 Binary files /dev/null and b/assets/img/flags/MU.png differ diff --git a/assets/img/flags/MV.png b/assets/img/flags/MV.png new file mode 100644 index 00000000..b87bb2ec Binary files /dev/null and b/assets/img/flags/MV.png differ diff --git a/assets/img/flags/MW.png b/assets/img/flags/MW.png new file mode 100644 index 00000000..d75a8d30 Binary files /dev/null and b/assets/img/flags/MW.png differ diff --git a/assets/img/flags/MX.png b/assets/img/flags/MX.png new file mode 100644 index 00000000..8fa79193 Binary files /dev/null and b/assets/img/flags/MX.png differ diff --git a/assets/img/flags/MY.png b/assets/img/flags/MY.png new file mode 100644 index 00000000..a8e39961 Binary files /dev/null and b/assets/img/flags/MY.png differ diff --git a/assets/img/flags/MZ.png b/assets/img/flags/MZ.png new file mode 100644 index 00000000..0fdc38c7 Binary files /dev/null and b/assets/img/flags/MZ.png differ diff --git a/assets/img/flags/NA.png b/assets/img/flags/NA.png new file mode 100644 index 00000000..52e2a792 Binary files /dev/null and b/assets/img/flags/NA.png differ diff --git a/assets/img/flags/NC.png b/assets/img/flags/NC.png new file mode 100644 index 00000000..e3288acf Binary files /dev/null and b/assets/img/flags/NC.png differ diff --git a/assets/img/flags/NE.png b/assets/img/flags/NE.png new file mode 100644 index 00000000..841e77fb Binary files /dev/null and b/assets/img/flags/NE.png differ diff --git a/assets/img/flags/NF.png b/assets/img/flags/NF.png new file mode 100644 index 00000000..7c1af026 Binary files /dev/null and b/assets/img/flags/NF.png differ diff --git a/assets/img/flags/NG.png b/assets/img/flags/NG.png new file mode 100644 index 00000000..25fe78f0 Binary files /dev/null and b/assets/img/flags/NG.png differ diff --git a/assets/img/flags/NI.png b/assets/img/flags/NI.png new file mode 100644 index 00000000..0f66accb Binary files /dev/null and b/assets/img/flags/NI.png differ diff --git a/assets/img/flags/NL.png b/assets/img/flags/NL.png new file mode 100644 index 00000000..036658e7 Binary files /dev/null and b/assets/img/flags/NL.png differ diff --git a/assets/img/flags/NO.png b/assets/img/flags/NO.png new file mode 100644 index 00000000..38a13c4c Binary files /dev/null and b/assets/img/flags/NO.png differ diff --git a/assets/img/flags/NP.png b/assets/img/flags/NP.png new file mode 100644 index 00000000..eed654be Binary files /dev/null and b/assets/img/flags/NP.png differ diff --git a/assets/img/flags/NR.png b/assets/img/flags/NR.png new file mode 100644 index 00000000..4b2d0806 Binary files /dev/null and b/assets/img/flags/NR.png differ diff --git a/assets/img/flags/NU.png b/assets/img/flags/NU.png new file mode 100644 index 00000000..d791c4af Binary files /dev/null and b/assets/img/flags/NU.png differ diff --git a/assets/img/flags/NZ.png b/assets/img/flags/NZ.png new file mode 100644 index 00000000..913b18af Binary files /dev/null and b/assets/img/flags/NZ.png differ diff --git a/assets/img/flags/OM.png b/assets/img/flags/OM.png new file mode 100644 index 00000000..b2a16c03 Binary files /dev/null and b/assets/img/flags/OM.png differ diff --git a/assets/img/flags/PA.png b/assets/img/flags/PA.png new file mode 100644 index 00000000..fc0a34a3 Binary files /dev/null and b/assets/img/flags/PA.png differ diff --git a/assets/img/flags/PE.png b/assets/img/flags/PE.png new file mode 100644 index 00000000..ce31457e Binary files /dev/null and b/assets/img/flags/PE.png differ diff --git a/assets/img/flags/PF.png b/assets/img/flags/PF.png new file mode 100644 index 00000000..c9327096 Binary files /dev/null and b/assets/img/flags/PF.png differ diff --git a/assets/img/flags/PG.png b/assets/img/flags/PG.png new file mode 100644 index 00000000..68b758df Binary files /dev/null and b/assets/img/flags/PG.png differ diff --git a/assets/img/flags/PH.png b/assets/img/flags/PH.png new file mode 100644 index 00000000..dc75142c Binary files /dev/null and b/assets/img/flags/PH.png differ diff --git a/assets/img/flags/PK.png b/assets/img/flags/PK.png new file mode 100644 index 00000000..014af065 Binary files /dev/null and b/assets/img/flags/PK.png differ diff --git a/assets/img/flags/PL.png b/assets/img/flags/PL.png new file mode 100644 index 00000000..4d0fc51f Binary files /dev/null and b/assets/img/flags/PL.png differ diff --git a/assets/img/flags/PN.png b/assets/img/flags/PN.png new file mode 100644 index 00000000..c046e9bc Binary files /dev/null and b/assets/img/flags/PN.png differ diff --git a/assets/img/flags/PR.png b/assets/img/flags/PR.png new file mode 100644 index 00000000..7d54f19a Binary files /dev/null and b/assets/img/flags/PR.png differ diff --git a/assets/img/flags/PS.png b/assets/img/flags/PS.png new file mode 100644 index 00000000..d4d85dcf Binary files /dev/null and b/assets/img/flags/PS.png differ diff --git a/assets/img/flags/PT.png b/assets/img/flags/PT.png new file mode 100644 index 00000000..18e276e5 Binary files /dev/null and b/assets/img/flags/PT.png differ diff --git a/assets/img/flags/PW.png b/assets/img/flags/PW.png new file mode 100644 index 00000000..f9bcdc6e Binary files /dev/null and b/assets/img/flags/PW.png differ diff --git a/assets/img/flags/PY.png b/assets/img/flags/PY.png new file mode 100644 index 00000000..c289b6cf Binary files /dev/null and b/assets/img/flags/PY.png differ diff --git a/assets/img/flags/QA.png b/assets/img/flags/QA.png new file mode 100644 index 00000000..95c7485d Binary files /dev/null and b/assets/img/flags/QA.png differ diff --git a/assets/img/flags/RE.png b/assets/img/flags/RE.png new file mode 100644 index 00000000..2ff851c8 Binary files /dev/null and b/assets/img/flags/RE.png differ diff --git a/assets/img/flags/RO.png b/assets/img/flags/RO.png new file mode 100644 index 00000000..3d9c2a3e Binary files /dev/null and b/assets/img/flags/RO.png differ diff --git a/assets/img/flags/RS.png b/assets/img/flags/RS.png new file mode 100644 index 00000000..d95bcdfc Binary files /dev/null and b/assets/img/flags/RS.png differ diff --git a/assets/img/flags/RU.png b/assets/img/flags/RU.png new file mode 100644 index 00000000..a4318e7d Binary files /dev/null and b/assets/img/flags/RU.png differ diff --git a/assets/img/flags/RW.png b/assets/img/flags/RW.png new file mode 100644 index 00000000..00f5e1e0 Binary files /dev/null and b/assets/img/flags/RW.png differ diff --git a/assets/img/flags/SA.png b/assets/img/flags/SA.png new file mode 100644 index 00000000..ba3f2de9 Binary files /dev/null and b/assets/img/flags/SA.png differ diff --git a/assets/img/flags/SB.png b/assets/img/flags/SB.png new file mode 100644 index 00000000..1b6384a0 Binary files /dev/null and b/assets/img/flags/SB.png differ diff --git a/assets/img/flags/SC.png b/assets/img/flags/SC.png new file mode 100644 index 00000000..2a495183 Binary files /dev/null and b/assets/img/flags/SC.png differ diff --git a/assets/img/flags/SD.png b/assets/img/flags/SD.png new file mode 100644 index 00000000..5fc853b1 Binary files /dev/null and b/assets/img/flags/SD.png differ diff --git a/assets/img/flags/SE.png b/assets/img/flags/SE.png new file mode 100644 index 00000000..ad7854b7 Binary files /dev/null and b/assets/img/flags/SE.png differ diff --git a/assets/img/flags/SG.png b/assets/img/flags/SG.png new file mode 100644 index 00000000..8b1c5f03 Binary files /dev/null and b/assets/img/flags/SG.png differ diff --git a/assets/img/flags/SH.png b/assets/img/flags/SH.png new file mode 100644 index 00000000..4b2961be Binary files /dev/null and b/assets/img/flags/SH.png differ diff --git a/assets/img/flags/SI.png b/assets/img/flags/SI.png new file mode 100644 index 00000000..08cc3f4e Binary files /dev/null and b/assets/img/flags/SI.png differ diff --git a/assets/img/flags/SK.png b/assets/img/flags/SK.png new file mode 100644 index 00000000..d622ef05 Binary files /dev/null and b/assets/img/flags/SK.png differ diff --git a/assets/img/flags/SL.png b/assets/img/flags/SL.png new file mode 100644 index 00000000..e8a3530f Binary files /dev/null and b/assets/img/flags/SL.png differ diff --git a/assets/img/flags/SM.png b/assets/img/flags/SM.png new file mode 100644 index 00000000..f0d65724 Binary files /dev/null and b/assets/img/flags/SM.png differ diff --git a/assets/img/flags/SN.png b/assets/img/flags/SN.png new file mode 100644 index 00000000..a4fc08fd Binary files /dev/null and b/assets/img/flags/SN.png differ diff --git a/assets/img/flags/SO.png b/assets/img/flags/SO.png new file mode 100644 index 00000000..3f0f4163 Binary files /dev/null and b/assets/img/flags/SO.png differ diff --git a/assets/img/flags/SR.png b/assets/img/flags/SR.png new file mode 100644 index 00000000..6a8eea24 Binary files /dev/null and b/assets/img/flags/SR.png differ diff --git a/assets/img/flags/SS.png b/assets/img/flags/SS.png new file mode 100644 index 00000000..c71cafaa Binary files /dev/null and b/assets/img/flags/SS.png differ diff --git a/assets/img/flags/ST.png b/assets/img/flags/ST.png new file mode 100644 index 00000000..480886ca Binary files /dev/null and b/assets/img/flags/ST.png differ diff --git a/assets/img/flags/SV.png b/assets/img/flags/SV.png new file mode 100644 index 00000000..b5f69fae Binary files /dev/null and b/assets/img/flags/SV.png differ diff --git a/assets/img/flags/SX.png b/assets/img/flags/SX.png new file mode 100644 index 00000000..25f4f559 Binary files /dev/null and b/assets/img/flags/SX.png differ diff --git a/assets/img/flags/SY.png b/assets/img/flags/SY.png new file mode 100644 index 00000000..dd5927a6 Binary files /dev/null and b/assets/img/flags/SY.png differ diff --git a/assets/img/flags/SZ.png b/assets/img/flags/SZ.png new file mode 100644 index 00000000..b0615c36 Binary files /dev/null and b/assets/img/flags/SZ.png differ diff --git a/assets/img/flags/TC.png b/assets/img/flags/TC.png new file mode 100644 index 00000000..b17607b9 Binary files /dev/null and b/assets/img/flags/TC.png differ diff --git a/assets/img/flags/TD.png b/assets/img/flags/TD.png new file mode 100644 index 00000000..787eebb6 Binary files /dev/null and b/assets/img/flags/TD.png differ diff --git a/assets/img/flags/TF.png b/assets/img/flags/TF.png new file mode 100644 index 00000000..82929045 Binary files /dev/null and b/assets/img/flags/TF.png differ diff --git a/assets/img/flags/TG.png b/assets/img/flags/TG.png new file mode 100644 index 00000000..be814c69 Binary files /dev/null and b/assets/img/flags/TG.png differ diff --git a/assets/img/flags/TH.png b/assets/img/flags/TH.png new file mode 100644 index 00000000..5ff77db9 Binary files /dev/null and b/assets/img/flags/TH.png differ diff --git a/assets/img/flags/TJ.png b/assets/img/flags/TJ.png new file mode 100644 index 00000000..b0b546be Binary files /dev/null and b/assets/img/flags/TJ.png differ diff --git a/assets/img/flags/TK.png b/assets/img/flags/TK.png new file mode 100644 index 00000000..b70e8235 Binary files /dev/null and b/assets/img/flags/TK.png differ diff --git a/assets/img/flags/TL.png b/assets/img/flags/TL.png new file mode 100644 index 00000000..b7e77dce Binary files /dev/null and b/assets/img/flags/TL.png differ diff --git a/assets/img/flags/TM.png b/assets/img/flags/TM.png new file mode 100644 index 00000000..e6f69d73 Binary files /dev/null and b/assets/img/flags/TM.png differ diff --git a/assets/img/flags/TN.png b/assets/img/flags/TN.png new file mode 100644 index 00000000..2548fd92 Binary files /dev/null and b/assets/img/flags/TN.png differ diff --git a/assets/img/flags/TO.png b/assets/img/flags/TO.png new file mode 100644 index 00000000..f96d9964 Binary files /dev/null and b/assets/img/flags/TO.png differ diff --git a/assets/img/flags/TR.png b/assets/img/flags/TR.png new file mode 100644 index 00000000..3af317d9 Binary files /dev/null and b/assets/img/flags/TR.png differ diff --git a/assets/img/flags/TT.png b/assets/img/flags/TT.png new file mode 100644 index 00000000..890321ab Binary files /dev/null and b/assets/img/flags/TT.png differ diff --git a/assets/img/flags/TV.png b/assets/img/flags/TV.png new file mode 100644 index 00000000..2ec31605 Binary files /dev/null and b/assets/img/flags/TV.png differ diff --git a/assets/img/flags/TW.png b/assets/img/flags/TW.png new file mode 100644 index 00000000..26425e4b Binary files /dev/null and b/assets/img/flags/TW.png differ diff --git a/assets/img/flags/TZ.png b/assets/img/flags/TZ.png new file mode 100644 index 00000000..c1671cf7 Binary files /dev/null and b/assets/img/flags/TZ.png differ diff --git a/assets/img/flags/UA.png b/assets/img/flags/UA.png new file mode 100644 index 00000000..74c20122 Binary files /dev/null and b/assets/img/flags/UA.png differ diff --git a/assets/img/flags/UG.png b/assets/img/flags/UG.png new file mode 100644 index 00000000..c8c24436 Binary files /dev/null and b/assets/img/flags/UG.png differ diff --git a/assets/img/flags/US.png b/assets/img/flags/US.png new file mode 100644 index 00000000..31aa3f18 Binary files /dev/null and b/assets/img/flags/US.png differ diff --git a/assets/img/flags/UY.png b/assets/img/flags/UY.png new file mode 100644 index 00000000..9397cece Binary files /dev/null and b/assets/img/flags/UY.png differ diff --git a/assets/img/flags/UZ.png b/assets/img/flags/UZ.png new file mode 100644 index 00000000..1df6c882 Binary files /dev/null and b/assets/img/flags/UZ.png differ diff --git a/assets/img/flags/VA.png b/assets/img/flags/VA.png new file mode 100644 index 00000000..25a852e9 Binary files /dev/null and b/assets/img/flags/VA.png differ diff --git a/assets/img/flags/VC.png b/assets/img/flags/VC.png new file mode 100644 index 00000000..e63a9c1d Binary files /dev/null and b/assets/img/flags/VC.png differ diff --git a/assets/img/flags/VE.png b/assets/img/flags/VE.png new file mode 100644 index 00000000..875f7733 Binary files /dev/null and b/assets/img/flags/VE.png differ diff --git a/assets/img/flags/VG.png b/assets/img/flags/VG.png new file mode 100644 index 00000000..0bd002e4 Binary files /dev/null and b/assets/img/flags/VG.png differ diff --git a/assets/img/flags/VI.png b/assets/img/flags/VI.png new file mode 100644 index 00000000..69d667a5 Binary files /dev/null and b/assets/img/flags/VI.png differ diff --git a/assets/img/flags/VN.png b/assets/img/flags/VN.png new file mode 100644 index 00000000..69d87f90 Binary files /dev/null and b/assets/img/flags/VN.png differ diff --git a/assets/img/flags/VU.png b/assets/img/flags/VU.png new file mode 100644 index 00000000..5401c2a6 Binary files /dev/null and b/assets/img/flags/VU.png differ diff --git a/assets/img/flags/WF.png b/assets/img/flags/WF.png new file mode 100644 index 00000000..922b74e2 Binary files /dev/null and b/assets/img/flags/WF.png differ diff --git a/assets/img/flags/WS.png b/assets/img/flags/WS.png new file mode 100644 index 00000000..d1f62df1 Binary files /dev/null and b/assets/img/flags/WS.png differ diff --git a/assets/img/flags/YE.png b/assets/img/flags/YE.png new file mode 100644 index 00000000..bad5e1f4 Binary files /dev/null and b/assets/img/flags/YE.png differ diff --git a/assets/img/flags/YT.png b/assets/img/flags/YT.png new file mode 100644 index 00000000..676e06ca Binary files /dev/null and b/assets/img/flags/YT.png differ diff --git a/assets/img/flags/ZA.png b/assets/img/flags/ZA.png new file mode 100644 index 00000000..701e0106 Binary files /dev/null and b/assets/img/flags/ZA.png differ diff --git a/assets/img/flags/ZM.png b/assets/img/flags/ZM.png new file mode 100644 index 00000000..e3d80780 Binary files /dev/null and b/assets/img/flags/ZM.png differ diff --git a/assets/img/flags/ZW.png b/assets/img/flags/ZW.png new file mode 100644 index 00000000..79864d46 Binary files /dev/null and b/assets/img/flags/ZW.png differ diff --git a/assets/img/flags/_abkhazia.png b/assets/img/flags/_abkhazia.png new file mode 100644 index 00000000..0abf686d Binary files /dev/null and b/assets/img/flags/_abkhazia.png differ diff --git a/assets/img/flags/_basque-country.png b/assets/img/flags/_basque-country.png new file mode 100644 index 00000000..bf2494d2 Binary files /dev/null and b/assets/img/flags/_basque-country.png differ diff --git a/assets/img/flags/_british-antarctic-territory.png b/assets/img/flags/_british-antarctic-territory.png new file mode 100644 index 00000000..b29a7dc2 Binary files /dev/null and b/assets/img/flags/_british-antarctic-territory.png differ diff --git a/assets/img/flags/_commonwealth.png b/assets/img/flags/_commonwealth.png new file mode 100644 index 00000000..8f08c8a0 Binary files /dev/null and b/assets/img/flags/_commonwealth.png differ diff --git a/assets/img/flags/_england.png b/assets/img/flags/_england.png new file mode 100644 index 00000000..7acb112f Binary files /dev/null and b/assets/img/flags/_england.png differ diff --git a/assets/img/flags/_gosquared.png b/assets/img/flags/_gosquared.png new file mode 100644 index 00000000..74f2eb52 Binary files /dev/null and b/assets/img/flags/_gosquared.png differ diff --git a/assets/img/flags/_kosovo.png b/assets/img/flags/_kosovo.png new file mode 100644 index 00000000..dfbb5f01 Binary files /dev/null and b/assets/img/flags/_kosovo.png differ diff --git a/assets/img/flags/_mars.png b/assets/img/flags/_mars.png new file mode 100644 index 00000000..4f5980b7 Binary files /dev/null and b/assets/img/flags/_mars.png differ diff --git a/assets/img/flags/_nagorno-karabakh.png b/assets/img/flags/_nagorno-karabakh.png new file mode 100644 index 00000000..f5a8d271 Binary files /dev/null and b/assets/img/flags/_nagorno-karabakh.png differ diff --git a/assets/img/flags/_nato.png b/assets/img/flags/_nato.png new file mode 100644 index 00000000..fdb05410 Binary files /dev/null and b/assets/img/flags/_nato.png differ diff --git a/assets/img/flags/_northern-cyprus.png b/assets/img/flags/_northern-cyprus.png new file mode 100644 index 00000000..f9bf8bd3 Binary files /dev/null and b/assets/img/flags/_northern-cyprus.png differ diff --git a/assets/img/flags/_olympics.png b/assets/img/flags/_olympics.png new file mode 100644 index 00000000..60452238 Binary files /dev/null and b/assets/img/flags/_olympics.png differ diff --git a/assets/img/flags/_red-cross.png b/assets/img/flags/_red-cross.png new file mode 100644 index 00000000..28636e96 Binary files /dev/null and b/assets/img/flags/_red-cross.png differ diff --git a/assets/img/flags/_scotland.png b/assets/img/flags/_scotland.png new file mode 100644 index 00000000..db580403 Binary files /dev/null and b/assets/img/flags/_scotland.png differ diff --git a/assets/img/flags/_somaliland.png b/assets/img/flags/_somaliland.png new file mode 100644 index 00000000..a903a3b7 Binary files /dev/null and b/assets/img/flags/_somaliland.png differ diff --git a/assets/img/flags/_south-ossetia.png b/assets/img/flags/_south-ossetia.png new file mode 100644 index 00000000..d616841b Binary files /dev/null and b/assets/img/flags/_south-ossetia.png differ diff --git a/assets/img/flags/_united-nations.png b/assets/img/flags/_united-nations.png new file mode 100644 index 00000000..8e45e999 Binary files /dev/null and b/assets/img/flags/_united-nations.png differ diff --git a/assets/img/flags/_unknown.png b/assets/img/flags/_unknown.png new file mode 100644 index 00000000..9d91c7f4 Binary files /dev/null and b/assets/img/flags/_unknown.png differ diff --git a/assets/img/flags/_wales.png b/assets/img/flags/_wales.png new file mode 100644 index 00000000..51f13c2e Binary files /dev/null and b/assets/img/flags/_wales.png differ diff --git a/assets/img/linux.svg b/assets/img/linux.svg new file mode 100755 index 00000000..deed3874 --- /dev/null +++ b/assets/img/linux.svg @@ -0,0 +1 @@ + diff --git a/assets/img/mac.svg b/assets/img/mac.svg new file mode 100755 index 00000000..6ea43ad8 --- /dev/null +++ b/assets/img/mac.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/plants1-br.png b/assets/img/plants1-br.png new file mode 100644 index 00000000..7027c2fd Binary files /dev/null and b/assets/img/plants1-br.png differ diff --git a/assets/img/plants1.png b/assets/img/plants1.png new file mode 100644 index 00000000..8e6af5ac Binary files /dev/null and b/assets/img/plants1.png differ diff --git a/assets/img/spn-feature-carousel/access-regional-content-easily.png b/assets/img/spn-feature-carousel/access-regional-content-easily.png new file mode 100644 index 00000000..a90eba8e Binary files /dev/null and b/assets/img/spn-feature-carousel/access-regional-content-easily.png differ diff --git a/assets/img/spn-feature-carousel/built-from-the-ground-up.png b/assets/img/spn-feature-carousel/built-from-the-ground-up.png new file mode 100644 index 00000000..bb8c0d6a Binary files /dev/null and b/assets/img/spn-feature-carousel/built-from-the-ground-up.png differ diff --git a/assets/img/spn-feature-carousel/bye-bye-vpns.png b/assets/img/spn-feature-carousel/bye-bye-vpns.png new file mode 100644 index 00000000..e3bb65a6 Binary files /dev/null and b/assets/img/spn-feature-carousel/bye-bye-vpns.png differ diff --git a/assets/img/spn-feature-carousel/easily-control-your-privacy.png b/assets/img/spn-feature-carousel/easily-control-your-privacy.png new file mode 100644 index 00000000..0d386b5f Binary files /dev/null and b/assets/img/spn-feature-carousel/easily-control-your-privacy.png differ diff --git a/assets/img/spn-feature-carousel/multiple-identities-for-each-app.png b/assets/img/spn-feature-carousel/multiple-identities-for-each-app.png new file mode 100644 index 00000000..b5082f73 Binary files /dev/null and b/assets/img/spn-feature-carousel/multiple-identities-for-each-app.png differ diff --git a/assets/img/spn-login.png b/assets/img/spn-login.png new file mode 100644 index 00000000..4a5541fb Binary files /dev/null and b/assets/img/spn-login.png differ diff --git a/assets/img/windows.svg b/assets/img/windows.svg new file mode 100755 index 00000000..e3e76225 --- /dev/null +++ b/assets/img/windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/world-50m.json b/assets/world-50m.json new file mode 100644 index 00000000..4e59231a --- /dev/null +++ b/assets/world-50m.json @@ -0,0 +1 @@ +{"type":"Topology","objects":{"land":{"type":"MultiPolygon","arcs":[[[0]],[[1]],[[2]],[[3]],[[4]],[[5]],[[6]],[[7]],[[8]],[[9]],[[10]],[[11]],[[12]],[[13]],[[14]],[[15]],[[16]],[[17]],[[18]],[[19]],[[20]],[[21]],[[22]],[[23]],[[24]],[[25]],[[26]],[[27]],[[28]],[[29]],[[30]],[[31]],[[32]],[[33]],[[34]],[[35]],[[36]],[[37]],[[38]],[[39]],[[40]],[[41]],[[42]],[[43]],[[44]],[[45]],[[46]],[[47]],[[48]],[[49]],[[50]],[[51]],[[52]],[[53]],[[54]],[[55]],[[56]],[[57]],[[58]],[[59]],[[60]],[[61]],[[62,63]],[[64,65,66]],[[67]],[[68,69,70,71,72]],[[73,74,75,76,77]],[[78]],[[79]],[[80]],[[81]],[[82]],[[83]],[[84]],[[85]],[[86]],[[87]],[[88]],[[89]],[[90]],[[91]],[[92]],[[93]],[[94]],[[95]],[[96]],[[97]],[[98,99]],[[100]],[[101]],[[102]],[[103]],[[104]],[[105]],[[106]],[[107]],[[108]],[[109]],[[110]],[[111]],[[112]],[[113]],[[114]],[[115,116]],[[117]],[[118]],[[119]],[[120]],[[121]],[[122]],[[123]],[[124]],[[125]],[[126]],[[127]],[[128]],[[129]],[[130]],[[131]],[[132]],[[133],[134]],[[135]],[[136]],[[137]],[[138]],[[139]],[[140]],[[141]],[[142]],[[143]],[[144]],[[145]],[[146]],[[147]],[[148]],[[149]],[[150]],[[151]],[[152]],[[153]],[[154]],[[155]],[[156]],[[157]],[[158]],[[159]],[[160]],[[161]],[[162]],[[163]],[[164]],[[165]],[[166]],[[167]],[[168]],[[169]],[[170]],[[171]],[[172]],[[173]],[[174]],[[175]],[[176]],[[177]],[[178]],[[179]],[[180]],[[181]],[[182]],[[183]],[[184]],[[185]],[[186]],[[187]],[[188]],[[189]],[[190]],[[191]],[[192]],[[193]],[[194]],[[195]],[[196]],[[197]],[[198]],[[199]],[[200]],[[201]],[[202]],[[203]],[[204]],[[205]],[[206,207,208,209,210,211,212]],[[213]],[[214]],[[215]],[[216]],[[217]],[[218]],[[219]],[[220]],[[221]],[[222]],[[223]],[[224]],[[225]],[[226]],[[227]],[[228]],[[229,230,231,232]],[[233],[234]],[[235]],[[236]],[[237]],[[238]],[[239]],[[240]],[[241]],[[242]],[[243]],[[244]],[[245]],[[246]],[[247]],[[248]],[[249]],[[250]],[[251]],[[252]],[[253]],[[254]],[[255]],[[256]],[[257]],[[258]],[[259]],[[260,261,262,263,264,265,266]],[[267]],[[268,269,270,271]],[[272]],[[273]],[[274,275,276,277,278,279,280,281,282,283]],[[284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306]],[[307]],[[308,309]],[[310]],[[311]],[[312]],[[313]],[[314]],[[315]],[[316]],[[317]],[[318]],[[319]],[[320]],[[321]],[[322]],[[323,324]],[[325]],[[326]],[[327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946],[947],[948],[949],[950],[951],[952,953,954,955],[956],[957,958],[959],[960],[961,962,963,964,965,966],[967],[968],[969],[970],[971],[972],[973],[974],[975,976],[977],[978],[979],[980],[981],[982],[983,984,985,986],[987],[988],[989],[990],[991],[992],[993],[994],[995],[996],[997],[998],[999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014],[1015],[1016],[1017],[1018],[1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029],[1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094],[1095],[1096],[1097],[1098],[1099],[1100],[1101],[1102],[1103],[1104],[1105],[1106],[1107],[1108],[1109],[1110],[1111],[1112],[1113],[1114],[1115],[1116],[1117],[1118],[1119],[1120],[1121],[1122],[1123],[1124],[1125],[1126],[1127],[1128],[1129],[1130],[1131],[1132],[1133],[1134],[1135],[1136],[1137],[1138],[1139],[1140],[1141]],[[1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272],[1273,1274,1275,1276,1277],[1278,1279,1280,1281,1282],[1283,1284,1285,1286,1287],[1288],[1289],[1290,1291,1292,1293],[1294,1295,1296,1297,1298,1299],[1300],[1301,1302,1303,1304],[1305],[1306,1307,1308,1309,1310,1311,1312,1313],[1314],[1315,1316,1317],[1318],[1319],[1320],[1321],[1322],[1323],[1324],[1325],[1326],[1327],[1328],[1329],[1330],[1331],[1332],[1333],[1334],[1335],[1336],[1337],[1338],[1339],[1340],[1341],[1342],[1343],[1344],[1345],[1346],[1347],[1348],[1349],[1350],[1351,1352,1353,1354],[1355],[1356],[1357],[1358],[1359],[1360,1361,1362,1363],[1364],[1365],[1366],[1367],[1368],[1369,1370,1371,1372,1373,1374,1375,1376],[1377],[1378],[1379,1380,1381,1382],[1383,1384,1385,1386],[1387],[1388],[1389],[1390],[1391],[1392],[1393],[1394],[1395],[1396,1397,1398,1399],[1400],[1401],[1402],[1403],[1404],[1405],[1406],[1407],[1408],[1409],[1410],[1411],[1412],[1413],[1414],[1415],[1416],[1417],[1418],[1419],[1420],[1421],[1422],[1423],[1424],[1425],[1426],[1427],[1428]],[[1429]],[[1430]],[[1431]],[[1432]],[[1433]],[[1434]],[[1435]],[[1436]],[[1437]],[[1438]],[[1439]],[[1440]],[[1441]],[[1442]],[[1443]],[[1444]],[[1445]],[[1446]],[[1447]],[[1448,1449]],[[1450]],[[1451]],[[1452]],[[1453]],[[1454]],[[1455]],[[1456,1457,1458]],[[1459]],[[1460]],[[1461]],[[1462]],[[1463]],[[1464]],[[1465]],[[1466]],[[1467]],[[1468]],[[1469]],[[1470]],[[1471],[1472]],[[1473,1474,1475,1476,1477,1478]],[[1479]],[[1480,1481,1482,1483,1484,1485,1486,1487,1488,1489]],[[1490,1491,1492,1493,1494,1495,1496,1497]],[[1498]],[[1499]],[[1500,1501]],[[1502]],[[1503,1504,1505]],[[1506]],[[1507,1508]],[[1509,1510,1511,1512]],[[1513,1514,1515,1516]],[[1517,1518,1519,1520]],[[1521,1522,1523]],[[1524,1525,1526]],[[1527,1528,1529,1530,1531,1532,1533]],[[1534]],[[1535]],[[1536,1537,1538,1539,1540]],[[1541]],[[1542]],[[1543]],[[1544,1545]],[[1546]],[[1547,1548,1549,1550,1551,1552]],[[1553]],[[1554]],[[1555,1556,1557,1558,1559,1560,1561,1562]],[[1563,1564,1565]],[[1566,1567,1568]],[[1569]],[[1570,1571,1572]],[[1573,1574,1575]],[[1576]],[[1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587]],[[1588]],[[1589,1590,1591,1592,1593,1594]],[[1595,1596,1597,1598,1599]],[[1600]],[[1601,1602,1603]],[[1604,1605,1606]],[[1607]],[[1608]],[[1609]],[[1610]],[[1611]],[[1612]],[[1613,1614,1615,1616]],[[1617,1618,1619,1620,1621]],[[1622]],[[1623,1624,1625]],[[1626,1627,1628]],[[1629]],[[1630,1631,1632]],[[1633]],[[1634,1635,1636]],[[1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664]],[[1665]],[[1666]],[[1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688]],[[1689]],[[1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702]],[[1703,1704,1705]],[[1706,1707,1708]],[[1709]],[[1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720]],[[1721]],[[1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791]],[[1792,1793,1794,1795,1796]],[[1797,1798,1799]],[[1800,1801]],[[1802,1803,1804,1805]],[[1806,1807]],[[1808]],[[1809]],[[1810]],[[1811,1812,1813,1814]],[[1815,1816,1817]],[[1818,1819]],[[1820,1821,1822,1823]],[[1824,1825,1826,1827,1828,1829,1830]],[[1831,1832,1833]],[[1834]],[[1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851]],[[1852]],[[1853]],[[1854,1855,1856,1857]],[[1858,1859,1860,1861,1862,1863,1864,1865]],[[1866,1867]],[[1868,1869,1870]],[[1871,1872]],[[1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990],[1991],[1992]],[[1993]],[[1994,1995]],[[1996]],[[1997,1998,1999,2000]],[[2001]],[[2002]],[[2003]],[[2004]],[[2005,2006,2007,2008,2009,2010,2011]],[[2012,2013]],[[2014]],[[2015,2016]],[[2017]],[[2018]],[[2019]],[[2020]],[[2021]],[[2022]],[[2023]],[[2024]],[[2025]],[[2026]],[[2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073]],[[2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090]],[[2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112]],[[2113]],[[2114]],[[2115,2116,2117,2118]],[[2119]],[[2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130]],[[2131,2132,2133,2134,2135,2136,2137,2138]],[[2139]],[[2140]],[[2141]],[[2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156]],[[2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196],[2197]],[[2198]],[[2199]],[[2200]],[[2201,2202,2203,2204]],[[2205]],[[2206]],[[2207,2208]],[[2209]],[[2210]],[[2211]],[[2212,2213,2214]],[[2215,2216,2217,2218]],[[2219,2220,2221,2222,2223]],[[2224]],[[2225]],[[2226]],[[2227]]]},"countries":{"type":"GeometryCollection","geometries":[{"type":"Polygon","properties":{"admin":"Afghanistan","name":"Afghanistan","postal":"AF","pop_est":28400000,"iso_a2":"AF","iso_a3":"AFG"},"id":4,"arcs":[[2228,2229,2230,2231,2232,2233,2234]]},{"type":"MultiPolygon","properties":{"admin":"Angola","name":"Angola","postal":"AO","pop_est":12799293,"iso_a2":"AO","iso_a3":"AGO"},"id":24,"arcs":[[[2235,2236,1193,2237]],[[1195,2238,2239]]]},{"type":"Polygon","properties":{"admin":"Albania","name":"Albania","postal":"AL","pop_est":3639453,"iso_a2":"AL","iso_a3":"ALB"},"id":8,"arcs":[[2240,2241,2242,1236,2243,2244,1385,2245,2246]]},{"type":"Polygon","properties":{"admin":"United Arab Emirates","name":"United Arab Emirates","postal":"AE","pop_est":4798491,"iso_a2":"AE","iso_a3":"ARE"},"id":784,"arcs":[[1176,2247,2248,1174,2249]]},{"type":"MultiPolygon","properties":{"admin":"Argentina","name":"Argentina","postal":"AR","pop_est":40913584,"iso_a2":"AR","iso_a3":"ARG"},"id":32,"arcs":[[[31]],[[2250,115]],[[2251,2252,2253,670,2254,2255]]]},{"type":"Polygon","properties":{"admin":"Armenia","name":"Armenia","postal":"ARM","pop_est":2967004,"iso_a2":"AM","iso_a3":"ARM"},"id":51,"arcs":[[2256,2257,2258,2259,2260]]},{"type":"MultiPolygon","properties":{"admin":"Antarctica","name":"Antarctica","postal":"AQ","pop_est":3802,"iso_a2":"AQ","iso_a3":"ATA"},"id":10,"arcs":[[[2]],[[7]],[[8]],[[6]],[[5]],[[9]],[[11]],[[3]],[[10]],[[4]],[[0]],[[1]],[[12]],[[16]],[[17]],[[15]],[[14]],[[13]],[[19]],[[20]],[[18]],[[21]],[[22]],[[34]],[[33]],[[36]],[[37]],[[38]],[[35]],[[39]],[[40]],[[41]],[[42]],[[43]],[[32]],[[44]],[[23]],[[25]],[[24]]]},{"type":"Polygon","properties":{"admin":"French Southern and Antarctic Lands","name":"Fr. S. Antarctic Lands","postal":"TF","pop_est":140,"iso_a2":"TF","iso_a3":"ATF"},"id":260,"arcs":[[119]]},{"type":"MultiPolygon","properties":{"admin":"Australia","name":"Australia","postal":"AU","pop_est":21262641,"iso_a2":"AU","iso_a3":"AUS"},"id":36,"arcs":[[[132]],[[127]],[[128]],[[129]],[[135]],[[136]],[[148]],[[239]],[[242]],[[243]],[[233]]]},{"type":"Polygon","properties":{"admin":"Austria","name":"Austria","postal":"A","pop_est":8210281,"iso_a2":"AT","iso_a3":"AUT"},"id":40,"arcs":[[2261,2262,2263,2264,2265,2266,2267,1363,2268,2269,2270]]},{"type":"MultiPolygon","properties":{"admin":"Azerbaijan","name":"Azerbaijan","postal":"AZ","pop_est":8238672,"iso_a2":"AZ","iso_a3":"AZE"},"id":31,"arcs":[[[2271,2272,-2258]],[[1274,2273,-2261,2274,2275]]]},{"type":"Polygon","properties":{"admin":"Burundi","name":"Burundi","postal":"BI","pop_est":8988091,"iso_a2":"BI","iso_a3":"BDI"},"id":108,"arcs":[[2276,2277,1309,2278,2279,2280]]},{"type":"Polygon","properties":{"admin":"Belgium","name":"Belgium","postal":"B","pop_est":10414336,"iso_a2":"BE","iso_a3":"BEL"},"id":56,"arcs":[[2281,2282,2283,2284,2285,1249,2286,2287]]},{"type":"Polygon","properties":{"admin":"Benin","name":"Benin","postal":"BJ","pop_est":8791832,"iso_a2":"BJ","iso_a3":"BEN"},"id":204,"arcs":[[2288,1201,2289,2290,2291]]},{"type":"Polygon","properties":{"admin":"Burkina Faso","name":"Burkina Faso","postal":"BF","pop_est":15746232,"iso_a2":"BF","iso_a3":"BFA"},"id":854,"arcs":[[2292,-2291,2293,2294,2295,2296]]},{"type":"MultiPolygon","properties":{"admin":"Bangladesh","name":"Bangladesh","postal":"BD","pop_est":156050883,"iso_a2":"BD","iso_a3":"BGD"},"id":50,"arcs":[[[1430]],[[2297,1164,2298]]]},{"type":"Polygon","properties":{"admin":"Bulgaria","name":"Bulgaria","postal":"BG","pop_est":7204687,"iso_a2":"BG","iso_a3":"BGR"},"id":100,"arcs":[[1233,2299,2300,2301,2302,2303]]},{"type":"MultiPolygon","properties":{"admin":"The Bahamas","name":"Bahamas","postal":"BS","pop_est":309156,"iso_a2":"BS","iso_a3":"BHS"},"id":44,"arcs":[[[67]],[[1429]],[[1431]],[[1437]],[[1434]],[[1433]]]},{"type":"Polygon","properties":{"admin":"Bosnia and Herzegovina","name":"Bosnia and Herz.","postal":"BiH","pop_est":4613414,"iso_a2":"BA","iso_a3":"BIH"},"id":70,"arcs":[[2304,2305,2306,1239,2307]]},{"type":"Polygon","properties":{"admin":"Belarus","name":"Belarus","postal":"BY","pop_est":9648533,"iso_a2":"BY","iso_a3":"BLR"},"id":112,"arcs":[[2308,2309,2310,2311,2312]]},{"type":"Polygon","properties":{"admin":"Belize","name":"Belize","postal":"BZ","pop_est":307899,"iso_a2":"BZ","iso_a3":"BLZ"},"id":84,"arcs":[[2313,2314,657]]},{"type":"Polygon","properties":{"admin":"Bolivia","name":"Bolivia","postal":"BO","pop_est":9775246,"iso_a2":"BO","iso_a3":"BOL"},"id":68,"arcs":[[2315,-2256,2316,2317,2318,964,2319,2320,2321]]},{"type":"MultiPolygon","properties":{"admin":"Brazil","name":"Brazil","postal":"BR","pop_est":198739269,"iso_a2":"BR","iso_a3":"BRA"},"id":76,"arcs":[[[193]],[[194]],[[156]],[[247]],[[249]],[[251]],[[2322,2323,668,2324,2325,955,2326,2327,-2253,2328,2329,2330,-2322,2331,2332,2333,2334]]]},{"type":"MultiPolygon","properties":{"admin":"Brunei","name":"Brunei","postal":"BN","pop_est":388190,"iso_a2":"BN","iso_a3":"BRN"},"id":96,"arcs":[[[265,2335]],[[2336,2337,264]]]},{"type":"Polygon","properties":{"admin":"Bhutan","name":"Bhutan","postal":"BT","pop_est":691141,"iso_a2":"BT","iso_a3":"BTN"},"id":64,"arcs":[[2338,2339]]},{"type":"Polygon","properties":{"admin":"Botswana","name":"Botswana","postal":"BW","pop_est":1990876,"iso_a2":"BW","iso_a3":"BWA"},"id":72,"arcs":[[2340,2341,2342]]},{"type":"Polygon","properties":{"admin":"Central African Republic","name":"Central African Rep.","postal":"CF","pop_est":4511488,"iso_a2":"CF","iso_a3":"CAF"},"id":140,"arcs":[[2343,2344,2345,2346,2347,2348]]},{"type":"MultiPolygon","properties":{"admin":"Canada","name":"Canada","postal":"CA","pop_est":33487208,"iso_a2":"CA","iso_a3":"CAN"},"id":124,"arcs":[[[1500,1501]],[[2216,2217,2218,2215]],[[1490,1491,1492,1493,1494,1495,1496,1497]],[[1480,1481,1482,1483,1484,1485,1486,1487,1488,1489]],[[500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,2349,2350,2351,986,2352,2353,2354,2355,2356,2357,2358,2359]],[[1473,1474,1475,1476,1477,1478]],[[2142,2143,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2155,2156]],[[2157,2158,2159,2160,2161,2162,2163,2164,2165,2166,2167,2168,2169,2170,2171,2172,2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185,2186,2187,2188,2189,2190,2191,2192,2193,2194,2195,2196]],[[2201,2202,2203,2204]],[[1513,1514,1515,1516]],[[1517,1518,1519,1520]],[[1524,1525,1526]],[[1527,1528,1529,1530,1531,1532,1533]],[[1563,1564,1565]],[[1994,1995]],[[1800,1801]],[[1854,1855,1856,1857]],[[1820,1821,1822,1823]],[[1858,1859,1860,1861,2360,1863,1864,1865]],[[1831,1832,1833]],[[1834]],[[1835,1836,1837,1838,1839,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851]],[[1868,1869,1870]],[[1871,1872]],[[1806,1807]],[[1815,1816,1817]],[[1802,1803,1804,1805]],[[1818,1819]],[[1811,1812,1813,1814]],[[2012,2013]],[[2015,2016]],[[2005,2006,2007,2008,2009,2010,2011]],[[2361,2362,2363,1000,1001,1002,1003,1004,1005,1006,1007,2364,2365,1027,1028,1029,1019,1020,2366,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,2367,2368,2369,2370,2371,2372,2373,2374,2375,2376,718,719,720,721,722,723,724,725,726,727,728,729,730,731,732,733,734,735,736,737,738,739,740,741,742,743,744,2377,2378,2379,2380,2381,2382,2383,2384,2385,2386,2387,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499]],[[2027,2028,2029,2030,2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2070,2071,2072,2073]],[[2115,2116,2117,2118]],[[2131,2132,2133,2134,2135,2136,2137,2138]],[[1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983,1984,1985,1986,1987,1988,1989,1990]],[[2091,2092,2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112]],[[2026]],[[2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130]],[[2074,2075,2076,2077,2078,2079,2080,2081,2082,2083,2084,2085,2086,2087,2088,2089,2090]],[[1613,1614,1615,1616]],[[1617,1618,1619,1620,1621]],[[1689]],[[1703,1704,1705]],[[1706,1707,1708]],[[1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702]],[[1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664]],[[1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688]],[[1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720]],[[1626,1627,1628]],[[1633]],[[1634,1635,1636]],[[1623,1624,1625]],[[1630,1631,1632]],[[267]],[[268,269,270,271]],[[274,275,276,277,278,279,280,281,282,283]],[[308,309]],[[284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306]],[[1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1786,1787,1788,1789,1790,1791]]]},{"type":"Polygon","properties":{"admin":"Switzerland","name":"Switzerland","postal":"CH","pop_est":7604467,"iso_a2":"CH","iso_a3":"CHE"},"id":756,"arcs":[[1362,-2268,2388,-2266,2389,2390,2391,2392,2393]]},{"type":"MultiPolygon","properties":{"admin":"Chile","name":"Chile","postal":"CL","pop_est":16601707,"iso_a2":"CL","iso_a3":"CHL"},"id":152,"arcs":[[[30]],[[29]],[[28]],[[27]],[[114]],[[113]],[[-2251,116]],[[149]],[[121]],[[117]],[[122]],[[120]],[[118]],[[123]],[[125]],[[124]],[[131]],[[-2255,671,2394,-2317]]]},{"type":"MultiPolygon","properties":{"admin":"China","name":"China","postal":"CN","pop_est":1338612970,"iso_a2":"CN","iso_a3":"CHN"},"id":156,"arcs":[[[101]],[[1441]],[[2395,1352,2396,2397,2398,1152,2399,1155,2400,1157,2401,2402,2403,2404,-2340,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,-2229,2420,2421,2422,2423,2424,2425]]]},{"type":"Polygon","properties":{"admin":"Ivory Coast","name":"Côte d'Ivoire","postal":"CI","pop_est":20617068,"iso_a2":"CI","iso_a3":"CIV"},"id":384,"arcs":[[-2296,2426,1206,2427,2428,2429]]},{"type":"Polygon","properties":{"admin":"Cameroon","name":"Cameroon","postal":"CM","pop_est":18879301,"iso_a2":"CM","iso_a3":"CMR"},"id":120,"arcs":[[-2348,2430,2431,2432,1199,2433,2434,2435]]},{"type":"Polygon","properties":{"admin":"Democratic Republic of the Congo","name":"Dem. Rep. Congo","postal":"DRC","pop_est":68692542,"iso_a2":"CD","iso_a3":"COD"},"id":180,"arcs":[[2436,2437,2438,1316,2439,2440,1304,2441,2442,2443,2444,1290,2445,2446,-2280,2447,1311,2448,2449,-2238,1194,-2240,2450,-2346]]},{"type":"Polygon","properties":{"admin":"Republic of Congo","name":"Congo","postal":"CG","pop_est":4012809,"iso_a2":"CG","iso_a3":"COG"},"id":178,"arcs":[[-2451,-2239,1196,2451,-2431,-2347]]},{"type":"Polygon","properties":{"admin":"Colombia","name":"Colombia","postal":"CO","pop_est":45644023,"iso_a2":"CO","iso_a3":"COL"},"id":170,"arcs":[[2452,-2333,2453,2454,674,2455,663]]},{"type":"MultiPolygon","properties":{"admin":"Comoros","name":"Comoros","postal":"KM","pop_est":752438,"iso_a2":"KM","iso_a3":"COM"},"id":174,"arcs":[[[238]],[[240]]]},{"type":"MultiPolygon","properties":{"admin":"Cape Verde","name":"Cape Verde","postal":"CV","pop_est":429474,"iso_a2":"CV","iso_a3":"CPV"},"id":132,"arcs":[[[81]],[[83]],[[86]],[[85]]]},{"type":"Polygon","properties":{"admin":"Costa Rica","name":"Costa Rica","postal":"CR","pop_est":4253877,"iso_a2":"CR","iso_a3":"CRI"},"id":188,"arcs":[[661,2456,676,2457]]},{"type":"MultiPolygon","properties":{"admin":"Cuba","name":"Cuba","postal":"CU","pop_est":11451652,"iso_a2":"CU","iso_a3":"CUB"},"id":192,"arcs":[[[325]],[[326]]]},{"type":"Polygon","properties":{"admin":"Northern Cyprus","name":"N. Cyprus","postal":"CN","pop_est":265100,"iso_a2":"-99","iso_a3":"-99"},"id":-99,"arcs":[[2458,1449]]},{"type":"Polygon","properties":{"admin":"Cyprus","name":"Cyprus","postal":"CY","pop_est":531640,"iso_a2":"CY","iso_a3":"CYP"},"id":196,"arcs":[[-2459,1448]]},{"type":"Polygon","properties":{"admin":"Czech Republic","name":"Czech Rep.","postal":"CZ","pop_est":10211904,"iso_a2":"CZ","iso_a3":"CZE"},"id":203,"arcs":[[2459,2460,-2271,2461]]},{"type":"MultiPolygon","properties":{"admin":"Germany","name":"Germany","postal":"D","pop_est":82329758,"iso_a2":"DE","iso_a3":"DEU"},"id":276,"arcs":[[[2206]],[[2462,1256,2463,2464,-2462,-2270,2465,1361,-2394,-2393,-2392,2466,2467,-2284,2468,1252,2469,2470]]]},{"type":"Polygon","properties":{"admin":"Djibouti","name":"Djibouti","postal":"DJ","pop_est":516055,"iso_a2":"DJ","iso_a3":"DJI"},"id":262,"arcs":[[2471,2472,2473,1185]]},{"type":"Polygon","properties":{"admin":"Dominica","name":"Dominica","postal":"DM","pop_est":72660,"iso_a2":"DM","iso_a3":"DMA"},"id":212,"arcs":[[80]]},{"type":"MultiPolygon","properties":{"admin":"Denmark","name":"Denmark","postal":"DK","pop_est":5500510,"iso_a2":"DK","iso_a3":"DNK"},"id":208,"arcs":[[[1534]],[[1542]],[[1546]],[[1543]],[[2474,-2471,2475,1254]]]},{"type":"Polygon","properties":{"admin":"Dominican Republic","name":"Dominican Rep.","postal":"DO","pop_est":9650054,"iso_a2":"DO","iso_a3":"DOM"},"id":214,"arcs":[[2476,98]]},{"type":"Polygon","properties":{"admin":"Algeria","name":"Algeria","postal":"DZ","pop_est":34178188,"iso_a2":"DZ","iso_a3":"DZA"},"id":12,"arcs":[[2477,2478,2479,2480,2481,2482,2483,1220]]},{"type":"MultiPolygon","properties":{"admin":"Ecuador","name":"Ecuador","postal":"EC","pop_est":14573101,"iso_a2":"EC","iso_a3":"ECU"},"id":218,"arcs":[[[173]],[[195]],[[188]],[[2484,673,-2455]]]},{"type":"Polygon","properties":{"admin":"Egypt","name":"Egypt","postal":"EG","pop_est":83082869,"iso_a2":"EG","iso_a3":"EGY"},"id":818,"arcs":[[2485,2486,1182,2487,2488,1223]]},{"type":"MultiPolygon","properties":{"admin":"Eritrea","name":"Eritrea","postal":"ER","pop_est":5647168,"iso_a2":"ER","iso_a3":"ERI"},"id":232,"arcs":[[[1184,-2474,2489,2490]]]},{"type":"MultiPolygon","properties":{"admin":"Spain","name":"Spain","postal":"E","pop_est":40525002,"iso_a2":"ES","iso_a3":"ESP"},"id":724,"arcs":[[[1443]],[[1445]],[[1446]],[[1438]],[[1460]],[[1465]],[[2491,-2493,2493,1245,2494,1247]]]},{"type":"MultiPolygon","properties":{"admin":"Estonia","name":"Estonia","postal":"EST","pop_est":1299371,"iso_a2":"EE","iso_a3":"EST"},"id":233,"arcs":[[[1609]],[[1554]],[[2495,2496,1397,2497,2498,2499,1268]]]},{"type":"Polygon","properties":{"admin":"Ethiopia","name":"Ethiopia","postal":"ET","pop_est":85237338,"iso_a2":"ET","iso_a3":"ETH"},"id":231,"arcs":[[-2473,2500,2501,2502,2503,2504,-2490]]},{"type":"Polygon","properties":{"admin":"Finland","name":"Finland","postal":"FIN","pop_est":5250275,"iso_a2":"FI","iso_a3":"FIN"},"id":246,"arcs":[[2505,1270,2506,2507]]},{"type":"MultiPolygon","properties":{"admin":"Fiji","name":"Fiji","postal":"FJ","pop_est":944720,"iso_a2":"FJ","iso_a3":"FJI"},"id":242,"arcs":[[[142]],[[146]]]},{"type":"MultiPolygon","properties":{"admin":"Falkland Islands","name":"Falkland Is.","postal":"FK","pop_est":3140,"iso_a2":"FK","iso_a3":"FLK"},"id":238,"arcs":[[[151]],[[150]]]},{"type":"MultiPolygon","properties":{"admin":"France","name":"France","postal":"F","pop_est":64057792,"iso_a2":"FR","iso_a3":"FRA"},"id":250,"arcs":[[[139]],[[-2324,2508,667]],[[87]],[[82]],[[84]],[[1459]],[[2509,-2467,-2391,2510,2511,1244,-2494,-2513,-2492,1248,-2286]]]},{"type":"Polygon","properties":{"admin":"Faroe Islands","name":"Faeroe Is.","postal":"FO","pop_est":48856,"iso_a2":"FO","iso_a3":"FRO"},"id":234,"arcs":[[1853]]},{"type":"Polygon","properties":{"admin":"Gabon","name":"Gabon","postal":"GA","pop_est":1514993,"iso_a2":"GA","iso_a3":"GAB"},"id":266,"arcs":[[-2452,1197,2513,-2432]]},{"type":"MultiPolygon","properties":{"admin":"United Kingdom","name":"United Kingdom","postal":"GB","pop_est":62262000,"iso_a2":"GB","iso_a3":"GBR"},"id":826,"arcs":[[[2205]],[[2514,2208]],[[1553]],[[1569]],[[1600]],[[1607]],[[2209]],[[1993]]]},{"type":"Polygon","properties":{"admin":"Georgia","name":"Georgia","postal":"GE","pop_est":4615807,"iso_a2":"GE","iso_a3":"GEO"},"id":268,"arcs":[[-2275,-2260,2515,1229,2516]]},{"type":"Polygon","properties":{"admin":"Ghana","name":"Ghana","postal":"GH","pop_est":23832495,"iso_a2":"GH","iso_a3":"GHA"},"id":288,"arcs":[[2517,1203,2518,1205,-2427,-2295]]},{"type":"Polygon","properties":{"admin":"Guinea","name":"Guinea","postal":"GN","pop_est":10057975,"iso_a2":"GN","iso_a3":"GIN"},"id":324,"arcs":[[2519,-2429,2520,2521,1209,2522,2523]]},{"type":"Polygon","properties":{"admin":"Gambia","name":"Gambia","postal":"GM","pop_est":1782893,"iso_a2":"GM","iso_a3":"GMB"},"id":270,"arcs":[[1212,2524]]},{"type":"Polygon","properties":{"admin":"Guinea Bissau","name":"Guinea-Bissau","postal":"GW","pop_est":1533964,"iso_a2":"GW","iso_a3":"GNB"},"id":624,"arcs":[[1210,2525,-2523]]},{"type":"MultiPolygon","properties":{"admin":"Equatorial Guinea","name":"Eq. Guinea","postal":"GQ","pop_est":650702,"iso_a2":"GQ","iso_a3":"GNQ"},"id":226,"arcs":[[[1198,-2433,-2514]],[[106]]]},{"type":"MultiPolygon","properties":{"admin":"Greece","name":"Greece","postal":"GR","pop_est":10737428,"iso_a2":"GR","iso_a3":"GRC"},"id":300,"arcs":[[[1451]],[[1447]],[[1454]],[[1455]],[[1462]],[[1463]],[[1461]],[[1464]],[[1235,-2243,2526,-2301,2527]]]},{"type":"MultiPolygon","properties":{"admin":"Greenland","name":"Greenland","postal":"GL","pop_est":57600,"iso_a2":"GL","iso_a3":"GRL"},"id":304,"arcs":[[[2017]],[[2018]],[[2003]],[[2025]],[[1622]],[[1709]],[[307]],[[312]],[[2113]]]},{"type":"Polygon","properties":{"admin":"Guatemala","name":"Guatemala","postal":"GT","pop_est":13276517,"iso_a2":"GT","iso_a3":"GTM"},"id":320,"arcs":[[-2314,658,2528,2529,680,2530]]},{"type":"Polygon","properties":{"admin":"Guam","name":"Guam","postal":"GU","pop_est":178430,"iso_a2":"GU","iso_a3":"GUM"},"id":316,"arcs":[[88]]},{"type":"Polygon","properties":{"admin":"Guyana","name":"Guyana","postal":"GY","pop_est":772298,"iso_a2":"GY","iso_a3":"GUY"},"id":328,"arcs":[[2531,-2335,2532,665]]},{"type":"Polygon","properties":{"admin":"Hong Kong S.A.R.","name":"Hong Kong","postal":"HK","pop_est":7061200,"iso_a2":"HK","iso_a3":"HKG"},"id":344,"arcs":[[-2400,2533,1154]]},{"type":"Polygon","properties":{"admin":"Honduras","name":"Honduras","postal":"HN","pop_est":7792854,"iso_a2":"HN","iso_a3":"HND"},"id":340,"arcs":[[2534,678,2535,-2529,659]]},{"type":"MultiPolygon","properties":{"admin":"Croatia","name":"Croatia","postal":"HR","pop_est":4489409,"iso_a2":"HR","iso_a3":"HRV"},"id":191,"arcs":[[[-2307,2536,1238]],[[2537,-2308,1240,2538,2539]]]},{"type":"MultiPolygon","properties":{"admin":"Haiti","name":"Haiti","postal":"HT","pop_est":9035536,"iso_a2":"HT","iso_a3":"HTI"},"id":332,"arcs":[[[-2477,99]]]},{"type":"Polygon","properties":{"admin":"Hungary","name":"Hungary","postal":"HU","pop_est":9905596,"iso_a2":"HU","iso_a3":"HUN"},"id":348,"arcs":[[2540,2541,2542,-2540,2543,-2263,2544]]},{"type":"MultiPolygon","properties":{"admin":"Indonesia","name":"Indonesia","postal":"INDO","pop_est":240271522,"iso_a2":"ID","iso_a3":"IDN"},"id":360,"arcs":[[[245]],[[203]],[[2545,207,2546,2547,2548,210,2549,2550]],[[214]],[[219]],[[218]],[[223]],[[220]],[[216]],[[213]],[[217]],[[225]],[[226]],[[228]],[[153]],[[155]],[[227]],[[164]],[[167]],[[169]],[[157]],[[171]],[[158]],[[162]],[[159]],[[160]],[[175]],[[176]],[[177]],[[178]],[[179]],[[182]],[[181]],[[183]],[[180]],[[185]],[[184]],[[187]],[[190]],[[189]],[[192]],[[191]],[[2551,2552,2553,2554,232]],[[197]],[[196]],[[198]],[[248]],[[253]],[[254]],[[255]],[[256]],[[252]],[[257]],[[186]],[[102]],[[259]],[[103]],[[104]],[[107]],[[2555,261,2556]],[[105]],[[258]]]},{"type":"Polygon","properties":{"admin":"Isle of Man","name":"Isle of Man","postal":"IM","pop_est":76512,"iso_a2":"IM","iso_a3":"IMN"},"id":833,"arcs":[[2200]]},{"type":"MultiPolygon","properties":{"admin":"India","name":"India","postal":"IND","pop_est":1166079220,"iso_a2":"IN","iso_a3":"IND"},"id":356,"arcs":[[[111]],[[52]],[[92]],[[-2412,2557,-2410,2558,-2408,2559,-2406,-2339,-2405,2560,-2299,1165,2561,2562,2563,-2417,2564,-2415,2565,-2413]]]},{"type":"Polygon","properties":{"admin":"Ireland","name":"Ireland","postal":"IRL","pop_est":4203200,"iso_a2":"IE","iso_a3":"IRL"},"id":372,"arcs":[[2207,-2515]]},{"type":"MultiPolygon","properties":{"admin":"Iran","name":"Iran","postal":"IRN","pop_est":66429284,"iso_a2":"IR","iso_a3":"IRN"},"id":364,"arcs":[[[1435]],[[-2257,-2274,1275,2566,-2232,2567,2568,2569,1168,2570,2571,-2272]]]},{"type":"Polygon","properties":{"admin":"Iraq","name":"Iraq","postal":"IRQ","pop_est":31129225,"iso_a2":"IQ","iso_a3":"IRQ"},"id":368,"arcs":[[-2571,1169,2572,2573,2574,2575,2576]]},{"type":"Polygon","properties":{"admin":"Iceland","name":"Iceland","postal":"IS","pop_est":306694,"iso_a2":"IS","iso_a3":"ISL"},"id":352,"arcs":[[1852]]},{"type":"Polygon","properties":{"admin":"Israel","name":"Israel","postal":"IS","pop_est":7233701,"iso_a2":"IL","iso_a3":"ISR"},"id":376,"arcs":[[2577,2578,2579,2580,2581,1181,-2487,2582,1225,2583,2584]]},{"type":"MultiPolygon","properties":{"admin":"Italy","name":"Italy","postal":"I","pop_est":58126212,"iso_a2":"IT","iso_a3":"ITA"},"id":380,"arcs":[[[1452]],[[1466]],[[2585,1242,-2511,-2390,-2265]]]},{"type":"Polygon","properties":{"admin":"Jamaica","name":"Jamaica","postal":"J","pop_est":2825928,"iso_a2":"JM","iso_a3":"JAM"},"id":388,"arcs":[[97]]},{"type":"Polygon","properties":{"admin":"Jordan","name":"Jordan","postal":"J","pop_est":6342948,"iso_a2":"JO","iso_a3":"JOR"},"id":400,"arcs":[[2586,1180,-2582,-2581,-2580,2587,2588,-2578,2589,-2575]]},{"type":"MultiPolygon","properties":{"admin":"Japan","name":"Japan","postal":"J","pop_est":127078679,"iso_a2":"JP","iso_a3":"JPN"},"id":392,"arcs":[[[1432]],[[1444]],[[1467]],[[1470]],[[1450]],[[1453]],[[1471]],[[1499]]]},{"type":"Polygon","properties":{"admin":"Siachen Glacier","name":"Siachen Glacier","postal":"SG","pop_est":6000,"iso_a2":"-99","iso_a3":"-99"},"id":-99,"arcs":[[-2563,2590,-2419]]},{"type":"MultiPolygon","properties":{"admin":"Kazakhstan","name":"Kazakhstan","postal":"KZ","pop_est":15399437,"iso_a2":"KZ","iso_a3":"KAZ"},"id":398,"arcs":[[[2591,2592,2593,1372]],[[-2423,2594,2595,2596,1376,2597,2598,2599,1277,2600]]]},{"type":"Polygon","properties":{"admin":"Kenya","name":"Kenya","postal":"KE","pop_est":39002772,"iso_a2":"KE","iso_a3":"KEN"},"id":404,"arcs":[[2601,1188,2602,2603,1299,2604,2605,2606,-2503]]},{"type":"Polygon","properties":{"admin":"Kyrgyzstan","name":"Kyrgyzstan","postal":"KG","pop_est":5431747,"iso_a2":"KG","iso_a3":"KGZ"},"id":417,"arcs":[[-2422,2607,2608,-2595]]},{"type":"Polygon","properties":{"admin":"Cambodia","name":"Cambodia","postal":"KH","pop_est":14494293,"iso_a2":"KH","iso_a3":"KHM"},"id":116,"arcs":[[1159,2609,2610,2611]]},{"type":"MultiPolygon","properties":{"admin":"South Korea","name":"Korea","postal":"KR","pop_est":48508972,"iso_a2":"KR","iso_a3":"KOR"},"id":410,"arcs":[[[1469]],[[1150,2612]]]},{"type":"Polygon","properties":{"admin":"Kosovo","name":"Kosovo","postal":"KO","pop_est":1804838,"iso_a2":"-99","iso_a3":"-99"},"id":-99,"arcs":[[2613,-2241,2614,2615]]},{"type":"MultiPolygon","properties":{"admin":"Kuwait","name":"Kuwait","postal":"KW","pop_est":2691158,"iso_a2":"KW","iso_a3":"KWT"},"id":414,"arcs":[[[1442]],[[2616,-2573,1170]]]},{"type":"Polygon","properties":{"admin":"Laos","name":"Lao PDR","postal":"LA","pop_est":6834942,"iso_a2":"LA","iso_a3":"LAO"},"id":418,"arcs":[[2617,-2611,2618,2619,-2403]]},{"type":"Polygon","properties":{"admin":"Lebanon","name":"Lebanon","postal":"LB","pop_est":4017095,"iso_a2":"LB","iso_a3":"LBN"},"id":422,"arcs":[[-2584,1226,2620]]},{"type":"Polygon","properties":{"admin":"Liberia","name":"Liberia","postal":"LR","pop_est":3441790,"iso_a2":"LR","iso_a3":"LBR"},"id":430,"arcs":[[-2428,1207,2621,-2521]]},{"type":"Polygon","properties":{"admin":"Libya","name":"Libya","postal":"LY","pop_est":6310434,"iso_a2":"LY","iso_a3":"LBY"},"id":434,"arcs":[[-2489,2622,2623,2624,-2479,2625,1222]]},{"type":"Polygon","properties":{"admin":"Sri Lanka","name":"Sri Lanka","postal":"LK","pop_est":21324791,"iso_a2":"LK","iso_a3":"LKA"},"id":144,"arcs":[[108]]},{"type":"Polygon","properties":{"admin":"Lesotho","name":"Lesotho","postal":"LS","pop_est":2130819,"iso_a2":"LS","iso_a3":"LSO"},"id":426,"arcs":[[2626]]},{"type":"Polygon","properties":{"admin":"Lithuania","name":"Lithuania","postal":"LT","pop_est":3555179,"iso_a2":"LT","iso_a3":"LTU"},"id":440,"arcs":[[-2312,2627,2628,2629,1266,2630]]},{"type":"Polygon","properties":{"admin":"Luxembourg","name":"Luxembourg","postal":"L","pop_est":491775,"iso_a2":"LU","iso_a3":"LUX"},"id":442,"arcs":[[-2468,-2510,-2285]]},{"type":"Polygon","properties":{"admin":"Latvia","name":"Latvia","postal":"LV","pop_est":2231503,"iso_a2":"LV","iso_a3":"LVA"},"id":428,"arcs":[[2631,-2313,-2631,1267,-2500]]},{"type":"Polygon","properties":{"admin":"Morocco","name":"Morocco","postal":"MA","pop_est":34859364,"iso_a2":"MA","iso_a3":"MAR"},"id":504,"arcs":[[-2484,2632,2633,1217,2634,1219]]},{"type":"Polygon","properties":{"admin":"Moldova","name":"Moldova","postal":"MD","pop_est":4320748,"iso_a2":"MD","iso_a3":"MDA"},"id":498,"arcs":[[2635,2636]]},{"type":"Polygon","properties":{"admin":"Madagascar","name":"Madagascar","postal":"MG","pop_est":20653556,"iso_a2":"MG","iso_a3":"MDG"},"id":450,"arcs":[[235]]},{"type":"MultiPolygon","properties":{"admin":"Mexico","name":"Mexico","postal":"MX","pop_est":111211789,"iso_a2":"MX","iso_a3":"MEX"},"id":484,"arcs":[[[1439]],[[1440]],[[2637,2638,2639,2640,2641,2642,2643,2644,2645,2646,2647,2648,2649,2650,2651,2652,2653,2654,656,-2315,-2531,681,2655,2656,2657]]]},{"type":"Polygon","properties":{"admin":"Macedonia","name":"Macedonia","postal":"MK","pop_est":2066718,"iso_a2":"MK","iso_a3":"MKD"},"id":807,"arcs":[[-2302,-2527,-2242,-2614,2658]]},{"type":"Polygon","properties":{"admin":"Mali","name":"Mali","postal":"ML","pop_est":12666987,"iso_a2":"ML","iso_a3":"MLI"},"id":466,"arcs":[[2659,-2297,-2430,-2520,2660,2661,-2481]]},{"type":"MultiPolygon","properties":{"admin":"Myanmar","name":"Myanmar","postal":"MM","pop_est":48137741,"iso_a2":"MM","iso_a3":"MMR"},"id":104,"arcs":[[[-2620,2662,1163,-2298,-2561,-2404]]]},{"type":"Polygon","properties":{"admin":"Montenegro","name":"Montenegro","postal":"ME","pop_est":672180,"iso_a2":"ME","iso_a3":"MNE"},"id":499,"arcs":[[2663,-2615,-2247,2664,1383,2665,-2244,1237,-2537,-2306]]},{"type":"Polygon","properties":{"admin":"Mongolia","name":"Mongolia","postal":"MN","pop_est":3041142,"iso_a2":"MN","iso_a3":"MNG"},"id":496,"arcs":[[-2425,2666]]},{"type":"Polygon","properties":{"admin":"Mozambique","name":"Mozambique","postal":"MZ","pop_est":21669278,"iso_a2":"MZ","iso_a3":"MOZ"},"id":508,"arcs":[[2667,2668,2669,2670,2671,2672,2673,1284,2674,2675,1190]]},{"type":"Polygon","properties":{"admin":"Mauritania","name":"Mauritania","postal":"MR","pop_est":3129486,"iso_a2":"MR","iso_a3":"MRT"},"id":478,"arcs":[[2676,1214,2677,-2482,-2662]]},{"type":"Polygon","properties":{"admin":"Mauritius","name":"Mauritius","postal":"MU","pop_est":1284264,"iso_a2":"MU","iso_a3":"MUS"},"id":480,"arcs":[[141]]},{"type":"Polygon","properties":{"admin":"Malawi","name":"Malawi","postal":"MW","pop_est":14268711,"iso_a2":"MW","iso_a3":"MWI"},"id":454,"arcs":[[1287,2678,-2673,2679,2680]]},{"type":"MultiPolygon","properties":{"admin":"Malaysia","name":"Malaysia","postal":"MY","pop_est":25715819,"iso_a2":"MY","iso_a3":"MYS"},"id":458,"arcs":[[[1161,2681]],[[2682,-2557,262,2683,-2337,-2336,266]]]},{"type":"Polygon","properties":{"admin":"Namibia","name":"Namibia","postal":"NA","pop_est":2108665,"iso_a2":"NA","iso_a3":"NAM"},"id":516,"arcs":[[2684,-2343,2685,1192,-2237]]},{"type":"MultiPolygon","properties":{"admin":"New Caledonia","name":"New Caledonia","postal":"NC","pop_est":227436,"iso_a2":"NC","iso_a3":"NCL"},"id":540,"arcs":[[[140]],[[138]]]},{"type":"Polygon","properties":{"admin":"Niger","name":"Niger","postal":"NE","pop_est":15306252,"iso_a2":"NE","iso_a3":"NER"},"id":562,"arcs":[[2686,2687,-2292,-2293,-2660,-2480,-2625]]},{"type":"Polygon","properties":{"admin":"Nigeria","name":"Nigeria","postal":"NG","pop_est":149229090,"iso_a2":"NG","iso_a3":"NGA"},"id":566,"arcs":[[2688,-2434,1200,-2289,-2688]]},{"type":"Polygon","properties":{"admin":"Nicaragua","name":"Nicaragua","postal":"NI","pop_est":5891199,"iso_a2":"NI","iso_a3":"NIC"},"id":558,"arcs":[[660,-2458,677,-2535]]},{"type":"MultiPolygon","properties":{"admin":"Netherlands","name":"Netherlands","postal":"NL","pop_est":16715999,"iso_a2":"NL","iso_a3":"NLD"},"id":528,"arcs":[[[-2287,1250]],[[2224]],[[1251,-2469,-2283,2689,-2288],[1388]]]},{"type":"MultiPolygon","properties":{"admin":"Norway","name":"Norway","postal":"N","pop_est":4676305,"iso_a2":"NO","iso_a3":"NOR"},"id":578,"arcs":[[[1810]],[[1809]],[[2014]],[[2019]],[[2002]],[[2690,-2508,2691,1143]],[[2021]],[[1629]],[[1721]],[[313]]]},{"type":"Polygon","properties":{"admin":"Nepal","name":"Nepal","postal":"NP","pop_est":28563377,"iso_a2":"NP","iso_a3":"NPL"},"id":524,"arcs":[[-2560,-2407]]},{"type":"MultiPolygon","properties":{"admin":"New Zealand","name":"New Zealand","postal":"NZ","pop_est":4213418,"iso_a2":"NZ","iso_a3":"NZL"},"id":554,"arcs":[[[126]],[[45]],[[130]],[[133]]]},{"type":"MultiPolygon","properties":{"admin":"Oman","name":"Oman","postal":"OM","pop_est":3418085,"iso_a2":"OM","iso_a3":"OMN"},"id":512,"arcs":[[[78]],[[1177,2692,2693,-2248]],[[-2250,1175]]]},{"type":"Polygon","properties":{"admin":"Pakistan","name":"Pakistan","postal":"PK","pop_est":176242949,"iso_a2":"PK","iso_a3":"PAK"},"id":586,"arcs":[[-2591,-2562,1166,2694,-2569,-2230,-2420]]},{"type":"MultiPolygon","properties":{"admin":"Panama","name":"Panama","postal":"PA","pop_est":3360474,"iso_a2":"PA","iso_a3":"PAN"},"id":591,"arcs":[[[56]],[[-2456,675,-2457,662]]]},{"type":"Polygon","properties":{"admin":"Peru","name":"Peru","postal":"PE","pop_est":29546963,"iso_a2":"PE","iso_a3":"PER"},"id":604,"arcs":[[-2332,-2321,2695,966,2696,962,2697,-2318,-2395,672,-2485,-2454]]},{"type":"MultiPolygon","properties":{"admin":"Philippines","name":"Philippines","postal":"PH","pop_est":97976603,"iso_a2":"PH","iso_a3":"PHL"},"id":608,"arcs":[[[110]],[[109]],[[112]],[[59]],[[49]],[[60]],[[61]],[[58]],[[53]],[[54]],[[79]],[[55]],[[89]],[[90]],[[93]],[[94]],[[95]],[[96]]]},{"type":"MultiPolygon","properties":{"admin":"Papua New Guinea","name":"Papua New Guinea","postal":"PG","pop_est":6057263,"iso_a2":"PG","iso_a3":"PNG"},"id":598,"arcs":[[[244]],[[202]],[[200]],[[201]],[[204]],[[166]],[[163]],[[165]],[[170]],[[2698,-2554,2699,2700,230]],[[161]],[[172]],[[174]]]},{"type":"Polygon","properties":{"admin":"Poland","name":"Poland","postal":"PL","pop_est":38482919,"iso_a2":"PL","iso_a3":"POL"},"id":616,"arcs":[[2701,-2628,-2311,2702,2703,-2460,-2465,2704,1258,2705,-2707,1261]]},{"type":"Polygon","properties":{"admin":"Puerto Rico","name":"Puerto Rico","postal":"PR","pop_est":3971020,"iso_a2":"PR","iso_a3":"PRI"},"id":630,"arcs":[[100]]},{"type":"Polygon","properties":{"admin":"North Korea","name":"Dem. Rep. Korea","postal":"KP","pop_est":22665345,"iso_a2":"KP","iso_a3":"PRK"},"id":408,"arcs":[[2707,1149,-2613,1151,-2399]]},{"type":"MultiPolygon","properties":{"admin":"Portugal","name":"Portugal","postal":"P","pop_est":10707924,"iso_a2":"PT","iso_a3":"PRT"},"id":620,"arcs":[[[1468]],[[1246,-2495]]]},{"type":"Polygon","properties":{"admin":"Paraguay","name":"Paraguay","postal":"PY","pop_est":6995655,"iso_a2":"PY","iso_a3":"PRY"},"id":600,"arcs":[[-2331,-2330,-2329,-2252,-2316]]},{"type":"MultiPolygon","properties":{"admin":"Palestine","name":"Palestine","postal":"PAL","pop_est":4119083,"iso_a2":"PS","iso_a3":"PSE"},"id":275,"arcs":[[[-2588,-2579,-2589]]]},{"type":"MultiPolygon","properties":{"admin":"French Polynesia","name":"Fr. Polynesia","postal":"PF","pop_est":287032,"iso_a2":"PF","iso_a3":"PYF"},"id":258,"arcs":[[[143]]]},{"type":"Polygon","properties":{"admin":"Qatar","name":"Qatar","postal":"QA","pop_est":833285,"iso_a2":"QA","iso_a3":"QAT"},"id":634,"arcs":[[2708,1172]]},{"type":"Polygon","properties":{"admin":"Romania","name":"Romania","postal":"RO","pop_est":22215421,"iso_a2":"RO","iso_a3":"ROU"},"id":642,"arcs":[[2709,1232,-2304,2710,-2542,2711,-2636]]},{"type":"MultiPolygon","properties":{"admin":"Russia","name":"Russia","postal":"RUS","pop_est":140041247,"iso_a2":"RU","iso_a3":"RUS"},"id":643,"arcs":[[[1498]],[[1502]],[[1479]],[[2141]],[[2198]],[[1535]],[[2712,-2629,-2702,1262,2713,1264]],[[1541]],[[1608]],[[2714,1867]],[[1808]],[[2004]],[[2001]],[[2020]],[[2022]],[[1996]],[[2715,1998,2716,2000]],[[2024]],[[2023]],[[2114]],[[2139]],[[2119]],[[1610]],[[1611]],[[1612]],[[1666]],[[1665]],[[2140]],[[2717,1148,-2708,-2398,2718,1354,2719,-2426,-2667,-2424,-2601,1273,-2276,-2517,1230,2720,-2309,-2632,-2499,2721,1399,2722,-2496,1269,-2506,-2691,1144,2723,1146]],[[272]],[[310]],[[273]],[[311]],[[316]],[[317]],[[318]],[[314]],[[319]],[[321]],[[320]],[[315]],[[322]]]},{"type":"Polygon","properties":{"admin":"Rwanda","name":"Rwanda","postal":"RW","pop_est":10473282,"iso_a2":"RW","iso_a3":"RWA"},"id":646,"arcs":[[2724,-2281,-2447,2725,1292,2726,-2444,2727]]},{"type":"Polygon","properties":{"admin":"Western Sahara","name":"W. Sahara","postal":"WS","pop_est":-99,"iso_a2":"EH","iso_a3":"ESH"},"id":732,"arcs":[[-2483,-2678,1215,2728,-2633]]},{"type":"MultiPolygon","properties":{"admin":"Saudi Arabia","name":"Saudi Arabia","postal":"SA","pop_est":28686633,"iso_a2":"SA","iso_a3":"SAU"},"id":682,"arcs":[[[-2617,1171,-2709,1173,-2249,-2694,2729,1179,-2587,-2574]]]},{"type":"Polygon","properties":{"admin":"Sudan","name":"Sudan","postal":"SD","pop_est":25946220,"iso_a2":"SD","iso_a3":"SDN"},"id":729,"arcs":[[1183,-2491,-2505,2730,-2344,2731,-2623,-2488]]},{"type":"Polygon","properties":{"admin":"South Sudan","name":"S. Sudan","postal":"SS","pop_est":10625176,"iso_a2":"SS","iso_a3":"SSD"},"id":728,"arcs":[[-2504,-2607,2732,-2437,-2345,-2731]]},{"type":"Polygon","properties":{"admin":"Senegal","name":"Senegal","postal":"SN","pop_est":13711597,"iso_a2":"SN","iso_a3":"SEN"},"id":686,"arcs":[[-2661,-2524,-2526,1211,-2525,1213,-2677]]},{"type":"Polygon","properties":{"admin":"South Georgia and South Sandwich Islands","name":"S. Geo. and S. Sandw. Is.","postal":"GS","pop_est":30,"iso_a2":"GS","iso_a3":"SGS"},"id":239,"arcs":[[26]]},{"type":"MultiPolygon","properties":{"admin":"Solomon Islands","name":"Solomon Is.","postal":"SB","pop_est":595613,"iso_a2":"SB","iso_a3":"SLB"},"id":90,"arcs":[[[241]],[[246]],[[199]],[[215]],[[205]],[[221]],[[224]],[[222]],[[152]]]},{"type":"MultiPolygon","properties":{"admin":"Sierra Leone","name":"Sierra Leone","postal":"SL","pop_est":6440053,"iso_a2":"SL","iso_a3":"SLE"},"id":694,"arcs":[[[57]],[[-2622,1208,-2522]]]},{"type":"Polygon","properties":{"admin":"El Salvador","name":"El Salvador","postal":"SV","pop_est":7185218,"iso_a2":"SV","iso_a3":"SLV"},"id":222,"arcs":[[-2536,679,-2530]]},{"type":"Polygon","properties":{"admin":"Somaliland","name":"Somaliland","postal":"SL","pop_est":3500000,"iso_a2":"-99","iso_a3":"-99"},"id":-99,"arcs":[[2733,-2501,-2472,1186]]},{"type":"Polygon","properties":{"admin":"Somalia","name":"Somalia","postal":"SO","pop_est":9832017,"iso_a2":"SO","iso_a3":"SOM"},"id":706,"arcs":[[-2602,-2502,-2734,1187]]},{"type":"Polygon","properties":{"admin":"Republic of Serbia","name":"Serbia","postal":"RS","pop_est":7379339,"iso_a2":"RS","iso_a3":"SRB"},"id":688,"arcs":[[-2711,-2303,-2659,-2616,-2664,-2305,-2538,-2543]]},{"type":"Polygon","properties":{"admin":"Sao Tome and Principe","name":"São Tomé and Principe","postal":"ST","pop_est":212679,"iso_a2":"ST","iso_a3":"STP"},"id":678,"arcs":[[250]]},{"type":"Polygon","properties":{"admin":"Suriname","name":"Suriname","postal":"SR","pop_est":481267,"iso_a2":"SR","iso_a3":"SUR"},"id":740,"arcs":[[-2509,-2323,-2532,666]]},{"type":"Polygon","properties":{"admin":"Slovakia","name":"Slovakia","postal":"SK","pop_est":5463046,"iso_a2":"SK","iso_a3":"SVK"},"id":703,"arcs":[[2734,-2545,-2262,-2461,-2704]]},{"type":"Polygon","properties":{"admin":"Slovenia","name":"Slovenia","postal":"SLO","pop_est":2005692,"iso_a2":"SI","iso_a3":"SVN"},"id":705,"arcs":[[-2539,1241,-2586,-2264,-2544]]},{"type":"MultiPolygon","properties":{"admin":"Sweden","name":"Sweden","postal":"S","pop_est":9059651,"iso_a2":"SE","iso_a3":"SWE"},"id":752,"arcs":[[[1588]],[[1576]],[[1271,2735,1142,-2692,-2507]]]},{"type":"Polygon","properties":{"admin":"Swaziland","name":"Swaziland","postal":"SW","pop_est":1123913,"iso_a2":"SZ","iso_a3":"SWZ"},"id":748,"arcs":[[-2669,2736]]},{"type":"Polygon","properties":{"admin":"Syria","name":"Syria","postal":"SYR","pop_est":20178485,"iso_a2":"SY","iso_a3":"SYR"},"id":760,"arcs":[[-2576,-2590,-2585,-2621,1227,2737]]},{"type":"Polygon","properties":{"admin":"Chad","name":"Chad","postal":"TD","pop_est":10329208,"iso_a2":"TD","iso_a3":"TCD"},"id":148,"arcs":[[-2732,-2349,-2436,-2435,-2689,-2687,-2624]]},{"type":"Polygon","properties":{"admin":"Togo","name":"Togo","postal":"TG","pop_est":6019877,"iso_a2":"TG","iso_a3":"TGO"},"id":768,"arcs":[[-2290,1202,-2518,-2294]]},{"type":"MultiPolygon","properties":{"admin":"Thailand","name":"Thailand","postal":"TH","pop_est":65905410,"iso_a2":"TH","iso_a3":"THA"},"id":764,"arcs":[[[-2619,-2610,1160,-2682,1162,-2663]]]},{"type":"Polygon","properties":{"admin":"Tajikistan","name":"Tajikistan","postal":"TJ","pop_est":7349145,"iso_a2":"TJ","iso_a3":"TJK"},"id":762,"arcs":[[-2608,-2421,-2235,2738]]},{"type":"Polygon","properties":{"admin":"Turkmenistan","name":"Turkmenistan","postal":"TM","pop_est":4884887,"iso_a2":"TM","iso_a3":"TKM"},"id":795,"arcs":[[-2233,-2567,1276,-2600,2739,2740,1382,2741,2742]]},{"type":"MultiPolygon","properties":{"admin":"East Timor","name":"Timor-Leste","postal":"TL","pop_est":1131612,"iso_a2":"TL","iso_a3":"TLS"},"id":626,"arcs":[[[2743,2744,-2548]],[[2745,-2551,2746,212]]]},{"type":"Polygon","properties":{"admin":"Trinidad and Tobago","name":"Trinidad and Tobago","postal":"TT","pop_est":1310000,"iso_a2":"TT","iso_a3":"TTO"},"id":780,"arcs":[[48]]},{"type":"MultiPolygon","properties":{"admin":"Tunisia","name":"Tunisia","postal":"TN","pop_est":10486339,"iso_a2":"TN","iso_a3":"TUN"},"id":788,"arcs":[[[-2626,-2478,1221]]]},{"type":"MultiPolygon","properties":{"admin":"Turkey","name":"Turkey","postal":"TR","pop_est":76805524,"iso_a2":"TR","iso_a3":"TUR"},"id":792,"arcs":[[[-2516,-2259,-2273,-2572,-2577,-2738,1228]],[[1234,-2528,-2300]]]},{"type":"Polygon","properties":{"admin":"Taiwan","name":"Taiwan","postal":"TW","pop_est":22974347,"iso_a2":"TW","iso_a3":"TWN"},"id":158,"arcs":[[1436]]},{"type":"MultiPolygon","properties":{"admin":"United Republic of Tanzania","name":"Tanzania","postal":"TZ","pop_est":41048532,"iso_a2":"TZ","iso_a3":"TZA"},"id":834,"arcs":[[[154]],[[168]],[[2210]],[[2211]],[[2747,1297,2748,-2603,1189,-2676,2749,1286,-2681,2750,2751,1307,2752,-2277,-2725,2753]]]},{"type":"MultiPolygon","properties":{"admin":"Uganda","name":"Uganda","postal":"UG","pop_est":32369558,"iso_a2":"UG","iso_a3":"UGA"},"id":800,"arcs":[[[2754,1295,2755,-2754,-2728,-2443,2756,1302,2757,-2440,1317,2758,-2438,-2733,-2606]]]},{"type":"Polygon","properties":{"admin":"Ukraine","name":"Ukraine","postal":"UA","pop_est":45700395,"iso_a2":"UA","iso_a3":"UKR"},"id":804,"arcs":[[1231,-2710,-2637,-2712,-2541,-2735,-2703,-2310,-2721]]},{"type":"Polygon","properties":{"admin":"Uruguay","name":"Uruguay","postal":"UY","pop_est":3494382,"iso_a2":"UY","iso_a3":"URY"},"id":858,"arcs":[[2759,953,2760,-2325,669,-2254,-2328]]},{"type":"MultiPolygon","properties":{"admin":"United States of America","name":"United States","postal":"US","pop_est":313973000,"iso_a2":"US","iso_a3":"USA"},"id":840,"arcs":[[[73,74,75,76,77]],[[62,63]],[[64,65,66]],[[68,69,70,71,72]],[[323,324]],[[1456,1457,1458]],[[2212,2213,2214]],[[2220,2221,2222,2223,2219]],[[-2374,-2373,-2372,-2371,-2370,-2369,2761,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1030,1031,1032,1033,1034,1035,1036,1037,1038,-2367,1021,1022,1023,1024,1025,1026,-2366,2762,1009,1010,1011,1012,1013,1014,2763,-2363,-2362,-2360,-2359,-2358,-2357,-2356,-2355,-2354,2764,984,2765,-2351,-2350,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,-2655,-2654,-2653,-2652,-2651,-2650,-2649,-2648,-2647,-2646,-2645,-2644,-2643,-2642,-2641,-2640,-2639,-2638,-2658,-2657,-2656,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,717,-2377,-2376,-2375]],[[1506]],[[1503,1504,1505]],[[1507,1508]],[[2199]],[[1509,1510,1511,1512]],[[1521,1522,1523]],[[1536,1537,1538,1539,1540]],[[1544,1545]],[[1547,1548,1549,1550,1551,1552]],[[1555,1556,1557,1558,1559,1560,1561,1562]],[[1566,1567,1568]],[[1570,1571,1572]],[[1595,1596,1597,1598,1599]],[[1589,1590,1591,1592,1593,1594]],[[1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587]],[[1604,1605,1606]],[[1601,1602,1603]],[[1573,1574,1575]],[[1797,1798,1799]],[[1792,1793,1794,1795,1796]],[[1824,1825,1826,1827,1828,1829,1830]],[[867,868,869,870,871,872,-2388,-2387,-2386,-2385,-2384,-2383,-2382,-2381,-2380,-2379,-2378,745,746,2766,748,749,750,751,752,753,754,755,756,757,758,759,760,761,762,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866]]]},{"type":"MultiPolygon","properties":{"admin":"Uzbekistan","name":"Uzbekistan","postal":"UZ","pop_est":27606007,"iso_a2":"UZ","iso_a3":"UZB"},"id":860,"arcs":[[[2767,-2593,2768,1374,2769,-2596,-2609,-2739,-2234,-2743,2770,1380,2771,-2740,-2599,2772,1370]]]},{"type":"MultiPolygon","properties":{"admin":"Venezuela","name":"Venezuela","postal":"VE","pop_est":26814843,"iso_a2":"VE","iso_a3":"VEN"},"id":862,"arcs":[[[51]],[[-2533,-2334,-2453,664]]]},{"type":"MultiPolygon","properties":{"admin":"Vietnam","name":"Vietnam","postal":"VN","pop_est":86967524,"iso_a2":"VN","iso_a3":"VNM"},"id":704,"arcs":[[[50]],[[1158,-2612,-2618,-2402]]]},{"type":"MultiPolygon","properties":{"admin":"Vanuatu","name":"Vanuatu","postal":"VU","pop_est":218519,"iso_a2":"VU","iso_a3":"VUT"},"id":548,"arcs":[[[137]],[[144]],[[147]],[[145]],[[236]],[[237]]]},{"type":"MultiPolygon","properties":{"admin":"Samoa","name":"Samoa","postal":"WS","pop_est":219998,"iso_a2":"WS","iso_a3":"WSM"},"id":882,"arcs":[[[46]],[[47]]]},{"type":"MultiPolygon","properties":{"admin":"Yemen","name":"Yemen","postal":"YE","pop_est":23822783,"iso_a2":"YE","iso_a3":"YEM"},"id":887,"arcs":[[[91]],[[1178,-2730,-2693]]]},{"type":"Polygon","properties":{"admin":"South Africa","name":"South Africa","postal":"ZA","pop_est":49052489,"iso_a2":"ZA","iso_a3":"ZAF"},"id":710,"arcs":[[-2670,-2737,-2668,1191,-2686,-2342,2773],[-2627]]},{"type":"Polygon","properties":{"admin":"Zambia","name":"Zambia","postal":"ZM","pop_est":11862740,"iso_a2":"ZM","iso_a3":"ZMB"},"id":894,"arcs":[[2774,-2751,-2680,-2672,2775,2776,1279,2777,-2685,-2236,-2450,2778,1313]]},{"type":"Polygon","properties":{"admin":"Zimbabwe","name":"Zimbabwe","postal":"ZW","pop_est":12619600,"iso_a2":"ZW","iso_a3":"ZWE"},"id":716,"arcs":[[-2776,-2671,-2774,-2341,-2778,1280,2779,1282]]}]},"states":{"type":"GeometryCollection","geometries":[{"type":"Polygon","properties":{"iso_a2":"CA","name":"Alberta","postal":"AB","admin":"Canada"},"arcs":[[2780,2781,2782,2783]]},{"type":"MultiPolygon","properties":{"iso_a2":"CA","name":"British Columbia","postal":"BC","admin":"Canada"},"arcs":[[[2784,2785,2786,2787,2788,2789,2790,2791,2792,2793,2794,2795,2796,2797,2798]],[[2799,2800,2801,2802]],[[2803,2804,2805,2806]],[[2807,2808,2809]],[[2810,2811,2812,2813,2814,2815,2816]],[[2817,2818,2819,2820,2821,2822,2823,2824,2825,2826,2827,-2782,2828,2829,2830,2831,2832,2833,2834,2835,2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846,2847,2848,2849,736,2850,2851,2852,2853,2854,2855,2856,2857]]]},{"type":"Polygon","properties":{"iso_a2":"CA","name":"Manitoba","postal":"MB","admin":"Canada"},"arcs":[[2858,2859,2860,2861,2862,2863,2864,2865,2866,2867,2868,2869,2870,2871,2872,2873,2874]]},{"type":"Polygon","properties":{"iso_a2":"CA","name":"New Brunswick","postal":"NB","admin":"Canada"},"arcs":[[2875,2876,2877,2878,2879,2880,2881,2882,2883,2884,2885,2886,2887,2888,2889,2890,2891]]},{"type":"MultiPolygon","properties":{"iso_a2":"CA","name":"Newfoundland and Labrador","postal":"NL","admin":"Canada"},"arcs":[[[2892,2893,2894,2895,2896,2897,2898,2899,2900,2901,2902,2903,2904,2905,2906,2907,2908,2909,2910,2911,2912,2913,2914,2915,2916,2917,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2928,2929,2930,2931]],[[2932,2933,2934,2935,2936,2937,2938,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972]]]},{"type":"MultiPolygon","properties":{"iso_a2":"CA","name":"Nova Scotia","postal":"NS","admin":"Canada"},"arcs":[[[-2876,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991]],[[2992,2993,2994,2995,2996,2997,2998,2999]]]},{"type":"MultiPolygon","properties":{"iso_a2":"CA","name":"Northwest Territories","postal":"NT","admin":"Canada"},"arcs":[[[3000,3001,-2783,-2828,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026]],[[3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051]],[[3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068]],[[3069]],[[3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088]],[[3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099]],[[3100,3101,3102]],[[3103,3104,3105]],[[3106]]]},{"type":"MultiPolygon","properties":{"iso_a2":"CA","name":"Nunavut","postal":"NU","admin":"Canada"},"arcs":[[[3107,3108,3109,3110]],[[3111,3112,3113]],[[3114,3115]],[[3116,3117]],[[3118,3119,3120,3121]],[[3122,3123,3124,3125]],[[3126,3127,3128,3129,3130,3131]],[[3132,3133,3134]],[[3135]],[[3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152]],[[3153,3154,3155]],[[3156,3157]],[[3158,3159]],[[3160,3161,3162]],[[3163,3164,3165,3166]],[[3167,3168]],[[3169,3170,3171,3172]],[[3173,3174]],[[3175,3176]],[[3177,3178,3179,3180,3181,3182,3183]],[[-2863,-3001,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,936,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3241,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3252,3253,3254,3255,3256,3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267,3268,3269,3270,3271,3272,3273,3274,3275,3276,3277,3278,3279,3280]],[[-3028,3281,3282,3283,3284,3285,3286,3287,3288,3289,3290,3291,3292,3293,3294,3295,3296,3297,3298,3299,3300,3301,3302,3303,3304,3305]],[[3306,3307,3308,3309]],[[3310,3311,3312,3313,3314,3315,3316,3317]],[[3318,3319,3320,3321,3322,3323,3324,3325,3326,3327,3328,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3340,3341,3342,3343,3344,3345,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3387,3388,3389,3390,3391,3392,3393,3394,3395,3396,3397,3398,3399,3400,3401,3402,3403,3404,3405,3406,3407,3408,3409,3410,3411,3412,3413,3414,3415,3416,3417,3418,3419,3420,3421,3422,3423,3424,3425,3426,3427,3428,3429,3430,3431,3432,3433,3434,3435]],[[2091,3436,3437,3438,3439,3440,3441,3442,3443,3444,3445,3446,3447,3448,3449,3450,3451,3452,3453,3454,3455,3456]],[[3457]],[[3458,3459,3460,3461,3462,3463,3464,3465,3466,3467,3468]],[[3469,3470,3471,3472]],[[3473,3474,3475,3476,3477]],[[3478,3479,3480]],[[3481,3482,3483]],[[3484,3485,3486,3487,3488,3489,3490,3491,3492,3493,3494,3495,3496]],[[-3071,3497,3498,3499,3500,3501,3502,3503,3504,3505,3506,3507,3508]],[[3509,3510,3511,3512,3513,3514,3515,3516,3517,3518,3519,3520,3521,3522,3523,3524,3525,3526,3527,3528,3529,3530]],[[3531,3532,3533]],[[3534]],[[3535,3536,3537]],[[3538,3539,3540,3541]],[[3542,3543,3544,3545,3546,3547,3548,3549,3550,3551]],[[3552,3553]],[[3554,3555,3556,3557,3558,3559,3560,3561,3562,3563,3564,3565,3566,3567,3568,3569,3570,3571,3572,3573,3574,3575,3576]],[[3577,3578,3579,3580,3581,3582,3583,1729,3584,3585,3586,3587,3588,3589,3590,3591,3592,3593,3594,3595,3596,3597,3598,3599,3600,3601,3602,3603,3604,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618,3619,3620,3621,3622,3623,3624,3625,3626,3627,3628,3629,3630,3631,3632,3633,3634,3635,3636,3637,3638,3639,3640,3641,3642,3643,3644,3645]]]},{"type":"MultiPolygon","properties":{"iso_a2":"CA","name":"Ontario","postal":"ON","admin":"Canada"},"arcs":[[[3646,3647,3648,3649]],[[3650,3651,3652]],[[3653,3654,3655,3656,3657,3658,3659,3660,3661,3662,3663,3664,3665,3666,3667,3668,3669,3670,3671,3672,3673,3674,3675,3676,3677,3678,3679,3680,3681,3682,3683,3684,3685,3686,3687,3688,3689,3690,3691,3692,3693,3694,3695,3696,3697,3698,3699,3700,3701,3702,-2859,3703,3704,3705,3706,3707,3708,3709,3710,3711,3712,3713,3714,3715]]]},{"type":"Polygon","properties":{"iso_a2":"CA","name":"Prince Edward Island","postal":"PE","admin":"Canada"},"arcs":[[3716,3717,3718,3719,3720,3721,3722,3723,3724,3725]]},{"type":"MultiPolygon","properties":{"iso_a2":"CA","name":"Québec","postal":"QC","admin":"Canada"},"arcs":[[[3726,3727]],[[-2884,3728,3729,3730,3731,3732,3733,3734,3735,3736,3737,3738,3739,3740,3741,3742,3743,3744,3745,3746,3747]],[[1473,3748,3749,3750,3751,3752]],[[-2933,3753,3754,3755,3756,482,3757,3758,3759,3760,3761,3762,3763,3764,3765,3766,3767,3768,3769,3770,3771,3772,3773,-3654,3774,-3653,3775,3776,3777,3778,3779,3780,3781,3782,3783,3784,3785,3786,3787,3788,3789,3790,3791,3792,3793,3794,3795,3796,3797,3798,3799,3800,3801,3802,3803,3804,3805,430,3806,3807,3808,3809,3810,3811,3812,3813,3814]]]},{"type":"Polygon","properties":{"iso_a2":"CA","name":"Saskatchewan","postal":"SK","admin":"Canada"},"arcs":[[3815,3816,-2784,-3002,-2862]]},{"type":"Polygon","properties":{"iso_a2":"CA","name":"Yukon","postal":"YT","admin":"Canada"},"arcs":[[-3003,-2827,3817,3818,3819,3820,3821]]},{"type":"MultiPolygon","properties":{"iso_a2":"US","name":"Alaska","postal":"AK","admin":"United States of America"},"arcs":[[[3822]],[[3823,3824,3825]],[[3826,3827]],[[3828,3829,3830,3831]],[[3832,3833,3834]],[[3835,3836,3837,3838,3839]],[[3840,3841]],[[3842,3843,3844,3845,1551,3846]],[[3847,3848,3849,3850,3851,3852,3853,3854]],[[3855,3856,3857]],[[3858,3859,3860]],[[3861,3862,3863,3864,3865]],[[3866,3867,3868,3869,3870,3871]],[[3872,3873,3874,3875,3876,3877,3878,3879,3880,3881,3882]],[[3883,3884,3885]],[[3886,3887,3888]],[[3889,3890,3891]],[[3892,3893,3894]],[[3895,3896,3897,3898,3899]],[[3900,3901,3902,3903,3904,3905,3906]],[[-3820,-3819,-3818,-2826,-2825,-2824,-2823,-2822,-2821,-2820,-2819,-2818,3907,3908,3909,3910,3911,3912,3913,3914,755,3915,3916,3917,3918,3919,3920,3921,3922,3923,3924,3925,3926,3927,3928,3929,3930,3931,3932,3933,3934,3935,3936,3937,3938,3939,3940,3941,3942,3943,3944,3945,3946,3947,3948,3949,3950,3951,3952,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3973,3974,3975,3976,3977,3978,3979,3980,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3992,3993,3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4029,4030,4031]]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Alabama","postal":"AL","admin":"United States of America"},"arcs":[[4032,4033,4034,4035,4036,4037,4038,4039]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Arkansas","postal":"AR","admin":"United States of America"},"arcs":[[4040,4041,4042,4043,4044,4045]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Arizona","postal":"AZ","admin":"United States of America"},"arcs":[[4046,4047,4048,4049,4050,4051,4052,4053]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"California","postal":"CA","admin":"United States of America"},"arcs":[[4054,4055,-4052,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4067,4068,4069,4070,4071,4072,4073]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Colorado","postal":"CO","admin":"United States of America"},"arcs":[[4074,4075,4076,4077,4078,4079]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Connecticut","postal":"CT","admin":"United States of America"},"arcs":[[4080,4081,4082,4083,4084]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Delaware","postal":"DE","admin":"United States of America"},"arcs":[[4085,4086,4087,4088,4089]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Florida","postal":"FL","admin":"United States of America"},"arcs":[[4090,4091,4092,4093,4094,4095,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,622,4111,4112,4113,4114,4115,4116,-4040,4117]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Georgia","postal":"GA","admin":"United States of America"},"arcs":[[4118,4119,4120,-4118,-4039,4121,4122,4123]]},{"type":"MultiPolygon","properties":{"iso_a2":"US","name":"Hawaii","postal":"HI","admin":"United States of America"},"arcs":[[[4124,4125,4126,4127,4128]],[[4129,4130]],[[4131,4132,4133]],[[4134,4135,4136,4137,4138]],[[4139,4140]]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Iowa","postal":"IA","admin":"United States of America"},"arcs":[[4141,4142,4143,4144,4145,4146]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Idaho","postal":"ID","admin":"United States of America"},"arcs":[[4147,4148,4149,4150,4151,-2830,4152]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Illinois","postal":"IL","admin":"United States of America"},"arcs":[[4153,4154,4155,4156,4157,-4142,4158]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Indiana","postal":"IN","admin":"United States of America"},"arcs":[[4159,4160,-4156,4161,4162]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Kansas","postal":"KS","admin":"United States of America"},"arcs":[[4163,-4075,4164,4165]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Kentucky","postal":"KY","admin":"United States of America"},"arcs":[[4166,4167,4168,4169,-4157,-4161,4170]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Louisiana","postal":"LA","admin":"United States of America"},"arcs":[[4171,4172,4173,4174,4175,4176,4177,4178,4179,4180,4181,4182,4183,4184,-4043,4185]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Massachusetts","postal":"MA","admin":"United States of America"},"arcs":[[4186,-4085,4187,4188,4189,4190,4191,4192,4193,4194,4195,4196,4197]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Maryland","postal":"MD","admin":"United States of America"},"arcs":[[-4089,4198,4199,4200,4201,4202,4203,4204,4205,4206,4207,4208,4209,4210,4211]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Maine","postal":"ME","admin":"United States of America"},"arcs":[[-2881,-2880,4212,4213,4214,4215,4216,4217,4218,4219,4220,-3732,-3731,-3730,-3729,-2883,-2882]]},{"type":"MultiPolygon","properties":{"iso_a2":"US","name":"Michigan","postal":"MI","admin":"United States of America"},"arcs":[[[-3672,4221,4222,4223,-4163,4224,4225,4226,4227,4228,4229,4230,4231,4232,4233,4234,4235,4236,4237,4238,4239,4240,4241,4242]],[[4243,4244,4245,4246,4247,4248,4249,4250,4251,4252,4253,4254,4255,4256]],[[4257,4258,4259,4260,4261]]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Minnesota","postal":"MN","admin":"United States of America"},"arcs":[[4262,4263,4264,-4146,4265,4266,-2860,-3703,-3702,-3701,-3700,-3699,-3698]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Missouri","postal":"MO","admin":"United States of America"},"arcs":[[-4158,-4170,4267,-4046,4268,-4166,4269,-4143]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Mississippi","postal":"MS","admin":"United States of America"},"arcs":[[4270,4271,-4186,-4042,4272,-4037]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Montana","postal":"MT","admin":"United States of America"},"arcs":[[4273,4274,-4153,-2829,-2781,-3817,4275]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"North Carolina","postal":"NC","admin":"United States of America"},"arcs":[[4276,-4123,4277,4278,4279,4280,4281,4282,4283,4284,4285,4286,4287,4288,4289,4290]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"North Dakota","postal":"ND","admin":"United States of America"},"arcs":[[4291,-4276,-3816,-2861,-4267]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Nebraska","postal":"NE","admin":"United States of America"},"arcs":[[-4144,-4270,-4165,-4080,4292,4293]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"New Hampshire","postal":"NH","admin":"United States of America"},"arcs":[[4294,-4190,4295,-3734,-3733,-4221]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"New Jersey","postal":"NJ","admin":"United States of America"},"arcs":[[4296,4297,4298,4299,4300,4301]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"New Mexico","postal":"NM","admin":"United States of America"},"arcs":[[4302,4303,4304,4305,-4047,-4077,4306]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Nevada","postal":"NV","admin":"United States of America"},"arcs":[[-4053,-4056,4307,-4150,4308]]},{"type":"MultiPolygon","properties":{"iso_a2":"US","name":"New York","postal":"NY","admin":"United States of America"},"arcs":[[[4309,4310,4311]],[[4312,-4188,-4084,4313,-4298,4314,4315,4316,-3666,4317,4318,4319,4320,4321,4322,-3657,-3656,-3736]]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Ohio","postal":"OH","admin":"United States of America"},"arcs":[[4323,-4171,-4160,-4224,4324,4325,4326,4327,4328]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Oklahoma","postal":"OK","admin":"United States of America"},"arcs":[[-4045,4329,-4307,-4076,-4164,-4269]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Oregon","postal":"OR","admin":"United States of America"},"arcs":[[4330,-4151,-4308,-4055,4331,4332,4333,4334,4335,4336]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Pennsylvania","postal":"PA","admin":"United States of America"},"arcs":[[-4297,4337,-4090,-4212,4338,-4329,4339,-4315]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Rhode Island","postal":"RI","admin":"United States of America"},"arcs":[[4340,4341,-4081,-4187]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"South Carolina","postal":"SC","admin":"United States of America"},"arcs":[[4342,4343,4344,4345,4346,-4124,-4277]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"South Dakota","postal":"SD","admin":"United States of America"},"arcs":[[-4266,-4145,-4294,4347,-4274,-4292]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Tennessee","postal":"TN","admin":"United States of America"},"arcs":[[4348,-4278,-4122,-4038,-4273,-4041,-4268,-4169]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Texas","postal":"TX","admin":"United States of America"},"arcs":[[-4044,-4185,4349,4350,4351,4352,4353,4354,4355,4356,4357,4358,4359,4360,4361,4362,4363,4364,4365,4366,4367,4368,4369,4370,-4303,-4330]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Utah","postal":"UT","admin":"United States of America"},"arcs":[[4371,-4078,-4054,-4309,-4149]]},{"type":"MultiPolygon","properties":{"iso_a2":"US","name":"Virginia","postal":"VA","admin":"United States of America"},"arcs":[[[-4200,4372,4373,4374]],[[-4210,4375,4376,4377,4378,4379,4380,4381,4382,-4279,-4349,-4168,4383]]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Vermont","postal":"VT","admin":"United States of America"},"arcs":[[-4189,-4313,-3735,-4296]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Washington","postal":"WA","admin":"United States of America"},"arcs":[[-4331,4384,4385,4386,4387,4388,4389,4390,4391,4392,4393,4394,4395,4396,-2832,-2831,-4152]]},{"type":"MultiPolygon","properties":{"iso_a2":"US","name":"Wisconsin","postal":"WI","admin":"United States of America"},"arcs":[[[4397,4398,4399]],[[-4244,4400,4401,4402,4403,-4159,-4147,-4265,4404,4405,4406,4407]]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"West Virginia","postal":"WV","admin":"United States of America"},"arcs":[[-4211,-4384,-4167,-4324,-4339]]},{"type":"Polygon","properties":{"iso_a2":"US","name":"Wyoming","postal":"WY","admin":"United States of America"},"arcs":[[-4293,-4079,-4372,-4148,-4275,-4348]]}]},"cities":{"type":"GeometryCollection","geometries":[{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"San Bernardino","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[174161,706874]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Bridgeport","adm0name":"United States of America","adm1name":"Connecticut","iso_a2":"US"},"coordinates":[296661,748698]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Rochester","adm0name":"United States of America","adm1name":"New York","iso_a2":"US"},"coordinates":[284383,760490]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Manchester","adm0name":"United Kingdom","adm1name":"Manchester","iso_a2":"GB"},"coordinates":[493750,821690]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Gujranwala","adm0name":"Pakistan","adm1name":"Punjab","iso_a2":"PK"},"coordinates":[706063,695262]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Incheon","adm0name":"South Korea","adm1name":"Inch'on-gwangyoksi","iso_a2":"KR"},"coordinates":[851778,726754]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Benin City","adm0name":"Nigeria","adm1name":"Edo","iso_a2":"NG"},"coordinates":[515605,542293]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Xiamen","adm0name":"China","adm1name":"Fujian","iso_a2":"CN"},"coordinates":[827994,649581]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Nanchong","adm0name":"China","adm1name":"Sichuan","iso_a2":"CN"},"coordinates":[794799,687086]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Neijiang","adm0name":"China","adm1name":"Sichuan","iso_a2":"CN"},"coordinates":[791800,679976]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Nanyang","adm0name":"China","adm1name":"Henan","iso_a2":"CN"},"coordinates":[812577,700238]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jinxi","adm0name":"China","adm1name":"Liaoning","iso_a2":"CN"},"coordinates":[835632,746152]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Yantai","adm0name":"China","adm1name":"Shandong","iso_a2":"CN"},"coordinates":[837216,727075]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Zaozhuang","adm0name":"China","adm1name":"Shandong","iso_a2":"CN"},"coordinates":[826578,711374]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Suzhou","adm0name":"China","adm1name":"Jiangsu","iso_a2":"CN"},"coordinates":[835050,690167]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Xuzhou","adm0name":"China","adm1name":"Jiangsu","iso_a2":"CN"},"coordinates":[825494,707819]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Wuxi","adm0name":"China","adm1name":"Jiangsu","iso_a2":"CN"},"coordinates":[834161,691823]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jilin","adm0name":"China","adm1name":"Jilin","iso_a2":"CN"},"coordinates":[851522,764516]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Chandigarh","adm0name":"India","adm1name":"Chandigarh","iso_a2":"IN"},"coordinates":[713272,686728]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jammu","adm0name":"India","adm1name":"Jammu and Kashmir","iso_a2":"IN"},"coordinates":[707901,698528]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Sholapur","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[710827,609416]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Aurangabad","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[709217,622600]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Nasik","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[704938,623220]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Dispur","adm0name":"India","adm1name":"Assam","iso_a2":"IN"},"coordinates":[754907,659606]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jullundur","adm0name":"India","adm1name":"Punjab","iso_a2":"IN"},"coordinates":[709907,690371]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Allahabad","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[727327,655536]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Moradabad","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[718763,675601]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ghaziabad","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[715017,674526]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Agra","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[716703,665699]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Aligarh","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[716832,669974]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Meerut","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[715827,676540]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Dhanbad","adm0name":"India","adm1name":"Jharkhand","iso_a2":"IN"},"coordinates":[740049,645733]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Gwalior","adm0name":"India","adm1name":"Madhya Pradesh","iso_a2":"IN"},"coordinates":[717160,660127]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Vadodara","adm0name":"India","adm1name":"Dadra and Nagar Haveli","iso_a2":"IN"},"coordinates":[703271,636904]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Rajkot","adm0name":"India","adm1name":"Dadra and Nagar Haveli","iso_a2":"IN"},"coordinates":[696661,636904]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Durazno","adm0name":"Uruguay","adm1name":"Durazno","iso_a2":"UY"},"coordinates":[343027,306781]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"International Falls","adm0name":"United States of America","adm1name":"Minnesota","iso_a2":"US"},"coordinates":[240525,792652]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"St. Paul","adm0name":"United States of America","adm1name":"Minnesota","iso_a2":"US"},"coordinates":[241431,770986]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Billings","adm0name":"United States of America","adm1name":"Montana","iso_a2":"US"},"coordinates":[198500,775987]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Great Falls","adm0name":"United States of America","adm1name":"Montana","iso_a2":"US"},"coordinates":[190833,786130]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Missoula","adm0name":"United States of America","adm1name":"Montana","iso_a2":"US"},"coordinates":[183352,782409]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Minot","adm0name":"United States of America","adm1name":"North Dakota","iso_a2":"US"},"coordinates":[218622,790468]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Fargo","adm0name":"United States of America","adm1name":"North Dakota","iso_a2":"US"},"coordinates":[231140,782439]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Hilo","adm0name":"United States of America","adm1name":"Hawaii","iso_a2":"US"},"coordinates":[69194,621429]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Olympia","adm0name":"United States of America","adm1name":"Washington","iso_a2":"US"},"coordinates":[158613,783392]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Spokane","adm0name":"United States of America","adm1name":"Washington","iso_a2":"US"},"coordinates":[173833,787136]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Vancouver","adm0name":"United States of America","adm1name":"Washington","iso_a2":"US"},"coordinates":[159333,775052]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Flagstaff","adm0name":"United States of America","adm1name":"Arizona","iso_a2":"US"},"coordinates":[189860,713247]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Tucson","adm0name":"United States of America","adm1name":"Arizona","iso_a2":"US"},"coordinates":[191967,695526]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Santa Barbara","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[167444,708726]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Fresno","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[167297,722427]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Eureka","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[155146,746448]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Colorado Springs","adm0name":"United States of America","adm1name":"Colorado","iso_a2":"US"},"coordinates":[208911,734959]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Reno","adm0name":"United States of America","adm1name":"Nevada","iso_a2":"US"},"coordinates":[167166,738910]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Elko","adm0name":"United States of America","adm1name":"Nevada","iso_a2":"US"},"coordinates":[178438,746627]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Albuquerque","adm0name":"United States of America","adm1name":"New Mexico","iso_a2":"US"},"coordinates":[203773,712695]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Salem","adm0name":"United States of America","adm1name":"Oregon","iso_a2":"US"},"coordinates":[158267,770891]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Casper","adm0name":"United States of America","adm1name":"Wyoming","iso_a2":"US"},"coordinates":[204687,758678]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Topeka","adm0name":"United States of America","adm1name":"Kansas","iso_a2":"US"},"coordinates":[234250,736067]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Kansas City","adm0name":"United States of America","adm1name":"Missouri","iso_a2":"US"},"coordinates":[237205,736416]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Tulsa","adm0name":"United States of America","adm1name":"Oklahoma","iso_a2":"US"},"coordinates":[233527,718708]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Sioux Falls","adm0name":"United States of America","adm1name":"South Dakota","iso_a2":"US"},"coordinates":[231305,762727]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Shreveport","adm0name":"United States of America","adm1name":"Louisiana","iso_a2":"US"},"coordinates":[239528,697262]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Baton Rouge","adm0name":"United States of America","adm1name":"Louisiana","iso_a2":"US"},"coordinates":[246833,685164]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ft. Worth","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[229610,698684]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Corpus Christi","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[229439,669078]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Austin","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[228487,684044]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Amarillo","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[217139,713436]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"El Paso","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[204133,693008]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Laredo","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[223591,667676]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Merida","adm0name":"Venezuela","adm1name":"Mérida","iso_a2":"VE"},"coordinates":[302417,554483]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Burlington","adm0name":"United States of America","adm1name":"Vermont","iso_a2":"US"},"coordinates":[296631,768212]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Montgomery","adm0name":"United States of America","adm1name":"Alabama","iso_a2":"US"},"coordinates":[260335,696442]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Tallahassee","adm0name":"United States of America","adm1name":"Florida","iso_a2":"US"},"coordinates":[265888,685117]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Orlando","adm0name":"United States of America","adm1name":"Florida","iso_a2":"US"},"coordinates":[273939,673635]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jacksonville","adm0name":"United States of America","adm1name":"Florida","iso_a2":"US"},"coordinates":[273133,684418]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Savannah","adm0name":"United States of America","adm1name":"Georgia","iso_a2":"US"},"coordinates":[274695,694425]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Columbia","adm0name":"United States of America","adm1name":"South Carolina","iso_a2":"US"},"coordinates":[275277,706385]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Indianapolis","adm0name":"United States of America","adm1name":"Indiana","iso_a2":"US"},"coordinates":[260633,740225]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Wilmington","adm0name":"United States of America","adm1name":"North Carolina","iso_a2":"US"},"coordinates":[283486,707484]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Knoxville","adm0name":"United States of America","adm1name":"Tennessee","iso_a2":"US"},"coordinates":[266888,717820]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Richmond","adm0name":"United States of America","adm1name":"Virginia","iso_a2":"US"},"coordinates":[284855,727192]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Charleston","adm0name":"United States of America","adm1name":"West Virginia","iso_a2":"US"},"coordinates":[273243,731919]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Baltimore","adm0name":"United States of America","adm1name":"Maryland","iso_a2":"US"},"coordinates":[287161,737560]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Syracuse","adm0name":"United States of America","adm1name":"New York","iso_a2":"US"},"coordinates":[288472,759765]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Puerto Ayacucho","adm0name":"Venezuela","adm1name":"Amazonas","iso_a2":"VE"},"coordinates":[312156,538272]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Port-of-Spain","adm0name":"Trinidad and Tobago","adm1name":"Port of Spain","iso_a2":"TT"},"coordinates":[329119,567824]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Augusta","adm0name":"United States of America","adm1name":"Maine","iso_a2":"US"},"coordinates":[306167,767233]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Sault Ste. Marie","adm0name":"United States of America","adm1name":"Michigan","iso_a2":"US"},"coordinates":[265708,780176]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Atakpame","adm0name":"Togo","adm1name":"Plateaux","iso_a2":"TG"},"coordinates":[503110,549328]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Sousse","adm0name":"Tunisia","adm1name":"Sousse","iso_a2":"TN"},"coordinates":[529513,716990]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Taizz","adm0name":"Yemen","adm1name":"Ta`izz","iso_a2":"YE"},"coordinates":[622326,585327]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Sitka","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[124090,842768]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Lvov","adm0name":"Ukraine","adm1name":"L'viv","iso_a2":"UA"},"coordinates":[566750,799962]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Odessa","adm0name":"Ukraine","adm1name":"Odessa","iso_a2":"UA"},"coordinates":[585299,780156]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Zhytomyr","adm0name":"Ukraine","adm1name":"Zhytomyr","iso_a2":"UA"},"coordinates":[579616,802395]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Dnipropetrovsk","adm0name":"Ukraine","adm1name":"Dnipropetrovs'k","iso_a2":"UA"},"coordinates":[597216,791946]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Donetsk","adm0name":"Ukraine","adm1name":"Donets'k","iso_a2":"UA"},"coordinates":[605077,789102]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Kharkiv","adm0name":"Ukraine","adm1name":"Kharkiv","iso_a2":"UA"},"coordinates":[600689,800951]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Turkmenbasy","adm0name":"Turkmenistan","adm1name":"Balkan","iso_a2":"TM"},"coordinates":[647137,741831]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Bukhara","adm0name":"Uzbekistan","adm1name":"Bukhoro","iso_a2":"UZ"},"coordinates":[678972,740392]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Nukus","adm0name":"Uzbekistan","adm1name":"Karakalpakstan","iso_a2":"UZ"},"coordinates":[665596,756329]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Turkmenabat","adm0name":"Turkmenistan","adm1name":"Chardzhou","iso_a2":"TM"},"coordinates":[676610,736423]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Mary","adm0name":"Turkmenistan","adm1name":"Mary","iso_a2":"TM"},"coordinates":[671759,727476]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Andijon","adm0name":"Uzbekistan","adm1name":"Andijon","iso_a2":"UZ"},"coordinates":[700944,746376]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Haiphong","adm0name":"Vietnam","adm1name":"Qu?ng Ninh","iso_a2":"VN"},"coordinates":[796328,628135]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Da Nang","adm0name":"Vietnam","adm1name":"Ðà N?ng","iso_a2":"VN"},"coordinates":[800693,599864]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Kabwe","adm0name":"Zambia","adm1name":"Central","iso_a2":"ZM"},"coordinates":[579027,419168]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Mufulira","adm0name":"Zambia","adm1name":"Copperbelt","iso_a2":"ZM"},"coordinates":[578499,430365]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Kitwe","adm0name":"Zambia","adm1name":"Copperbelt","iso_a2":"ZM"},"coordinates":[578388,428825]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Livingstone","adm0name":"Zambia","adm1name":"Southern","iso_a2":"ZM"},"coordinates":[571833,398907]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Chitungwiza","adm0name":"Zimbabwe","adm1name":"Harare","iso_a2":"ZW"},"coordinates":[586388,398077]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Douala","adm0name":"Cameroon","adm1name":"Littoral","iso_a2":"CM"},"coordinates":[526967,528784]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Birmingham","adm0name":"United Kingdom","adm1name":"West Midlands","iso_a2":"GB"},"coordinates":[494661,815614]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Belfast","adm0name":"United Kingdom","adm1name":"Belfast","iso_a2":"GB"},"coordinates":[483444,828192]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Izmir","adm0name":"Turkey","adm1name":"Izmir","iso_a2":"TR"},"coordinates":[575416,732442]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Bursa","adm0name":"Turkey","adm1name":"Bursa","iso_a2":"TR"},"coordinates":[580744,742892]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Samsun","adm0name":"Turkey","adm1name":"Samsun","iso_a2":"TR"},"coordinates":[600954,749278]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Konya","adm0name":"Turkey","adm1name":"Konya","iso_a2":"TR"},"coordinates":[590203,729117]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Adana","adm0name":"Turkey","adm1name":"Adana","iso_a2":"TR"},"coordinates":[598105,723904]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Gulu","adm0name":"Uganda","adm1name":"Aswa","iso_a2":"UG"},"coordinates":[589666,521187]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Kigali","adm0name":"Rwanda","adm1name":"Kigali City","iso_a2":"RW"},"coordinates":[583496,493155]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Cottica","adm0name":"Suriname","adm1name":"Sipaliwini","iso_a2":"SR"},"coordinates":[349352,527527]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Cordoba","adm0name":"Spain","adm1name":"Andalucía","iso_a2":"ES"},"coordinates":[486749,729135]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Maradi","adm0name":"Niger","adm1name":"Maradi","iso_a2":"NE"},"coordinates":[519712,584648]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Tahoua","adm0name":"Niger","adm1name":"Tahoua","iso_a2":"NE"},"coordinates":[514610,592991]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Constanta","adm0name":"Romania","adm1name":"Constanta","iso_a2":"RO"},"coordinates":[579472,766594]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Luleå","adm0name":"Sweden","adm1name":"Norrbotten","iso_a2":"SE"},"coordinates":[561551,893341]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Sundsvall","adm0name":"Sweden","adm1name":"Västernorrland","iso_a2":"SE"},"coordinates":[548101,874403]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Iasi","adm0name":"Romania","adm1name":"Iasi","iso_a2":"RO"},"coordinates":[576596,784163]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Surat Thani","adm0name":"Thailand","adm1name":"Surat Thani","iso_a2":"TH"},"coordinates":[775944,558926]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Chiang Mai","adm0name":"Thailand","adm1name":"Chiang Mai","iso_a2":"TH"},"coordinates":[774944,616096]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Nakhon Ratchasima","adm0name":"Thailand","adm1name":"Nakhon Ratchasima","iso_a2":"TH"},"coordinates":[783611,593584]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Mbabane","adm0name":"Swaziland","adm1name":"Hhohho","iso_a2":"SZ"},"coordinates":[586481,348805]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Piura","adm0name":"Peru","adm1name":"Piura","iso_a2":"PE"},"coordinates":[276028,473851]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Arequipa","adm0name":"Peru","adm1name":"Arequipa","iso_a2":"PE"},"coordinates":[301300,407449]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Chimbote","adm0name":"Peru","adm1name":"Ancash","iso_a2":"PE"},"coordinates":[281750,450982]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Pucallpa","adm0name":"Peru","adm1name":"Ucayali","iso_a2":"PE"},"coordinates":[292958,455136]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Iquitos","adm0name":"Peru","adm1name":"Loreto","iso_a2":"PE"},"coordinates":[296527,482500]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Huancayo","adm0name":"Peru","adm1name":"Junín","iso_a2":"PE"},"coordinates":[291110,433149]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Ciudad del Este","adm0name":"Paraguay","adm1name":"Alto Paraná","iso_a2":"PY"},"coordinates":[348289,353544]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Ponta Delgada","adm0name":"Portugal","adm1name":"Azores","iso_a2":"PT"},"coordinates":[428704,728355]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Vigo","adm0name":"Spain","adm1name":"Galicia","iso_a2":"ES"},"coordinates":[475750,754847]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Bilbao","adm0name":"Spain","adm1name":"País Vasco","iso_a2":"ES"},"coordinates":[491861,760950]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Kaolack","adm0name":"Senegal","adm1name":"Kaolack","iso_a2":"SM"},"coordinates":[455277,588548]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Kaedi","adm0name":"Senegal","adm1name":"Matam","iso_a2":"SM"},"coordinates":[462500,600397]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Geneina","adm0name":"Sudan","adm1name":"West Darfur","iso_a2":"SD"},"coordinates":[562333,584401]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Medina","adm0name":"Saudi Arabia","adm1name":"Al Madinah","iso_a2":"SA"},"coordinates":[609939,649878]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Tabuk","adm0name":"Saudi Arabia","adm1name":"Tabuk","iso_a2":"SA"},"coordinates":[601541,672875]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Juba","adm0name":"South Sudan","adm1name":"Central Equatoria","iso_a2":"SS"},"coordinates":[587722,533332]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Malakal","adm0name":"South Sudan","adm1name":"Upper Nile","iso_a2":"SS"},"coordinates":[587933,561218]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Omdurman","adm0name":"Sudan","adm1name":"Khartoum","iso_a2":"SD"},"coordinates":[590222,597237]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"El Obeid","adm0name":"Sudan","adm1name":"North Kurdufan","iso_a2":"SD"},"coordinates":[583935,582821]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"The Hague","adm0name":"Netherlands","adm1name":"Zuid-Holland","iso_a2":"NL"},"coordinates":[511860,813263]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Kristiansand","adm0name":"Norway","adm1name":"Vest-Agder","iso_a2":"NO"},"coordinates":[522222,849323]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Ljubljana","adm0name":"Slovenia","adm1name":"Osrednjeslovenska","iso_a2":"SI"},"coordinates":[540318,777570]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Bratislava","adm0name":"Slovakia","adm1name":"Bratislavský","iso_a2":"SK"},"coordinates":[547547,789979]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Hammerfest","adm0name":"Norway","adm1name":"Finnmark","iso_a2":"NO"},"coordinates":[565800,923346]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Doha","adm0name":"Qatar","adm1name":"Ad Dawhah","iso_a2":"QA"},"coordinates":[643147,654526]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Quetta","adm0name":"Pakistan","adm1name":"Baluchistan","iso_a2":"PK"},"coordinates":[686175,683766]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Larkana","adm0name":"Pakistan","adm1name":"Sind","iso_a2":"PK"},"coordinates":[689463,668005]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Springbok","adm0name":"South Africa","adm1name":"Northern Cape","iso_a2":"ZA"},"coordinates":[549675,328958]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Upington","adm0name":"South Africa","adm1name":"Northern Cape","iso_a2":"ZA"},"coordinates":[558972,336107]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Worcester","adm0name":"South Africa","adm1name":"Western Cape","iso_a2":"ZA"},"coordinates":[553999,305418]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"George","adm0name":"South Africa","adm1name":"Western Cape","iso_a2":"ZA"},"coordinates":[562360,303582]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Tete","adm0name":"Mozambique","adm1name":"Tete","iso_a2":"MZ"},"coordinates":[593277,408919]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Pemba","adm0name":"Mozambique","adm1name":"Cabo Delgado","iso_a2":"MZ"},"coordinates":[612589,427799]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Nampula","adm0name":"Mozambique","adm1name":"Nampula","iso_a2":"MZ"},"coordinates":[609147,415044]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Welkom","adm0name":"South Africa","adm1name":"Orange Free State","iso_a2":"ZA"},"coordinates":[574249,339011]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Xai-Xai","adm0name":"Mozambique","adm1name":"Gaza","iso_a2":"MZ"},"coordinates":[593443,356369]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Goroka","adm0name":"Papua New Guinea","adm1name":"Eastern Highlands","iso_a2":"PG"},"coordinates":[903848,468677]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Mt. Hagen","adm0name":"Papua New Guinea","adm1name":"Western Highlands","iso_a2":"PG"},"coordinates":[900601,469980]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Rabaul","adm0name":"Papua New Guinea","adm1name":"East New Britain","iso_a2":"PG"},"coordinates":[922620,479802]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Lae","adm0name":"Papua New Guinea","adm1name":"Morobe","iso_a2":"PG"},"coordinates":[908305,464828]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"David","adm0name":"Panama","adm1name":"Chiriquí","iso_a2":"PA"},"coordinates":[271018,554680]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Oujda","adm0name":"Morocco","adm1name":"Oriental","iso_a2":"MA"},"coordinates":[494694,710237]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Safi","adm0name":"Morocco","adm1name":"Doukkala - Abda","iso_a2":"MA"},"coordinates":[474333,696195]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Podgorica","adm0name":"Montenegro","adm1name":"Podgorica","iso_a2":"ME"},"coordinates":[553517,756305]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Quelimane","adm0name":"Mozambique","adm1name":"Zambezia","iso_a2":"MZ"},"coordinates":[602472,398787]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"East London","adm0name":"South Africa","adm1name":"Eastern Cape","iso_a2":"ZA"},"coordinates":[577416,309388]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Middelburg","adm0name":"South Africa","adm1name":"Eastern Cape","iso_a2":"ZA"},"coordinates":[569472,318097]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Naltchik","adm0name":"Russia","adm1name":"Kabardin-Balkar","iso_a2":"RU"},"coordinates":[621161,762420]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Stavropol","adm0name":"Russia","adm1name":"Stavropol'","iso_a2":"RU"},"coordinates":[616610,771613]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ugolnye Kopi","adm0name":"Russia","adm1name":"Chukchi Autonomous Okrug","iso_a2":"RU"},"coordinates":[993610,888227]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kaliningrad","adm0name":"Russia","adm1name":"Kaliningrad","iso_a2":"RU"},"coordinates":[556937,828785]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Pskov","adm0name":"Russia","adm1name":"Pskov","iso_a2":"RU"},"coordinates":[578694,847328]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Bryansk","adm0name":"Russia","adm1name":"Bryansk","iso_a2":"RU"},"coordinates":[595638,820254]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Smolensk","adm0name":"Russia","adm1name":"Smolensk","iso_a2":"RU"},"coordinates":[589020,829274]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Petrozavodsk","adm0name":"Russia","adm1name":"Karelia","iso_a2":"RU"},"coordinates":[595222,871144]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Tver","adm0name":"Russia","adm1name":"Tver'","iso_a2":"RU"},"coordinates":[599693,841581]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Vologda","adm0name":"Russia","adm1name":"Vologda","iso_a2":"RU"},"coordinates":[610888,855504]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Yaroslavl","adm0name":"Russia","adm1name":"Yaroslavl'","iso_a2":"RU"},"coordinates":[610749,846084]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Rostov","adm0name":"Russia","adm1name":"Rostov","iso_a2":"RU"},"coordinates":[610307,784568]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Sochi","adm0name":"Russia","adm1name":"Krasnodar","iso_a2":"RU"},"coordinates":[610360,762964]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Krasnodar","adm0name":"Russia","adm1name":"Krasnodar","iso_a2":"RU"},"coordinates":[608333,771435]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Penza","adm0name":"Russia","adm1name":"Penza","iso_a2":"RU"},"coordinates":[624999,819779]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ryazan","adm0name":"Russia","adm1name":"Ryazan'","iso_a2":"RU"},"coordinates":[610333,828311]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Voronezh","adm0name":"Russia","adm1name":"Voronezh","iso_a2":"RU"},"coordinates":[609077,811201]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Magnitogorsk","adm0name":"Russia","adm1name":"Chelyabinsk","iso_a2":"RU"},"coordinates":[663833,821217]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Chelyabinsk","adm0name":"Russia","adm1name":"Chelyabinsk","iso_a2":"RU"},"coordinates":[670657,831492]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Vorkuta","adm0name":"Russia","adm1name":"Komi","iso_a2":"RU"},"coordinates":[677805,904617]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kirov","adm0name":"Russia","adm1name":"Kirov","iso_a2":"RU"},"coordinates":[637971,851831]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Nizhny Tagil","adm0name":"Russia","adm1name":"Sverdlovsk","iso_a2":"RU"},"coordinates":[666597,847862]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Astrakhan","adm0name":"Russia","adm1name":"Astrakhan'","iso_a2":"RU"},"coordinates":[633486,779307]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Orenburg","adm0name":"Russia","adm1name":"Orenburg","iso_a2":"RU"},"coordinates":[653083,811485]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Saratov","adm0name":"Russia","adm1name":"Saratov","iso_a2":"RU"},"coordinates":[627855,810312]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ulyanovsk","adm0name":"Russia","adm1name":"Ul'yanovsk","iso_a2":"RU"},"coordinates":[634471,826592]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Omsk","adm0name":"Russia","adm1name":"Omsk","iso_a2":"RU"},"coordinates":[703882,830514]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Tyumen","adm0name":"Russia","adm1name":"Tyumen'","iso_a2":"RU"},"coordinates":[682027,843241]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Novokuznetsk","adm0name":"Russia","adm1name":"Kemerovo","iso_a2":"RU"},"coordinates":[741986,823156]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kemerovo","adm0name":"Russia","adm1name":"Kemerovo","iso_a2":"RU"},"coordinates":[739139,832576]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Groznyy","adm0name":"Russia","adm1name":"Chechnya","iso_a2":"RU"},"coordinates":[626940,761357]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Kandy","adm0name":"Sri Lanka","adm1name":"Kandy","iso_a2":"LK"},"coordinates":[724082,547847]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Sri Jawewardenepura Kotte","adm0name":"Sri Lanka","adm1name":"Colombo","iso_a2":"LK"},"coordinates":[722082,545596]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Daejeon","adm0name":"South Korea","adm1name":"Daejeon","iso_a2":"KR"},"coordinates":[853952,719997]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Gwangju","adm0name":"South Korea","adm1name":"Kwangju-gwangyoksi","iso_a2":"KR"},"coordinates":[852523,713097]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Busan","adm0name":"South Korea","adm1name":"Busan","iso_a2":"KR"},"coordinates":[858355,712648]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Zamboanga","adm0name":"Philippines","adm1name":"Zamboanga del Sur","iso_a2":"PH"},"coordinates":[839105,545725]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Laoag","adm0name":"Philippines","adm1name":"Ilocos Norte","iso_a2":"PH"},"coordinates":[834981,612535]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Baguio City","adm0name":"Philippines","adm1name":"Benguet","iso_a2":"PH"},"coordinates":[834915,602056]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"General Santos","adm0name":"Philippines","adm1name":"South Cotabato","iso_a2":"PH"},"coordinates":[847707,540920]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ust-Ulimsk","adm0name":"Russia","adm1name":"Irkutsk","iso_a2":"RU"},"coordinates":[785092,848276]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Angarsk","adm0name":"Russia","adm1name":"Irkutsk","iso_a2":"RU"},"coordinates":[788666,816107]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Abakan","adm0name":"Russia","adm1name":"Krasnoyarsk","iso_a2":"RU"},"coordinates":[754013,822882]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Norilsk","adm0name":"Russia","adm1name":"Taymyr","iso_a2":"RU"},"coordinates":[745068,915519]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Khatanga","adm0name":"Russia","adm1name":"Taymyr","iso_a2":"RU"},"coordinates":[784624,931522]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kyzyl","adm0name":"Russia","adm1name":"Tuva","iso_a2":"RU"},"coordinates":[762175,811051]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ulan Ude","adm0name":"Russia","adm1name":"Buryat","iso_a2":"RU"},"coordinates":[798958,811752]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Blagoveshchensk","adm0name":"Russia","adm1name":"Amur","iso_a2":"RU"},"coordinates":[854259,802519]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Bukachacha","adm0name":"Russia","adm1name":"Chita","iso_a2":"RU"},"coordinates":[824768,818614]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Dalnegorsk","adm0name":"Russia","adm1name":"Primor'ye","iso_a2":"RU"},"coordinates":[876436,768575]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ambarchik","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[950925,917361]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Batagay","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[873985,905542]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Chokurdakh","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[910817,923092]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ust Nera","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[897777,887239]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Lensk","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[819296,864481]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Aldan","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[848303,851908]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Mirnyy","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[816558,875232]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Zhigansk","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[842697,900291]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Okhotsk","adm0name":"Russia","adm1name":"Khabarovsk","iso_a2":"RU"},"coordinates":[897824,856529]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Khabarovsk","adm0name":"Russia","adm1name":"Khabarovsk","iso_a2":"RU"},"coordinates":[875333,791786]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Okha","adm0name":"Russia","adm1name":"Sakhalin","iso_a2":"RU"},"coordinates":[897076,822113]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Yuzhno Sakhalinsk","adm0name":"Russia","adm1name":"Sakhalin","iso_a2":"RU"},"coordinates":[896500,782959]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Mexicali","adm0name":"Mexico","adm1name":"Baja California","iso_a2":"MX"},"coordinates":[179216,698162]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"La Paz","adm0name":"Mexico","adm1name":"Baja California Sur","iso_a2":"MX"},"coordinates":[193556,647733]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Torreon","adm0name":"Mexico","adm1name":"Coahuila","iso_a2":"MX"},"coordinates":[212716,656217]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Culiacan","adm0name":"Mexico","adm1name":"Sinaloa","iso_a2":"MX"},"coordinates":[201716,651833]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Nogales","adm0name":"Mexico","adm1name":"Sonora","iso_a2":"MX"},"coordinates":[191820,690182]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Hermosillo","adm0name":"Mexico","adm1name":"Sonora","iso_a2":"MX"},"coordinates":[191794,677112]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Guaymas","adm0name":"Mexico","adm1name":"Sonora","iso_a2":"MX"},"coordinates":[191972,670187]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"San Luis Potosi","adm0name":"Mexico","adm1name":"San Luis Potosí","iso_a2":"MX"},"coordinates":[219439,636074]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Matamoros","adm0name":"Mexico","adm1name":"Tamaulipas","iso_a2":"MX"},"coordinates":[229166,658042]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Nuevo Laredo","adm0name":"Mexico","adm1name":"Tamaulipas","iso_a2":"MX"},"coordinates":[223472,667639]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Colima","adm0name":"Mexico","adm1name":"Colima","iso_a2":"MX"},"coordinates":[211889,618644]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Campeche","adm0name":"Mexico","adm1name":"Campeche","iso_a2":"MX"},"coordinates":[248611,622199]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Oaxaca","adm0name":"Mexico","adm1name":"Oaxaca","iso_a2":"MX"},"coordinates":[231472,605923]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Leon","adm0name":"Mexico","adm1name":"Guanajuato","iso_a2":"MX"},"coordinates":[217495,630031]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Maiduguri","adm0name":"Nigeria","adm1name":"Borno","iso_a2":"NG"},"coordinates":[536550,574933]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Port Harcourt","adm0name":"Nigeria","adm1name":"Rivers","iso_a2":"NG"},"coordinates":[519466,533225]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Makurdi","adm0name":"Nigeria","adm1name":"Benue","iso_a2":"NG"},"coordinates":[523694,550513]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ibadan","adm0name":"Nigeria","adm1name":"Oyo","iso_a2":"NG"},"coordinates":[510910,548451]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Ogbomosho","adm0name":"Nigeria","adm1name":"Oyo","iso_a2":"NG"},"coordinates":[511772,552894]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Warri","adm0name":"Nigeria","adm1name":"Delta","iso_a2":"NG"},"coordinates":[515999,537420]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kaduna","adm0name":"Nigeria","adm1name":"Kaduna","iso_a2":"NG"},"coordinates":[520661,567054]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Gdansk","adm0name":"Poland","adm1name":"Pomeranian","iso_a2":"PL"},"coordinates":[551777,826770]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Kraków","adm0name":"Poland","adm1name":"Lesser Poland","iso_a2":"PL"},"coordinates":[555438,801306]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Dalandzadgad","adm0name":"Mongolia","adm1name":"Ömnögovi","iso_a2":"MN"},"coordinates":[790111,762926]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Wonsan","adm0name":"North Korea","adm1name":"Kangwon-do","iso_a2":"KP"},"coordinates":[853974,736721]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Sinuiju","adm0name":"North Korea","adm1name":"P'yongan-bukto","iso_a2":"KP"},"coordinates":[845613,742204]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Dund-Us","adm0name":"Mongolia","adm1name":"Hovd","iso_a2":"MN"},"coordinates":[754536,789189]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Choybalsan","adm0name":"Mongolia","adm1name":"Dornod","iso_a2":"MN"},"coordinates":[818071,789486]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Lüderitz","adm0name":"Namibia","adm1name":"Karas","iso_a2":"NA"},"coordinates":[542109,346842]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Walvis Bay","adm0name":"Namibia","adm1name":"Erongo","iso_a2":"NA"},"coordinates":[540292,368707]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Mwanza","adm0name":"Tanzania","adm1name":"Mwanza","iso_a2":"TZ"},"coordinates":[591472,489788]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Morogoro","adm0name":"Tanzania","adm1name":"Morogoro","iso_a2":"TZ"},"coordinates":[604610,464312]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Dodoma","adm0name":"Tanzania","adm1name":"Dodoma","iso_a2":"TZ"},"coordinates":[599305,468084]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Arusha","adm0name":"Tanzania","adm1name":"Arusha","iso_a2":"TZ"},"coordinates":[601861,484811]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Napier","adm0name":"New Zealand","adm1name":"Gisborne","iso_a2":"NZ"},"coordinates":[991429,270760]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Manukau","adm0name":"New Zealand","adm1name":"Auckland","iso_a2":"NZ"},"coordinates":[985790,285513]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Hamilton","adm0name":"New Zealand","adm1name":"Auckland","iso_a2":"NZ"},"coordinates":[986944,280950]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Blenheim","adm0name":"New Zealand","adm1name":"Marlborough","iso_a2":"NZ"},"coordinates":[983220,258728]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Dunedin","adm0name":"New Zealand","adm1name":"Otago","iso_a2":"NZ"},"coordinates":[973555,232904]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Bern","adm0name":"Switzerland","adm1name":"Bern","iso_a2":"CH"},"coordinates":[520741,782673]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Malmö","adm0name":"Sweden","adm1name":"Skåne","iso_a2":"SE"},"coordinates":[536203,834018]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Laayoune","adm0name":"Morocco","adm1name":"Laâyoune - Boujdour - Sakia El Hamra","iso_a2":"MA"},"coordinates":[463333,665566]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ternate","adm0name":"Indonesia","adm1name":"Maluku Utara","iso_a2":"ID"},"coordinates":[853785,509415]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ambon","adm0name":"Indonesia","adm1name":"Maluku","iso_a2":"ID"},"coordinates":[856110,482698]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Raba","adm0name":"Indonesia","adm1name":"Nusa Tenggara Barat","iso_a2":"ID"},"coordinates":[829907,454656]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jayapura","adm0name":"Indonesia","adm1name":"Papua","iso_a2":"ID"},"coordinates":[890832,489710]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Florence","adm0name":"Italy","adm1name":"Toscana","iso_a2":"IT"},"coordinates":[531250,764090]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Catania","adm0name":"Italy","adm1name":"Sicily","iso_a2":"IT"},"coordinates":[541888,726884]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Pristina","adm0name":"Kosovo","adm1name":"Pristina","iso_a2":"-99"},"coordinates":[558793,757494]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Meru","adm0name":"Kenya","adm1name":"Eastern","iso_a2":"KE"},"coordinates":[604555,505072]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Eldoret","adm0name":"Kenya","adm1name":"Rift Valley","iso_a2":"KE"},"coordinates":[597971,507798]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Banda Aceh","adm0name":"Indonesia","adm1name":"Aceh","iso_a2":"ID"},"coordinates":[764777,537597]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"George Town","adm0name":"Malaysia","adm1name":"Pulau Pinang","iso_a2":"MY"},"coordinates":[778692,536790]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Zhangye","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[779027,735356]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Wuwei","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[785113,729420]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Dunhuang","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[762949,742540]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Tianshui","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[794216,709715]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Dulan","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[772962,718985]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Golmud","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[763564,720465]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Yulin","adm0name":"China","adm1name":"Guangxi","iso_a2":"CN"},"coordinates":[805966,638799]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Bose","adm0name":"China","adm1name":"Guangxi","iso_a2":"CN"},"coordinates":[796147,646309]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Wuzhou","adm0name":"China","adm1name":"Guangxi","iso_a2":"CN"},"coordinates":[809222,643823]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Lupanshui","adm0name":"China","adm1name":"Guizhou","iso_a2":"CN"},"coordinates":[791198,662286]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Quanzhou","adm0name":"China","adm1name":"Fujian","iso_a2":"CN"},"coordinates":[829382,652248]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Hefei","adm0name":"China","adm1name":"Anhui","iso_a2":"CN"},"coordinates":[825772,693423]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Suzhou","adm0name":"China","adm1name":"Anhui","iso_a2":"CN"},"coordinates":[824935,704004]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Zhanjiang","adm0name":"China","adm1name":"Guangdong","iso_a2":"CN"},"coordinates":[806605,630327]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Shaoguan","adm0name":"China","adm1name":"Guangdong","iso_a2":"CN"},"coordinates":[815499,651643]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Balikpapan","adm0name":"Indonesia","adm1name":"Kalimantan Timur","iso_a2":"ID"},"coordinates":[824527,497311]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Kuching","adm0name":"Malaysia","adm1name":"Sarawak","iso_a2":"MY"},"coordinates":[806472,513781]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Antsiranana","adm0name":"Madagascar","adm1name":"Antsiranana","iso_a2":"MG"},"coordinates":[636976,431985]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Fianarantsoa","adm0name":"Madagascar","adm1name":"Fianarantsoa","iso_a2":"MG"},"coordinates":[630786,377736]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Mahajanga","adm0name":"Madagascar","adm1name":"Mahajanga","iso_a2":"MG"},"coordinates":[628736,411881]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Toliara","adm0name":"Madagascar","adm1name":"Toliary","iso_a2":"MG"},"coordinates":[621360,366340]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Surakarta","adm0name":"Indonesia","adm1name":"Jawa Tengah","iso_a2":"ID"},"coordinates":[807846,459899]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Bandar Lampung","adm0name":"Indonesia","adm1name":"Lampung","iso_a2":"ID"},"coordinates":[792411,472559]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Tanjungpandan","adm0name":"Indonesia","adm1name":"Bangka-Belitung","iso_a2":"ID"},"coordinates":[799027,488425]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Malang","adm0name":"Indonesia","adm1name":"Jawa Timur","iso_a2":"ID"},"coordinates":[812800,457451]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Kupang","adm0name":"Indonesia","adm1name":"Nusa Tenggara Timur","iso_a2":"ID"},"coordinates":[843285,444414]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Parepare","adm0name":"Indonesia","adm1name":"Sulawesi Selatan","iso_a2":"ID"},"coordinates":[832314,480920]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Cuenca","adm0name":"Ecuador","adm1name":"Azuay","iso_a2":"EC"},"coordinates":[280555,487536]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Santa Cruz","adm0name":"Ecuador","adm1name":"Galápagos","iso_a2":"EC"},"coordinates":[249027,501557]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Puerto Limon","adm0name":"Costa Rica","adm1name":"Limón","iso_a2":"CR"},"coordinates":[269351,563962]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Santiago de Cuba","adm0name":"Cuba","adm1name":"Santiago de Cuba","iso_a2":"CU"},"coordinates":[289385,623354]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Santiago","adm0name":"Dominican Republic","adm1name":"Santiago","iso_a2":"DO"},"coordinates":[303694,620244]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Manizales","adm0name":"Colombia","adm1name":"Caldas","iso_a2":"CO"},"coordinates":[290222,534695]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Pasto","adm0name":"Colombia","adm1name":"Nariño","iso_a2":"CO"},"coordinates":[285330,511907]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Barranquilla","adm0name":"Colombia","adm1name":"Atlántico","iso_a2":"CO"},"coordinates":[292217,569660]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Roseau","adm0name":"Dominica","adm1name":"Saint George","iso_a2":"DM"},"coordinates":[329480,595367]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Mbandaka","adm0name":"Congo (Kinshasa)","adm1name":"Équateur","iso_a2":"CD"},"coordinates":[550722,504955]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Moundou","adm0name":"Chad","adm1name":"Logone Oriental","iso_a2":"TD"},"coordinates":[544694,555371]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Suez","adm0name":"Egypt","adm1name":"As Suways","iso_a2":"EG"},"coordinates":[590416,682480]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Bur Said","adm0name":"Egypt","adm1name":"Bur Sa`id","iso_a2":"EG"},"coordinates":[589694,689915]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"El Faiyum","adm0name":"Egypt","adm1name":"Al Fayyum","iso_a2":"EG"},"coordinates":[585666,678363]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Aswan","adm0name":"Egypt","adm1name":"Aswan","iso_a2":"EG"},"coordinates":[591385,647422]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Asyut","adm0name":"Egypt","adm1name":"Asyut","iso_a2":"EG"},"coordinates":[586610,665803]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Kisangani","adm0name":"Congo (Kinshasa)","adm1name":"Orientale","iso_a2":"CD"},"coordinates":[570055,507798]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Assab","adm0name":"Eritrea","adm1name":"Debubawi Keyih Bahri","iso_a2":"ER"},"coordinates":[618694,581794]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Djibouti","adm0name":"Djibouti","adm1name":"Djibouti","iso_a2":"DJ"},"coordinates":[619855,573411]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Dresden","adm0name":"Germany","adm1name":"Sachsen","iso_a2":"DE"},"coordinates":[538194,807160]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Xigaze","adm0name":"China","adm1name":"Xizang","iso_a2":"CN"},"coordinates":[746897,678007]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Shache","adm0name":"China","adm1name":"Xinjiang Uygur","iso_a2":"CN"},"coordinates":[714583,732371]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Yining","adm0name":"China","adm1name":"Xinjiang Uygur","iso_a2":"CN"},"coordinates":[725971,764800]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Altay","adm0name":"China","adm1name":"Xinjiang Uygur","iso_a2":"CN"},"coordinates":[744768,788301]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Putrajaya","adm0name":"Malaysia","adm1name":"Selangor","iso_a2":"MY"},"coordinates":[782504,521981]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Shizuishan","adm0name":"China","adm1name":"Ningxia Hui","iso_a2":"CN"},"coordinates":[796580,737152]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Yulin","adm0name":"China","adm1name":"Shaanxi","iso_a2":"CN"},"coordinates":[804814,731525]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ankang","adm0name":"China","adm1name":"Shaanxi","iso_a2":"CN"},"coordinates":[802832,698328]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Houma","adm0name":"China","adm1name":"Shanxi","iso_a2":"CN"},"coordinates":[808916,715746]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Yueyang","adm0name":"China","adm1name":"Hunan","iso_a2":"CN"},"coordinates":[814160,678790]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Hengyang","adm0name":"China","adm1name":"Hunan","iso_a2":"CN"},"coordinates":[812744,663978]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Mianyang","adm0name":"China","adm1name":"Sichuan","iso_a2":"CN"},"coordinates":[791022,691171]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Xichang","adm0name":"China","adm1name":"Sichuan","iso_a2":"CN"},"coordinates":[784166,669891]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Baoshan","adm0name":"China","adm1name":"Yunnan","iso_a2":"CN"},"coordinates":[775415,653540]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Gejiu","adm0name":"China","adm1name":"Yunnan","iso_a2":"CN"},"coordinates":[786527,643230]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Shijianzhuang","adm0name":"China","adm1name":"Hebei","iso_a2":"CN"},"coordinates":[817993,730154]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Handan","adm0name":"China","adm1name":"Hebei","iso_a2":"CN"},"coordinates":[817993,721445]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Anshan","adm0name":"China","adm1name":"Liaoning","iso_a2":"CN"},"coordinates":[841494,748312]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Dalian","adm0name":"China","adm1name":"Liaoning","iso_a2":"CN"},"coordinates":[837855,735325]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Qingdao","adm0name":"China","adm1name":"Shandong","iso_a2":"CN"},"coordinates":[834244,718542]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Linyi","adm0name":"China","adm1name":"Shandong","iso_a2":"CN"},"coordinates":[828688,712559]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Huaiyin","adm0name":"China","adm1name":"Jiangsu","iso_a2":"CN"},"coordinates":[830633,703671]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Wenzhou","adm0name":"China","adm1name":"Zhejiang","iso_a2":"CN"},"coordinates":[835133,670731]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ningbo","adm0name":"China","adm1name":"Zhejiang","iso_a2":"CN"},"coordinates":[837632,681751]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Fukuoka","adm0name":"Japan","adm1name":"Fukuoka","iso_a2":"JP"},"coordinates":[862243,703760]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Miyazaki","adm0name":"Japan","adm1name":"Miyazaki","iso_a2":"JP"},"coordinates":[865050,693815]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Naha","adm0name":"Japan","adm1name":"Okinawa","iso_a2":"JP"},"coordinates":[854647,659980]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kochi","adm0name":"Japan","adm1name":"Kochi","iso_a2":"JP"},"coordinates":[870937,703556]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Gorontalo","adm0name":"Indonesia","adm1name":"Gorontalo","iso_a2":"ID"},"coordinates":[841861,507976]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Tongliao","adm0name":"China","adm1name":"Nei Mongol","iso_a2":"CN"},"coordinates":[839633,763153]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Hohhot","adm0name":"China","adm1name":"Nei Mongol","iso_a2":"CN"},"coordinates":[810161,746565]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Chifeng","adm0name":"China","adm1name":"Nei Mongol","iso_a2":"CN"},"coordinates":[830410,755155]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ulanhot","adm0name":"China","adm1name":"Nei Mongol","iso_a2":"CN"},"coordinates":[839110,777716]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Hailar","adm0name":"China","adm1name":"Nei Mongol","iso_a2":"CN"},"coordinates":[832499,796200]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jiamusi","adm0name":"China","adm1name":"Heilongjiang","iso_a2":"CN"},"coordinates":[862077,782171]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Beian","adm0name":"China","adm1name":"Heilongjiang","iso_a2":"CN"},"coordinates":[851338,790507]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Daqing","adm0name":"China","adm1name":"Heilongjiang","iso_a2":"CN"},"coordinates":[847216,780690]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jixi","adm0name":"China","adm1name":"Heilongjiang","iso_a2":"CN"},"coordinates":[863800,773106]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Nagoya","adm0name":"Japan","adm1name":"Aichi","iso_a2":"JP"},"coordinates":[880313,713003]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Nagano","adm0name":"Japan","adm1name":"Nagano","iso_a2":"JP"},"coordinates":[883805,721849]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kushiro","adm0name":"Japan","adm1name":"Hokkaido","iso_a2":"JP"},"coordinates":[901040,759320]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Hakodate","adm0name":"Japan","adm1name":"Hokkaido","iso_a2":"JP"},"coordinates":[890943,752329]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kyoto","adm0name":"Japan","adm1name":"Kyoto","iso_a2":"JP"},"coordinates":[877077,712262]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Sendai","adm0name":"Japan","adm1name":"Miyagi","iso_a2":"JP"},"coordinates":[891720,731559]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Sakata","adm0name":"Japan","adm1name":"Yamagata","iso_a2":"JP"},"coordinates":[888471,735297]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Bandundu","adm0name":"Congo (Kinshasa)","adm1name":"Bandundu","iso_a2":"CD"},"coordinates":[548277,485107]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Kananga","adm0name":"Congo (Kinshasa)","adm1name":"Kasaï-Occidental","iso_a2":"CD"},"coordinates":[562216,469833]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Kasongo","adm0name":"Congo (Kinshasa)","adm1name":"Maniema","iso_a2":"CD"},"coordinates":[574055,478353]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Mbuji-Mayi","adm0name":"Congo (Kinshasa)","adm1name":"Kasaï-Oriental","iso_a2":"CD"},"coordinates":[565549,468293]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Kalemie","adm0name":"Congo (Kinshasa)","adm1name":"Katanga","iso_a2":"CD"},"coordinates":[581110,469566]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Butembo","adm0name":"Congo (Kinshasa)","adm1name":"Nord-Kivu","iso_a2":"CD"},"coordinates":[581332,505487]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Goma","adm0name":"Congo (Kinshasa)","adm1name":"Nord-Kivu","iso_a2":"CD"},"coordinates":[581171,494771]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Mzuzu","adm0name":"Malawi","adm1name":"Mzimba","iso_a2":"MW"},"coordinates":[594500,436823]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Blantyre","adm0name":"Malawi","adm1name":"Blantyre","iso_a2":"MW"},"coordinates":[597193,411170]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Quetzaltenango","adm0name":"Guatemala","adm1name":"Quezaltenango","iso_a2":"GT"},"coordinates":[245778,592576]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Banjul","adm0name":"The Gambia","adm1name":"Banjul","iso_a2":"GM"},"coordinates":[453912,584424]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Faridabad","adm0name":"India","adm1name":"Haryana","iso_a2":"IN"},"coordinates":[714762,673180]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Srinagar","adm0name":"India","adm1name":"Jammu and Kashmir","iso_a2":"IN"},"coordinates":[707813,706752]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Vijayawada","adm0name":"India","adm1name":"Andhra Pradesh","iso_a2":"IN"},"coordinates":[723966,602600]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Thiruvananthapuram","adm0name":"India","adm1name":"Kerala","iso_a2":"IN"},"coordinates":[713744,555086]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Kochi","adm0name":"India","adm1name":"Kerala","iso_a2":"IN"},"coordinates":[711727,564062]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Cuttack","adm0name":"India","adm1name":"Orissa","iso_a2":"IN"},"coordinates":[738583,625991]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Hubli","adm0name":"India","adm1name":"Karnataka","iso_a2":"IN"},"coordinates":[708674,595728]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Mangalore","adm0name":"India","adm1name":"Karnataka","iso_a2":"IN"},"coordinates":[707916,581143]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Mysore","adm0name":"India","adm1name":"Karnataka","iso_a2":"IN"},"coordinates":[712938,577658]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Gulbarga","adm0name":"India","adm1name":"Karnataka","iso_a2":"IN"},"coordinates":[713388,607506]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Kolhapur","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[706166,603655]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Nanded","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[714721,618289]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Akola","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[713916,627412]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Guwahati","adm0name":"India","adm1name":"Assam","iso_a2":"IN"},"coordinates":[754911,659712]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Kayes","adm0name":"Congo (Brazzaville)","adm1name":"Bouenza","iso_a2":"CG"},"coordinates":[536888,479953]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Franceville","adm0name":"Gabon","adm1name":"Haut-Ogooué","iso_a2":"GA"},"coordinates":[537731,495041]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Bordeaux","adm0name":"France","adm1name":"Aquitaine","iso_a2":"FR"},"coordinates":[498342,770440]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Marseille","adm0name":"France","adm1name":"Provence-Alpes-Côte-d'Azur","iso_a2":"FR"},"coordinates":[514925,761198]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Le Havre","adm0name":"France","adm1name":"Haute-Normandie","iso_a2":"FR"},"coordinates":[500291,798007]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Gao","adm0name":"Mali","adm1name":"Gao","iso_a2":"ML"},"coordinates":[499860,601088]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Coihaique","adm0name":"Chile","adm1name":"Aisén del General Carlos Ibáñez del Campo","iso_a2":"CL"},"coordinates":[299806,234740]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Arica","adm0name":"Chile","adm1name":"Arica y Parinacota","iso_a2":"CL"},"coordinates":[304750,395115]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Copiapo","adm0name":"Chile","adm1name":"Atacama","iso_a2":"CL"},"coordinates":[304610,342624]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"La Serena","adm0name":"Chile","adm1name":"Coquimbo","iso_a2":"CL"},"coordinates":[302083,327576]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Los Angeles","adm0name":"Chile","adm1name":"Bío-Bío","iso_a2":"CL"},"coordinates":[299000,282787]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Narsarsuaq","adm0name":"Greenland","adm1name":"Kommune Kujalleq","iso_a2":"GL"},"coordinates":[373843,867096]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Sisimiut","adm0name":"Greenland","adm1name":"Qeqqata Kommunia","iso_a2":"GL"},"coordinates":[350925,901360]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Upernavik","adm0name":"Greenland","adm1name":"Qaasuitsup Kommunia","iso_a2":"GL"},"coordinates":[344051,935480]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Qaanaaq","adm0name":"Greenland","adm1name":"Qaasuitsup Kommunia","iso_a2":"GL"},"coordinates":[307410,963764]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Nouadhibou","adm0name":"Mauritania","adm1name":"Dakhlet Nouadhibou","iso_a2":"MR"},"coordinates":[452622,628538]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Kayes","adm0name":"Mali","adm1name":"Kayes","iso_a2":"ML"},"coordinates":[468221,590325]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Ayoun el Atrous","adm0name":"Mauritania","adm1name":"Hodh el Gharbi","iso_a2":"MR"},"coordinates":[473287,603457]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Segou","adm0name":"Mali","adm1name":"Ségou","iso_a2":"ML"},"coordinates":[482611,584342]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Skopje","adm0name":"Macedonia","adm1name":"Centar","iso_a2":"MK"},"coordinates":[559537,753544]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Al Jawf","adm0name":"Libya","adm1name":"Al Kufrah","iso_a2":"LY"},"coordinates":[564694,648089]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Tmassah","adm0name":"Libya","adm1name":"Murzuq","iso_a2":"LY"},"coordinates":[543888,660925]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Misratah","adm0name":"Libya","adm1name":"Misratah","iso_a2":"LY"},"coordinates":[541944,696550]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Zuwarah","adm0name":"Libya","adm1name":"An Nuqat al Khams","iso_a2":"LY"},"coordinates":[533553,699835]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Kirkuk","adm0name":"Iraq","adm1name":"At-Ta'mim","iso_a2":"IQ"},"coordinates":[623311,714871]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Mosul","adm0name":"Iraq","adm1name":"Ninawa","iso_a2":"IQ"},"coordinates":[619841,720053]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"An Najaf","adm0name":"Iraq","adm1name":"An-Najaf","iso_a2":"IQ"},"coordinates":[623153,694302]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Bahir Dar","adm0name":"Ethiopia","adm1name":"Amhara","iso_a2":"ET"},"coordinates":[603842,573441]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Mekele","adm0name":"Ethiopia","adm1name":"Tigray","iso_a2":"ET"},"coordinates":[609638,584697]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Dire Dawa","adm0name":"Ethiopia","adm1name":"Dire Dawa","iso_a2":"ET"},"coordinates":[616277,561532]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Rovaniemi","adm0name":"Finland","adm1name":"Lapland","iso_a2":"FI"},"coordinates":[571433,898693]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Vaasa","adm0name":"Finland","adm1name":"Western Finland","iso_a2":"FI"},"coordinates":[560000,878550]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Tampere","adm0name":"Finland","adm1name":"Pirkanmaa","iso_a2":"FI"},"coordinates":[565972,869071]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Aqtobe","adm0name":"Kazakhstan","adm1name":"Aqtöbe","iso_a2":"KZ"},"coordinates":[658805,802598]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Rudny","adm0name":"Kazakhstan","adm1name":"Qostanay","iso_a2":"KZ"},"coordinates":[675360,818432]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Qyzylorda","adm0name":"Kazakhstan","adm1name":"Qyzylorda","iso_a2":"KZ"},"coordinates":[681846,770133]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Atyrau","adm0name":"Kazakhstan","adm1name":"Atyrau","iso_a2":"KZ"},"coordinates":[644221,783834]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Ekibastuz","adm0name":"Kazakhstan","adm1name":"Pavlodar","iso_a2":"KZ"},"coordinates":[709222,811189]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Pavlodar","adm0name":"Kazakhstan","adm1name":"Pavlodar","iso_a2":"KZ"},"coordinates":[713750,814566]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Semey","adm0name":"Kazakhstan","adm1name":"East Kazakhstan","iso_a2":"KZ"},"coordinates":[722985,803517]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Oskemen","adm0name":"Kazakhstan","adm1name":"East Kazakhstan","iso_a2":"KZ"},"coordinates":[729486,800881]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Yazd","adm0name":"Iran","adm1name":"Yazd","iso_a2":"IR"},"coordinates":[651028,693826]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Ahvaz","adm0name":"Iran","adm1name":"Khuzestan","iso_a2":"IR"},"coordinates":[635327,690046]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Basra","adm0name":"Iraq","adm1name":"Al-Basrah","iso_a2":"IQ"},"coordinates":[632809,685504]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Bandar-e-Abbas","adm0name":"Iran","adm1name":"Hormozgan","iso_a2":"IR"},"coordinates":[656311,665886]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Hamadan","adm0name":"Iran","adm1name":"Hamadan","iso_a2":"IR"},"coordinates":[634763,710864]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Tabriz","adm0name":"Iran","adm1name":"East Azarbaijan","iso_a2":"IR"},"coordinates":[628608,730369]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ludhiana","adm0name":"India","adm1name":"Punjab","iso_a2":"IN"},"coordinates":[710750,687959]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Kota","adm0name":"India","adm1name":"Rajasthan","iso_a2":"IN"},"coordinates":[710647,653906]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jodhpur","adm0name":"India","adm1name":"Rajasthan","iso_a2":"IN"},"coordinates":[702818,660493]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Shymkent","adm0name":"Kazakhstan","adm1name":"South Kazakhstan","iso_a2":"KZ"},"coordinates":[693319,755440]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Taraz","adm0name":"Kazakhstan","adm1name":"Zhambyl","iso_a2":"KZ"},"coordinates":[698236,758876]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Lucknow","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[724758,663830]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Saharanpur","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[715416,682273]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ranchi","adm0name":"India","adm1name":"Jharkhand","iso_a2":"IN"},"coordinates":[737021,643183]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Bhagalpur","adm0name":"India","adm1name":"Bihar","iso_a2":"IN"},"coordinates":[741610,654191]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Raipur","adm0name":"India","adm1name":"Chhattisgarh","iso_a2":"IN"},"coordinates":[726757,630534]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Jabalpur","adm0name":"India","adm1name":"Madhya Pradesh","iso_a2":"IN"},"coordinates":[722091,642028]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Indore","adm0name":"India","adm1name":"Madhya Pradesh","iso_a2":"IN"},"coordinates":[710730,639303]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Pondicherry","adm0name":"India","adm1name":"Puducherry","iso_a2":"IN"},"coordinates":[721749,575425]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Salem","adm0name":"India","adm1name":"Tamil Nadu","iso_a2":"IN"},"coordinates":[717160,573867]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Tiruchirappalli","adm0name":"India","adm1name":"Tamil Nadu","iso_a2":"IN"},"coordinates":[718577,568772]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Pointe-Noire","adm0name":"Congo (Brazzaville)","adm1name":"Kouilou","iso_a2":"CG"},"coordinates":[533000,476457]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Kankan","adm0name":"Guinea","adm1name":"Kankan","iso_a2":"GN"},"coordinates":[474138,566272]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Nzerekore","adm0name":"Guinea","adm1name":"Nzerekore","iso_a2":"GN"},"coordinates":[475472,550691]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Bouake","adm0name":"Ivory Coast","adm1name":"Vallée du Bandama","iso_a2":"CI"},"coordinates":[486027,550276]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"St.-Denis","adm0name":"France","adm1name":"La Réunion","iso_a2":"RE"},"coordinates":[654022,381021]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Rio Branco","adm0name":"Brazil","adm1name":"Acre","iso_a2":"BR"},"coordinates":[311666,445671]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"São Luís","adm0name":"Brazil","adm1name":"Maranhão","iso_a2":"BR"},"coordinates":[377034,489822]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Porto Velho","adm0name":"Brazil","adm1name":"Rondônia","iso_a2":"BR"},"coordinates":[322500,452878]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Alvorada","adm0name":"Brazil","adm1name":"Tocantins","iso_a2":"BR"},"coordinates":[363661,430839]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Corumba","adm0name":"Brazil","adm1name":"Mato Grosso do Sul","iso_a2":"BR"},"coordinates":[339861,392057]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Belo Horizonte","adm0name":"Brazil","adm1name":"Minas Gerais","iso_a2":"BR"},"coordinates":[378008,386743]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Montes Claros","adm0name":"Brazil","adm1name":"Minas Gerais","iso_a2":"BR"},"coordinates":[378166,405660]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Uberlandia","adm0name":"Brazil","adm1name":"Minas Gerais","iso_a2":"BR"},"coordinates":[365888,392745]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Colider","adm0name":"Brazil","adm1name":"Mato Grosso","iso_a2":"BR"},"coordinates":[345970,440630]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Alta Floresta","adm0name":"Brazil","adm1name":"Mato Grosso","iso_a2":"BR"},"coordinates":[344694,446065]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Cuiaba","adm0name":"Brazil","adm1name":"Mato Grosso","iso_a2":"BR"},"coordinates":[344203,412487]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Pelotas","adm0name":"Brazil","adm1name":"Rio Grande do Sul","iso_a2":"BR"},"coordinates":[354639,316616]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Caxias do Sul","adm0name":"Brazil","adm1name":"Rio Grande do Sul","iso_a2":"BR"},"coordinates":[357860,331842]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ponta Grossa","adm0name":"Brazil","adm1name":"Paraná","iso_a2":"BR"},"coordinates":[360666,356072]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Teresina","adm0name":"Brazil","adm1name":"Piauí","iso_a2":"BR"},"coordinates":[381161,474543]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Maceio","adm0name":"Brazil","adm1name":"Alagoas","iso_a2":"BR"},"coordinates":[400745,447735]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Vitoria da Conquista","adm0name":"Brazil","adm1name":"Bahia","iso_a2":"BR"},"coordinates":[386555,416739]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Barreiras","adm0name":"Brazil","adm1name":"Bahia","iso_a2":"BR"},"coordinates":[375000,432794]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Vila Velha","adm0name":"Brazil","adm1name":"Espírito Santo","iso_a2":"BR"},"coordinates":[388005,384050]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Natal","adm0name":"Brazil","adm1name":"Rio Grande do Norte","iso_a2":"BR"},"coordinates":[402105,470485]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Thompson","adm0name":"Canada","adm1name":"Manitoba","iso_a2":"CA"},"coordinates":[228148,835005]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Brandon","adm0name":"Canada","adm1name":"Manitoba","iso_a2":"CA"},"coordinates":[222361,799952]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Fort Smith","adm0name":"Canada","adm1name":"Alberta","iso_a2":"CA"},"coordinates":[189212,860185]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Fort McMurray","adm0name":"Canada","adm1name":"Alberta","iso_a2":"CA"},"coordinates":[190601,840831]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Peace River","adm0name":"Canada","adm1name":"Alberta","iso_a2":"CA"},"coordinates":[174213,837869]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Fort St. John","adm0name":"Canada","adm1name":"British Columbia","iso_a2":"CA"},"coordinates":[164352,837967]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Iqaluit","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[309722,882404]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Cambridge Bay","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[208240,914197]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kugluktuk","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[180207,906387]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Chesterfield Inlet","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[248055,879962]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Arviat","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[238726,866752]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Taloyoak","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[240185,916664]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Igloolik","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[272796,915024]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Dawson City","adm0name":"Canada","adm1name":"Yukon","iso_a2":"CA"},"coordinates":[112731,884277]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Timmins","adm0name":"Canada","adm1name":"Ontario","iso_a2":"CA"},"coordinates":[274074,791855]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"North Bay","adm0name":"Canada","adm1name":"Ontario","iso_a2":"CA"},"coordinates":[279305,779019]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kuujjuarapik","adm0name":"Canada","adm1name":"Québec","iso_a2":"CA"},"coordinates":[283983,832229]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Kuujjuaq","adm0name":"Canada","adm1name":"Québec","iso_a2":"CA"},"coordinates":[310000,848928]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Sydney","adm0name":"Canada","adm1name":"Nova Scotia","iso_a2":"CA"},"coordinates":[332833,777634]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Labrador City","adm0name":"Canada","adm1name":"Newfoundland and Labrador","iso_a2":"CA"},"coordinates":[314122,818366]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Ebolowa","adm0name":"Cameroon","adm1name":"Sud","iso_a2":"CM"},"coordinates":[530972,521898]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Bambari","adm0name":"Central African Republic","adm1name":"Ouaka","iso_a2":"CF"},"coordinates":[557408,538854]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Venice","adm0name":"Italy","adm1name":"Veneto","iso_a2":"IT"},"coordinates":[534263,773916]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"El Calafate","adm0name":"Argentina","adm1name":"Santa Cruz","iso_a2":"AR"},"coordinates":[299166,206520]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"San Juan","adm0name":"Argentina","adm1name":"San Juan","iso_a2":"AR"},"coordinates":[309667,317800]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Rawson","adm0name":"Argentina","adm1name":"Chubut","iso_a2":"AR"},"coordinates":[319167,248188]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Neuquen","adm0name":"Argentina","adm1name":"Neuquén","iso_a2":"AR"},"coordinates":[310944,273959]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Trinidad","adm0name":"Bolivia","adm1name":"El Beni","iso_a2":"BO"},"coordinates":[319722,416838]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Santa Rosa","adm0name":"Argentina","adm1name":"La Pampa","iso_a2":"AR"},"coordinates":[321389,287763]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"San Carlos de Bariloche","adm0name":"Argentina","adm1name":"Río Negro","iso_a2":"AR"},"coordinates":[301944,260926]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Salta","adm0name":"Argentina","adm1name":"Salta","iso_a2":"AR"},"coordinates":[318286,357889]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Tucumán","adm0name":"Argentina","adm1name":"Tucumán","iso_a2":"AR"},"coordinates":[318837,345858]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Formosa","adm0name":"Argentina","adm1name":"Formosa","iso_a2":"AR"},"coordinates":[338380,349657]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Santa Fe","adm0name":"Argentina","adm1name":"Santa Fe","iso_a2":"AR"},"coordinates":[331416,317363]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Rosario","adm0name":"Argentina","adm1name":"Santa Fe","iso_a2":"AR"},"coordinates":[331477,309511]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Campinas","adm0name":"Brazil","adm1name":"São Paulo","iso_a2":"BR"},"coordinates":[369161,369059]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Sorocaba","adm0name":"Brazil","adm1name":"São Paulo","iso_a2":"BR"},"coordinates":[368139,365552]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Ribeirao Preto","adm0name":"Brazil","adm1name":"São Paulo","iso_a2":"BR"},"coordinates":[367139,379296]},{"type":"Point","properties":{"scalerank":4,"labelrank":1,"name":"Petrolina","adm0name":"Brazil","adm1name":"Pernambuco","iso_a2":"BR"},"coordinates":[387472,449145]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Bamenda","adm0name":"Cameroon","adm1name":"Nord-Ouest","iso_a2":"CM"},"coordinates":[528194,540027]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Garoua","adm0name":"Cameroon","adm1name":"Nord","iso_a2":"CM"},"coordinates":[537194,559815]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Herat","adm0name":"Afghanistan","adm1name":"Hirat","iso_a2":"AF"},"coordinates":[672694,708103]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Mazar-e Sharif","adm0name":"Afghanistan","adm1name":"Balkh","iso_a2":"AF"},"coordinates":[686388,722144]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Battambang","adm0name":"Cambodia","adm1name":"Batdâmbâng","iso_a2":"KH"},"coordinates":[786666,582327]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Siem Reap","adm0name":"Cambodia","adm1name":"Siemréab","iso_a2":"KH"},"coordinates":[788471,583907]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Malanje","adm0name":"Angola","adm1name":"Malanje","iso_a2":"AO"},"coordinates":[545389,448198]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Benguela","adm0name":"Angola","adm1name":"Benguela","iso_a2":"AO"},"coordinates":[537242,430198]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Lubango","adm0name":"Angola","adm1name":"Huíla","iso_a2":"AO"},"coordinates":[537471,416383]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Namibe","adm0name":"Angola","adm1name":"Namibe","iso_a2":"AO"},"coordinates":[533778,414725]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Tarija","adm0name":"Bolivia","adm1name":"Tarija","iso_a2":"BO"},"coordinates":[320138,377243]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Bridgetown","adm0name":"Barbados","adm1name":"Saint Michael","iso_a2":"BB"},"coordinates":[334399,582339]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Annaba","adm0name":"Algeria","adm1name":"Annaba","iso_a2":"DZ"},"coordinates":[521555,723448]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Parakou","adm0name":"Benin","adm1name":"Borgou","iso_a2":"BJ"},"coordinates":[507278,560052]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Porto-Novo","adm0name":"Benin","adm1name":"Ouémé","iso_a2":"BJ"},"coordinates":[507268,543127]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Constantine","adm0name":"Algeria","adm1name":"Constantine","iso_a2":"DZ"},"coordinates":[518332,720130]},{"type":"Point","properties":{"scalerank":4,"labelrank":6,"name":"Brest","adm0name":"Belarus","adm1name":"Brest","iso_a2":"BY"},"coordinates":[565833,813381]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Khulna","adm0name":"Bangladesh","adm1name":"Khulna","iso_a2":"BD"},"coordinates":[748772,640043]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Francistown","adm0name":"Botswana","adm1name":"Central","iso_a2":"BW"},"coordinates":[576388,379296]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Mahalapye","adm0name":"Botswana","adm1name":"Central","iso_a2":"BW"},"coordinates":[574500,367862]},{"type":"Point","properties":{"scalerank":4,"labelrank":7,"name":"Serowe","adm0name":"Botswana","adm1name":"Central","iso_a2":"BW"},"coordinates":[574194,372068]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Katherine","adm0name":"Australia","adm1name":"Northern Territory","iso_a2":"AU"},"coordinates":[867406,419010]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Busselton","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[820412,305321]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Mandurah","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[821519,312034]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Broome","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[839530,398304]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Kalgoorlie","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[837388,322627]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Albany","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[827476,297261]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Port Hedland","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[829460,384389]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Karratha","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[824638,381901]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Geraldton","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[818332,334291]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Griffith","adm0name":"Australia","adm1name":"New South Wales","iso_a2":"AU"},"coordinates":[905665,301567]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Orange","adm0name":"Australia","adm1name":"New South Wales","iso_a2":"AU"},"coordinates":[914166,307551]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Dubbo","adm0name":"Australia","adm1name":"New South Wales","iso_a2":"AU"},"coordinates":[912769,313595]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Armidale","adm0name":"Australia","adm1name":"New South Wales","iso_a2":"AU"},"coordinates":[921297,323948]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Broken Hill","adm0name":"Australia","adm1name":"New South Wales","iso_a2":"AU"},"coordinates":[892869,315431]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Port Lincoln","adm0name":"Australia","adm1name":"South Australia","iso_a2":"AU"},"coordinates":[877407,298942]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Whyalla","adm0name":"Australia","adm1name":"South Australia","iso_a2":"AU"},"coordinates":[882114,309062]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Portland","adm0name":"Australia","adm1name":"Victoria","iso_a2":"AU"},"coordinates":[893305,277573]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Bendigo","adm0name":"Australia","adm1name":"Victoria","iso_a2":"AU"},"coordinates":[900777,286934]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Wangaratta","adm0name":"Australia","adm1name":"Victoria","iso_a2":"AU"},"coordinates":[906388,289304]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Windorah","adm0name":"Australia","adm1name":"Queensland","iso_a2":"AU"},"coordinates":[896250,354039]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Mount Isa","adm0name":"Australia","adm1name":"Queensland","iso_a2":"AU"},"coordinates":[887471,381939]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Rockhampton","adm0name":"Australia","adm1name":"Queensland","iso_a2":"AU"},"coordinates":[918110,366299]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Cairns","adm0name":"Australia","adm1name":"Queensland","iso_a2":"AU"},"coordinates":[904897,404666]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Gold Coast","adm0name":"Australia","adm1name":"Queensland","iso_a2":"AU"},"coordinates":[926245,338350]},{"type":"Point","properties":{"scalerank":4,"labelrank":3,"name":"Devonport","adm0name":"Australia","adm1name":"Tasmania","iso_a2":"AU"},"coordinates":[906475,260673]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Bobo Dioulasso","adm0name":"Burkina Faso","adm1name":"Houet","iso_a2":"BF"},"coordinates":[488083,570952]},{"type":"Point","properties":{"scalerank":4,"labelrank":2,"name":"Rajshahi","adm0name":"Bangladesh","adm1name":"Rajshahi","iso_a2":"BD"},"coordinates":[746118,649137]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Mandalay","adm0name":"Myanmar","adm1name":"Mandalay","iso_a2":"MM"},"coordinates":[766897,634889]},{"type":"Point","properties":{"scalerank":4,"labelrank":5,"name":"Sittwe","adm0name":"Myanmar","adm1name":"Rakhine","iso_a2":"MM"},"coordinates":[758000,624035]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Bujumbura","adm0name":"Burundi","adm1name":"Bujumbura Mairie","iso_a2":"BI"},"coordinates":[581555,484715]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Pago Pago","adm0name":"American Samoa","iso_a2":"AS"},"coordinates":[25815,420136]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Kingstown","adm0name":"Saint Vincent and the Grenadines","iso_a2":"VC"},"coordinates":[329966,582614]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Castries","adm0name":"Saint Lucia","iso_a2":"LC"},"coordinates":[330555,587671]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Basseterre","adm0name":"Saint Kitts and Nevis","iso_a2":"KN"},"coordinates":[325786,607222]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Las Palmas","adm0name":"Spain","iso_a2":"ES"},"coordinates":[457138,671194]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Berbera","adm0name":"Somaliland","iso_a2":"-99"},"coordinates":[625045,566542]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Port Louis","adm0name":"Mauritius","iso_a2":"MU"},"coordinates":[659722,385241]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Gaza","adm0name":"Palestine","iso_a2":"PS"},"coordinates":[595680,691515]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Saint George's","adm0name":"Grenada","iso_a2":"GD"},"coordinates":[328495,576122]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Papeete","adm0name":"French Polynesia","iso_a2":"PF"},"coordinates":[84537,400842]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Manama","adm0name":"Bahrain","iso_a2":"BH"},"coordinates":[640508,660152]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Freeport","adm0name":"The Bahamas","iso_a2":"BS"},"coordinates":[281389,661912]},{"type":"Point","properties":{"scalerank":4,"labelrank":0,"name":"Saint John's","adm0name":"Antigua and Barbuda","iso_a2":"AG"},"coordinates":[328194,606132]},{"type":"Point","properties":{"scalerank":4,"labelrank":8,"name":"Taichung","adm0name":"Taiwan","adm1name":"Taichung City","iso_a2":"TW"},"coordinates":[835226,647805]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Kozhikode","adm0name":"India","adm1name":"Kerala","iso_a2":"IN"},"coordinates":[710466,571381]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Bhubaneshwar","adm0name":"India","adm1name":"Orissa","iso_a2":"IN"},"coordinates":[738403,624820]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Jamshedpur","adm0name":"India","adm1name":"Jharkhand","iso_a2":"IN"},"coordinates":[739431,639733]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Montevideo","adm0name":"Uruguay","adm1name":"Montevideo","iso_a2":"UY"},"coordinates":[343963,298213]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Helena","adm0name":"United States of America","adm1name":"Montana","iso_a2":"US"},"coordinates":[188791,780754]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Bismarck","adm0name":"United States of America","adm1name":"North Dakota","iso_a2":"US"},"coordinates":[220046,782031]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Boise","adm0name":"United States of America","adm1name":"Idaho","iso_a2":"US"},"coordinates":[177146,763074]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"San Jose","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[161523,725711]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Sacramento","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[162578,733265]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Las Vegas","adm0name":"United States of America","adm1name":"Nevada","iso_a2":"US"},"coordinates":[179939,719253]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Santa Fe","adm0name":"United States of America","adm1name":"New Mexico","iso_a2":"US"},"coordinates":[205729,716142]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Portland","adm0name":"United States of America","adm1name":"Oregon","iso_a2":"US"},"coordinates":[159217,774410]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Salt Lake City","adm0name":"United States of America","adm1name":"Utah","iso_a2":"US"},"coordinates":[189078,746298]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Cheyenne","adm0name":"United States of America","adm1name":"Wyoming","iso_a2":"US"},"coordinates":[208834,748449]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Des Moines","adm0name":"United States of America","adm1name":"Iowa","iso_a2":"US"},"coordinates":[239944,751055]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Omaha","adm0name":"United States of America","adm1name":"Nebraska","iso_a2":"US"},"coordinates":[233305,749041]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Oklahoma City","adm0name":"United States of America","adm1name":"Oklahoma","iso_a2":"US"},"coordinates":[229109,714869]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Pierre","adm0name":"United States of America","adm1name":"South Dakota","iso_a2":"US"},"coordinates":[221248,767575]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"San Antonio","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[226363,679425]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"San Cristobal","adm0name":"Venezuela","adm1name":"Táchira","iso_a2":"VE"},"coordinates":[299305,550750]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Valencia","adm0name":"Venezuela","adm1name":"Carabobo","iso_a2":"VE"},"coordinates":[311161,565336]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Jackson","adm0name":"United States of America","adm1name":"Mississippi","iso_a2":"US"},"coordinates":[249486,696070]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Raleigh","adm0name":"United States of America","adm1name":"North Carolina","iso_a2":"US"},"coordinates":[281542,716923]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Cleveland","adm0name":"United States of America","adm1name":"Ohio","iso_a2":"US"},"coordinates":[273064,750415]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Cincinnati","adm0name":"United States of America","adm1name":"Ohio","iso_a2":"US"},"coordinates":[265392,736741]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Nashville","adm0name":"United States of America","adm1name":"Tennessee","iso_a2":"US"},"coordinates":[258939,719016]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Memphis","adm0name":"United States of America","adm1name":"Tennessee","iso_a2":"US"},"coordinates":[249994,712796]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Norfolk","adm0name":"United States of America","adm1name":"Virginia","iso_a2":"US"},"coordinates":[288111,723033]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Milwaukee","adm0name":"United States of America","adm1name":"Wisconsin","iso_a2":"US"},"coordinates":[255773,759792]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Buffalo","adm0name":"United States of America","adm1name":"New York","iso_a2":"US"},"coordinates":[280884,758769]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Pittsburgh","adm0name":"United States of America","adm1name":"Pennsylvania","iso_a2":"US"},"coordinates":[277772,744254]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Ciudad Guayana","adm0name":"Venezuela","adm1name":"Bolívar","iso_a2":"VE"},"coordinates":[326055,554305]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Lome","adm0name":"Togo","adm1name":"Maritime","iso_a2":"TG"},"coordinates":[503390,541057]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Tunis","adm0name":"Tunisia","adm1name":"Tunis","iso_a2":"TN"},"coordinates":[528276,722753]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Kodiak","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[76648,847091]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Cold Bay","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[48014,831747]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Bethel","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[50678,864884]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Point Hope","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[36644,909640]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Barrow","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[64476,927075]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Nome","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[40538,886881]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Valdez","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[93477,866914]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Juneau","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[126611,850197]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Fairbanks","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[89693,888841]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Prudhoe Bay","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[87030,921160]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Sevastapol","adm0name":"Ukraine","adm1name":"Crimea","iso_a2":"UA"},"coordinates":[592958,768948]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Abu Dhabi","adm0name":"United Arab Emirates","adm1name":"Abu Dhabi","iso_a2":"AE"},"coordinates":[651018,649669]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Ashgabat","adm0name":"Turkmenistan","adm1name":"Ahal","iso_a2":"TM"},"coordinates":[662175,729550]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Samarqand","adm0name":"Uzbekistan","adm1name":"Samarkand","iso_a2":"UZ"},"coordinates":[685958,739740]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Lusaka","adm0name":"Zambia","adm1name":"Lusaka","iso_a2":"ZM"},"coordinates":[578559,413393]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Harare","adm0name":"Zimbabwe","adm1name":"Harare","iso_a2":"ZW"},"coordinates":[586230,399168]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Bulawayo","adm0name":"Zimbabwe","adm1name":"Bulawayo","iso_a2":"ZW"},"coordinates":[579388,385221]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Dili","adm0name":"East Timor","adm1name":"Dili","iso_a2":"TL"},"coordinates":[848831,454008]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Port Vila","adm0name":"Vanuatu","adm1name":"Shefa","iso_a2":"VU"},"coordinates":[967545,399657]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Tegucigalpa","adm0name":"Honduras","adm1name":"Francisco Morazán","iso_a2":"HN"},"coordinates":[257724,588276]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Georgetown","adm0name":"Guyana","adm1name":"East Berbice-Corentyne","iso_a2":"GY"},"coordinates":[338424,545015]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Reykjavík","adm0name":"Iceland","adm1name":"Suðurnes","iso_a2":"IS"},"coordinates":[439028,884771]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Port-au-Prince","adm0name":"Haiti","adm1name":"Ouest","iso_a2":"HT"},"coordinates":[299061,614574]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Glasgow","adm0name":"United Kingdom","adm1name":"Glasgow","iso_a2":"GB"},"coordinates":[488187,835753]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Kampala","adm0name":"Uganda","adm1name":"Kampala","iso_a2":"UG"},"coordinates":[590503,506605]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Aden","adm0name":"Yemen","adm1name":"`Adan","iso_a2":"YE"},"coordinates":[625025,580430]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Paramaribo","adm0name":"Suriname","adm1name":"Paramaribo","iso_a2":"SR"},"coordinates":[346758,539286]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Seville","adm0name":"Spain","adm1name":"Andalucía","iso_a2":"ES"},"coordinates":[483389,726322]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Zinder","adm0name":"Niger","adm1name":"Zinder","iso_a2":"NE"},"coordinates":[524953,586474]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Niamey","adm0name":"Niger","adm1name":"Niamey","iso_a2":"NE"},"coordinates":[505874,584808]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Port Sudan","adm0name":"Sudan","adm1name":"Red Sea","iso_a2":"SD"},"coordinates":[603378,620930]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Dushanbe","adm0name":"Tajikistan","adm1name":"Tadzhikistan Territories","iso_a2":"TJ"},"coordinates":[691038,733164]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Cusco","adm0name":"Peru","adm1name":"Cusco","iso_a2":"PE"},"coordinates":[300077,424589]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Tacna","adm0name":"Peru","adm1name":"Tacna","iso_a2":"PE"},"coordinates":[304861,398077]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Trujillo","adm0name":"Peru","adm1name":"La Libertad","iso_a2":"PE"},"coordinates":[280499,456610]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Ica","adm0name":"Peru","adm1name":"Ica","iso_a2":"PE"},"coordinates":[289651,421372]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Asuncion","adm0name":"Paraguay","adm1name":"Asunción","iso_a2":"PY"},"coordinates":[339879,354861]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Managua","adm0name":"Nicaragua","adm1name":"Managua","iso_a2":"NI"},"coordinates":[260359,576729]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Freetown","adm0name":"Sierra Leone","adm1name":"Western","iso_a2":"SL"},"coordinates":[463233,554909]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Agadez","adm0name":"Niger","adm1name":"Agadez","iso_a2":"NE"},"coordinates":[522174,605409]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Niyala","adm0name":"Sudan","adm1name":"South Darfur","iso_a2":"SD"},"coordinates":[569138,576166]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Wau","adm0name":"South Sudan","adm1name":"West Bahr-al-Ghazal","iso_a2":"SS"},"coordinates":[577750,550335]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Dongola","adm0name":"Sudan","adm1name":"Northern","iso_a2":"SD"},"coordinates":[584676,618269]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Kassala","adm0name":"Sudan","adm1name":"Kassala","iso_a2":"SD"},"coordinates":[601083,596309]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Tromsø","adm0name":"Norway","adm1name":"Troms","iso_a2":"NO"},"coordinates":[552755,917266]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Trondheim","adm0name":"Norway","adm1name":"Sør-Trøndelag","iso_a2":"NO"},"coordinates":[528934,880426]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Bergen","adm0name":"Norway","adm1name":"Hordaland","iso_a2":"NO"},"coordinates":[514790,862501]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Islamabad","adm0name":"Pakistan","adm1name":"F.C.T.","iso_a2":"PK"},"coordinates":[703235,704383]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Multan","adm0name":"Pakistan","adm1name":"Punjab","iso_a2":"PK"},"coordinates":[698480,683647]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Hyderabad","adm0name":"Pakistan","adm1name":"Sind","iso_a2":"PK"},"coordinates":[689924,655091]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Peshawar","adm0name":"Pakistan","adm1name":"N.W.F.P.","iso_a2":"PK"},"coordinates":[698702,706190]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Kathmandu","adm0name":"Nepal","adm1name":"Bhaktapur","iso_a2":"NP"},"coordinates":[736984,668935]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Nacala","adm0name":"Mozambique","adm1name":"Nampula","iso_a2":"MZ"},"coordinates":[613096,418702]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Bloemfontein","adm0name":"South Africa","adm1name":"Orange Free State","iso_a2":"ZA"},"coordinates":[572860,332197]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Pretoria","adm0name":"South Africa","adm1name":"Gauteng","iso_a2":"ZA"},"coordinates":[578409,352429]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Port Moresby","adm0name":"Papua New Guinea","adm1name":"Central","iso_a2":"PG"},"coordinates":[908867,448644]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Honiara","adm0name":"Solomon Islands","adm1name":"Guadalcanal","iso_a2":"SB"},"coordinates":[944304,448802]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Panama City","adm0name":"Panama","adm1name":"Panama","iso_a2":"PA"},"coordinates":[279069,557859]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Fez","adm0name":"Morocco","adm1name":"Fès - Boulemane","iso_a2":"MA"},"coordinates":[486104,706483]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Rabat","adm0name":"Morocco","adm1name":"Rabat - Salé - Zemmour - Zaer","iso_a2":"MA"},"coordinates":[481009,706298]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Marrakesh","adm0name":"Morocco","adm1name":"Marrakech - Tensift - Al Haouz","iso_a2":"MA"},"coordinates":[477772,692119]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Chisinau","adm0name":"Moldova","adm1name":"Chisinau","iso_a2":"MD"},"coordinates":[580159,783196]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Beira","adm0name":"Mozambique","adm1name":"Sofala","iso_a2":"MZ"},"coordinates":[596860,387294]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Port Elizabeth","adm0name":"South Africa","adm1name":"Eastern Cape","iso_a2":"ZA"},"coordinates":[571105,303474]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Maputo","adm0name":"Mozambique","adm1name":"Maputo","iso_a2":"MZ"},"coordinates":[590520,350958]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Tomsk","adm0name":"Russia","adm1name":"Tomsk","iso_a2":"RU"},"coordinates":[736041,839419]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Anadyr","adm0name":"Russia","adm1name":"Chukchi Autonomous Okrug","iso_a2":"RU"},"coordinates":[992986,888248]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Murmansk","adm0name":"Russia","adm1name":"Murmansk","iso_a2":"RU"},"coordinates":[591944,913327]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Archangel","adm0name":"Russia","adm1name":"Arkhangel'sk","iso_a2":"RU"},"coordinates":[612625,887289]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Nizhny Novgorod","adm0name":"Russia","adm1name":"Nizhegorod","iso_a2":"RU"},"coordinates":[622217,838471]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Volgograd","adm0name":"Russia","adm1name":"Volgograd","iso_a2":"RU"},"coordinates":[623605,793308]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Ufa","adm0name":"Russia","adm1name":"Bashkortostan","iso_a2":"RU"},"coordinates":[655660,829329]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Yekaterinburg","adm0name":"Russia","adm1name":"Sverdlovsk","iso_a2":"RU"},"coordinates":[668327,841534]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Samara","adm0name":"Russia","adm1name":"Samara","iso_a2":"RU"},"coordinates":[639303,819880]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Kazan","adm0name":"Russia","adm1name":"Tatarstan","iso_a2":"RU"},"coordinates":[636456,835017]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Surgut","adm0name":"Russia","adm1name":"Khanty-Mansiy","iso_a2":"RU"},"coordinates":[703958,867649]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Barnaul","adm0name":"Russia","adm1name":"Altay","iso_a2":"RU"},"coordinates":[732624,820816]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Novosibirsk","adm0name":"Russia","adm1name":"Novosibirsk","iso_a2":"RU"},"coordinates":[730438,830751]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Mogadishu","adm0name":"Somalia","adm1name":"Banaadir","iso_a2":"SO"},"coordinates":[626013,516973]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Muscat","adm0name":"Oman","adm1name":"Muscat","iso_a2":"OM"},"coordinates":[662758,644613]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Colombo","adm0name":"Sri Lanka","adm1name":"Colombo","iso_a2":"LK"},"coordinates":[721827,545785]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Cebu","adm0name":"Philippines","adm1name":"Cebu","iso_a2":"PH"},"coordinates":[844161,565868]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Iloilo","adm0name":"Philippines","adm1name":"Iloilo","iso_a2":"PH"},"coordinates":[840402,568139]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Davao","adm0name":"Philippines","adm1name":"Davao Del Sur","iso_a2":"PH"},"coordinates":[848966,546852]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Bratsk","adm0name":"Russia","adm1name":"Irkutsk","iso_a2":"RU"},"coordinates":[782263,837417]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Irkutsk","adm0name":"Russia","adm1name":"Irkutsk","iso_a2":"RU"},"coordinates":[789569,814685]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Krasnoyarsk","adm0name":"Russia","adm1name":"Krasnoyarsk","iso_a2":"RU"},"coordinates":[757955,836581]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Dickson","adm0name":"Russia","adm1name":"Taymyr","iso_a2":"RU"},"coordinates":[723736,940205]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Chita","adm0name":"Russia","adm1name":"Chita","iso_a2":"RU"},"coordinates":[815180,813115]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Vladivostok","adm0name":"Russia","adm1name":"Primor'ye","iso_a2":"RU"},"coordinates":[866416,760239]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Nizhneyansk","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[877962,927920]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Yakutsk","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[860374,872240]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Tiksi","adm0name":"Russia","adm1name":"Sakha (Yakutia)","iso_a2":"RU"},"coordinates":[857874,929067]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Magadan","adm0name":"Russia","adm1name":"Maga Buryatdan","iso_a2":"RU"},"coordinates":[918916,857666]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Tijuana","adm0name":"Mexico","adm1name":"Baja California","iso_a2":"MX"},"coordinates":[174772,697273]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Chihuahua","adm0name":"Mexico","adm1name":"Chihuahua","iso_a2":"MX"},"coordinates":[205314,674434]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Mazatlan","adm0name":"Mexico","adm1name":"Sinaloa","iso_a2":"MX"},"coordinates":[204388,642289]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Tampico","adm0name":"Mexico","adm1name":"Tamaulipas","iso_a2":"MX"},"coordinates":[228139,636832]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Acapulco","adm0name":"Mexico","adm1name":"Guerrero","iso_a2":"MX"},"coordinates":[222456,604544]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Veracruz","adm0name":"Mexico","adm1name":"Veracruz","iso_a2":"MX"},"coordinates":[232889,618332]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Tuxtla Gutierrez","adm0name":"Mexico","adm1name":"Chiapas","iso_a2":"MX"},"coordinates":[241250,603952]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Cancun","adm0name":"Mexico","adm1name":"Quintana Roo","iso_a2":"MX"},"coordinates":[258805,630138]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Merida","adm0name":"Mexico","adm1name":"Yucatán","iso_a2":"MX"},"coordinates":[251059,628944]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Enugu","adm0name":"Nigeria","adm1name":"Enugu","iso_a2":"NG"},"coordinates":[520833,542930]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Sokoto","adm0name":"Nigeria","adm1name":"Sokoto","iso_a2":"NG"},"coordinates":[514555,582090]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Perm","adm0name":"Russia","adm1name":"Perm'","iso_a2":"RU"},"coordinates":[656244,848347]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Erdenet","adm0name":"Mongolia","adm1name":"Orhon","iso_a2":"MN"},"coordinates":[789217,795331]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Ulaanbaatar","adm0name":"Mongolia","adm1name":"Ulaanbaatar","iso_a2":"MN"},"coordinates":[796984,788609]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Wellington","adm0name":"New Zealand","adm1name":"Manawatu-Wanganui","iso_a2":"NZ"},"coordinates":[985509,260037]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Mbeya","adm0name":"Tanzania","adm1name":"Mbeya","iso_a2":"TZ"},"coordinates":[592861,452049]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Windhoek","adm0name":"Namibia","adm1name":"Khomas","iso_a2":"NA"},"coordinates":[547454,371002]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Grootfontein","adm0name":"Namibia","adm1name":"Otjozondjupa","iso_a2":"NA"},"coordinates":[550323,388796]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Zanzibar","adm0name":"Tanzania","adm1name":"Zanzibar West","iso_a2":"TZ"},"coordinates":[608889,468223]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Christchurch","adm0name":"New Zealand","adm1name":"Canterbury","iso_a2":"NZ"},"coordinates":[979527,246796]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Valencia","adm0name":"Spain","adm1name":"Comunidad Valenciana","iso_a2":"ES"},"coordinates":[498883,738656]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Palana","adm0name":"Russia","adm1name":"Kamchatka","iso_a2":"RU"},"coordinates":[944305,854757]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Petropavlovsk Kamchatskiy","adm0name":"Russia","adm1name":"Kamchatka","iso_a2":"RU"},"coordinates":[940619,819080]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Abuja","adm0name":"Nigeria","adm1name":"Federal Capital Territory","iso_a2":"NG"},"coordinates":[520920,558542]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Padang","adm0name":"Indonesia","adm1name":"Sumatera Barat","iso_a2":"ID"},"coordinates":[778771,499041]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Bissau","adm0name":"Guinea Bissau","adm1name":"Bissau","iso_a2":"GW"},"coordinates":[456670,575011]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Palermo","adm0name":"Italy","adm1name":"Sicily","iso_a2":"IT"},"coordinates":[537078,730599]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Amman","adm0name":"Jordan","adm1name":"Amman","iso_a2":"JO"},"coordinates":[599808,694015]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Vilnius","adm0name":"Lithuania","adm1name":"Vilniaus","iso_a2":"LT"},"coordinates":[570324,828686]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Riga","adm0name":"Latvia","adm1name":"Riga","iso_a2":"LV"},"coordinates":[566943,842115]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Bishkek","adm0name":"Kyrgyzstan","adm1name":"Bishkek","iso_a2":"KG"},"coordinates":[707175,758728]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Jiayuguan","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[773055,740629]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Xining","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[782688,721682]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Guilin","adm0name":"China","adm1name":"Guangxi","iso_a2":"CN"},"coordinates":[806327,654499]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Huainan","adm0name":"China","adm1name":"Anhui","iso_a2":"CN"},"coordinates":[824938,698043]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Shantou","adm0name":"China","adm1name":"Guangdong","iso_a2":"CN"},"coordinates":[824077,643183]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Tarakan","adm0name":"Indonesia","adm1name":"Kalimantan Timur","iso_a2":"ID"},"coordinates":[826757,524268]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Mombasa","adm0name":"Kenya","adm1name":"Coast","iso_a2":"KE"},"coordinates":[610243,480793]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Maseru","adm0name":"Lesotho","adm1name":"Maseru","iso_a2":"LS"},"coordinates":[576342,331032]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Antananarivo","adm0name":"Madagascar","adm1name":"Antananarivo","iso_a2":"MG"},"coordinates":[631985,392658]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Semarang","adm0name":"Indonesia","adm1name":"Jawa Tengah","iso_a2":"ID"},"coordinates":[806717,463455]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Palembang","adm0name":"Indonesia","adm1name":"Sumatera Selatan","iso_a2":"ID"},"coordinates":[790966,487074]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Bandjarmasin","adm0name":"Indonesia","adm1name":"Kalimantan Selatan","iso_a2":"ID"},"coordinates":[818277,484989]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Ujungpandang","adm0name":"Indonesia","adm1name":"Sulawesi Selatan","iso_a2":"ID"},"coordinates":[831749,474277]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Lyon","adm0name":"France","adm1name":"Rhône-Alpes","iso_a2":"FR"},"coordinates":[513411,775891]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Quito","adm0name":"Ecuador","adm1name":"Pichincha","iso_a2":"EC"},"coordinates":[281939,503455]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"San Jose","adm0name":"Costa Rica","adm1name":"San José","iso_a2":"CR"},"coordinates":[266428,563588]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"San Salvador","adm0name":"El Salvador","adm1name":"San Salvador","iso_a2":"SV"},"coordinates":[252208,585953]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Kingston","adm0name":"Jamaica","adm1name":"Kingston","iso_a2":"JM"},"coordinates":[286756,611221]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Cartagena","adm0name":"Colombia","adm1name":"Bolívar","iso_a2":"CO"},"coordinates":[290232,566341]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Mitu","adm0name":"Colombia","adm1name":"Vaupés","iso_a2":"CO"},"coordinates":[305073,511816]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Bumba","adm0name":"Congo (Kinshasa)","adm1name":"Équateur","iso_a2":"CD"},"coordinates":[562388,517692]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Ndjamena","adm0name":"Chad","adm1name":"Hadjer-Lamis","iso_a2":"TD"},"coordinates":[541797,576492]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Abeche","adm0name":"Chad","adm1name":"Ouaddaï","iso_a2":"TD"},"coordinates":[557860,586711]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Malabo","adm0name":"Equatorial Guinea","adm1name":"Bioko Norte","iso_a2":"GQ"},"coordinates":[524398,526934]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Luxor","adm0name":"Egypt","adm1name":"Qina","iso_a2":"EG"},"coordinates":[590694,656975]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Asmara","adm0name":"Eritrea","adm1name":"Anseba","iso_a2":"ER"},"coordinates":[608148,595558]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Zagreb","adm0name":"Croatia","adm1name":"Grad Zagreb","iso_a2":"HR"},"coordinates":[544444,776057]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Tallinn","adm0name":"Estonia","adm1name":"Harju","iso_a2":"EE"},"coordinates":[568688,856830]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Lhasa","adm0name":"China","adm1name":"Xizang","iso_a2":"CN"},"coordinates":[753055,680348]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Hami","adm0name":"China","adm1name":"Xinjiang Uygur","iso_a2":"CN"},"coordinates":[759763,758443]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Hotan","adm0name":"China","adm1name":"Xinjiang Uygur","iso_a2":"CN"},"coordinates":[722018,724513]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Kashgar","adm0name":"China","adm1name":"Xinjiang Uygur","iso_a2":"CN"},"coordinates":[711027,738593]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Yinchuan","adm0name":"China","adm1name":"Ningxia Hui","iso_a2":"CN"},"coordinates":[795197,732631]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Pingxiang","adm0name":"China","adm1name":"Jiangxi","iso_a2":"CN"},"coordinates":[816244,668362]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Nagasaki","adm0name":"Japan","adm1name":"Nagasaki","iso_a2":"JP"},"coordinates":[860790,698831]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Qiqihar","adm0name":"China","adm1name":"Heilongjiang","iso_a2":"CN"},"coordinates":[844410,785222]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Kikwit","adm0name":"Congo (Kinshasa)","adm1name":"Bandundu","iso_a2":"CD"},"coordinates":[552361,474917]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Matadi","adm0name":"Congo (Kinshasa)","adm1name":"Bas-Congo","iso_a2":"CD"},"coordinates":[537360,470257]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Kolwezi","adm0name":"Congo (Kinshasa)","adm1name":"Katanga","iso_a2":"CD"},"coordinates":[570756,441226]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Lubumbashi","adm0name":"Congo (Kinshasa)","adm1name":"Katanga","iso_a2":"CD"},"coordinates":[576327,435531]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Lilongwe","adm0name":"Malawi","adm1name":"Lilongwe","iso_a2":"MW"},"coordinates":[593842,421873]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Guatemala","adm0name":"Guatemala","adm1name":"Guatemala","iso_a2":"GT"},"coordinates":[248530,591351]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Cayenne","adm0name":"France","adm1name":"Guinaa","iso_a2":"GF"},"coordinates":[354639,533942]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Libreville","adm0name":"Gabon","adm1name":"Estuaire","iso_a2":"GA"},"coordinates":[526271,507000]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Vishakhapatnam","adm0name":"India","adm1name":"Andhra Pradesh","iso_a2":"IN"},"coordinates":[731396,609769]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Suva","adm0name":"Fiji","adm1name":"Central","iso_a2":"FJ"},"coordinates":[995670,397289]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Port-Gentil","adm0name":"Gabon","adm1name":"Ogooué-Maritime","iso_a2":"GA"},"coordinates":[524389,500451]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Timbuktu","adm0name":"Mali","adm1name":"Timbuktu","iso_a2":"ML"},"coordinates":[491620,604050]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Punta Arenas","adm0name":"Chile","adm1name":"Magallanes y Antártica Chilena","iso_a2":"CL"},"coordinates":[302944,189744]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Iquique","adm0name":"Chile","adm1name":"Tarapacá","iso_a2":"CL"},"coordinates":[305194,384747]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Antofagasta","adm0name":"Chile","adm1name":"Antofagasta","iso_a2":"CL"},"coordinates":[304444,364604]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Valparaiso","adm0name":"Chile","adm1name":"Valparaíso","iso_a2":"CL"},"coordinates":[301047,308939]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Valdivia","adm0name":"Chile","adm1name":"Los Ríos","iso_a2":"CL"},"coordinates":[296541,268953]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Concepcion","adm0name":"Chile","adm1name":"Bío-Bío","iso_a2":"CL"},"coordinates":[297083,286520]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Puerto Montt","adm0name":"Chile","adm1name":"Los Lagos","iso_a2":"CL"},"coordinates":[297416,259030]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Nuuk","adm0name":"Greenland","adm1name":"Kommuneqarfik Sermersooq","iso_a2":"GL"},"coordinates":[356298,885057]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Nouakchott","adm0name":"Mauritania","adm1name":"Nouakchott","iso_a2":"MR"},"coordinates":[455623,611869]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Bamako","adm0name":"Mali","adm1name":"Bamako","iso_a2":"ML"},"coordinates":[477771,579673]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Atar","adm0name":"Mauritania","adm1name":"Adrar","iso_a2":"MR"},"coordinates":[463750,626267]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Djenne","adm0name":"Mali","adm1name":"Mopti","iso_a2":"ML"},"coordinates":[487360,587067]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Sabha","adm0name":"Libya","adm1name":"Sabha","iso_a2":"LY"},"coordinates":[540092,664874]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Banghazi","adm0name":"Libya","adm1name":"Benghazi","iso_a2":"LY"},"coordinates":[555735,695002]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Thessaloniki","adm0name":"Greece","adm1name":"Kentriki Makedonia","iso_a2":"GR"},"coordinates":[563564,745831]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Beirut","adm0name":"Lebanon","adm1name":"Beirut","iso_a2":"LB"},"coordinates":[598632,705401]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Tbilisi","adm0name":"Georgia","adm1name":"Tbilisi","iso_a2":"GE"},"coordinates":[624412,751926]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Gonder","adm0name":"Ethiopia","adm1name":"Amhara","iso_a2":"ET"},"coordinates":[604055,579425]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Astana","adm0name":"Kazakhstan","adm1name":"Aqmola","iso_a2":"KZ"},"coordinates":[698409,807937]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Qaraghandy","adm0name":"Kazakhstan","adm1name":"Qaraghandy","iso_a2":"KZ"},"coordinates":[703096,800258]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Almaty","adm0name":"Kazakhstan","adm1name":"Almaty","iso_a2":"KZ"},"coordinates":[713646,761406]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Isfahan","adm0name":"Iran","adm1name":"Esfahan","iso_a2":"IR"},"coordinates":[643606,698458]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Shiraz","adm0name":"Iran","adm1name":"Fars","iso_a2":"IR"},"coordinates":[646022,680270]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Amritsar","adm0name":"India","adm1name":"Punjab","iso_a2":"IN"},"coordinates":[707966,692178]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Varanasi","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[730549,654795]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Asansol","adm0name":"India","adm1name":"West Bengal","iso_a2":"IN"},"coordinates":[741614,645039]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Bhilai","adm0name":"India","adm1name":"Chhattisgarh","iso_a2":"IN"},"coordinates":[726197,630426]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Bhopal","adm0name":"India","adm1name":"Madhya Pradesh","iso_a2":"IN"},"coordinates":[715022,642472]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Madurai","adm0name":"India","adm1name":"Tamil Nadu","iso_a2":"IN"},"coordinates":[716994,563499]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Coimbatore","adm0name":"India","adm1name":"Tamil Nadu","iso_a2":"IN"},"coordinates":[713744,569897]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Vientiane","adm0name":"Laos","adm1name":"Vientiane [prefecture]","iso_a2":"LA"},"coordinates":[784999,611160]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Brazzaville","adm0name":"Congo (Brazzaville)","adm1name":"Pool","iso_a2":"CG"},"coordinates":[542451,479495]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Conakry","adm0name":"Guinea","adm1name":"Conakry","iso_a2":"GN"},"coordinates":[461993,561198]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Yamoussoukro","adm0name":"Ivory Coast","adm1name":"Lacs","iso_a2":"CI"},"coordinates":[485346,545112]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Cruzeiro do Sul","adm0name":"Brazil","adm1name":"Acre","iso_a2":"BR"},"coordinates":[298138,459513]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Leticia","adm0name":"Colombia","adm1name":"Amazonas","iso_a2":"CO"},"coordinates":[305679,479827]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Manaus","adm0name":"Brazil","adm1name":"Amazonas","iso_a2":"BR"},"coordinates":[333328,486363]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Caxias","adm0name":"Brazil","adm1name":"Maranhão","iso_a2":"BR"},"coordinates":[379583,476084]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Santarem","adm0name":"Brazil","adm1name":"Pará","iso_a2":"BR"},"coordinates":[348055,490302]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Maraba","adm0name":"Brazil","adm1name":"Pará","iso_a2":"BR"},"coordinates":[363566,473022]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Vilhena","adm0name":"Brazil","adm1name":"Rondônia","iso_a2":"BR"},"coordinates":[333009,429378]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Ji-Parana","adm0name":"Brazil","adm1name":"Rondônia","iso_a2":"BR"},"coordinates":[327870,440536]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Campo Grande","adm0name":"Brazil","adm1name":"Mato Grosso do Sul","iso_a2":"BR"},"coordinates":[348282,383573]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Florianopolis","adm0name":"Brazil","adm1name":"Santa Catarina","iso_a2":"BR"},"coordinates":[365216,341333]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Feira de Santana","adm0name":"Brazil","adm1name":"Bahia","iso_a2":"BR"},"coordinates":[391750,432142]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Winnipeg","adm0name":"Canada","adm1name":"Manitoba","iso_a2":"CA"},"coordinates":[230094,800247]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Churchill","adm0name":"Canada","adm1name":"Manitoba","iso_a2":"CA"},"coordinates":[238427,852873]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Regina","adm0name":"Canada","adm1name":"Saskatchewan","iso_a2":"CA"},"coordinates":[209397,803606]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Saskatoon","adm0name":"Canada","adm1name":"Saskatchewan","iso_a2":"CA"},"coordinates":[203694,813796]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Calgary","adm0name":"Canada","adm1name":"Alberta","iso_a2":"CA"},"coordinates":[183106,807367]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Prince Rupert","adm0name":"Canada","adm1name":"British Columbia","iso_a2":"CA"},"coordinates":[137973,826514]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Victoria","adm0name":"Canada","adm1name":"British Columbia","iso_a2":"CA"},"coordinates":[157361,791657]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Arctic Bay","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[263425,937400]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Resolute","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[236389,947175]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Repulse Bay","adm0name":"Canada","adm1name":"Nunavut","iso_a2":"CA"},"coordinates":[260325,898868]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Yellowknife","adm0name":"Canada","adm1name":"Northwest Territories","iso_a2":"CA"},"coordinates":[182230,874652]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Fort Good Hope","adm0name":"Canada","adm1name":"Northwest Territories","iso_a2":"CA"},"coordinates":[142685,897311]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Whitehorse","adm0name":"Canada","adm1name":"Yukon","iso_a2":"CA"},"coordinates":[124861,864430]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Boa Vista","adm0name":"Brazil","adm1name":"Roraima","iso_a2":"BR"},"coordinates":[331483,521401]},{"type":"Point","properties":{"scalerank":3,"labelrank":1,"name":"Macapá","adm0name":"Brazil","adm1name":"Amapá","iso_a2":"BR"},"coordinates":[358194,504913]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Ottawa","adm0name":"Canada","adm1name":"Ontario","iso_a2":"CA"},"coordinates":[289716,773798]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Fort Severn","adm0name":"Canada","adm1name":"Ontario","iso_a2":"CA"},"coordinates":[256528,836387]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Thunder Bay","adm0name":"Canada","adm1name":"Ontario","iso_a2":"CA"},"coordinates":[252014,791734]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Québec","adm0name":"Canada","adm1name":"Québec","iso_a2":"CA"},"coordinates":[302095,782218]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Halifax","adm0name":"Canada","adm1name":"Nova Scotia","iso_a2":"CA"},"coordinates":[323333,769244]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"St. John’s","adm0name":"Canada","adm1name":"Newfoundland and Labrador","iso_a2":"CA"},"coordinates":[353663,786632]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Nain","adm0name":"Canada","adm1name":"Newfoundland and Labrador","iso_a2":"CA"},"coordinates":[328650,839729]},{"type":"Point","properties":{"scalerank":3,"labelrank":2,"name":"Charlottetown","adm0name":"Canada","adm1name":"Prince Edward Island","iso_a2":"CA"},"coordinates":[324635,778719]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Ndele","adm0name":"Central African Republic","adm1name":"Bamingui-Bangoran","iso_a2":"CF"},"coordinates":[557369,554537]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Belgrade","adm0name":"Serbia","adm1name":"Grad Beograd","iso_a2":"RS"},"coordinates":[556849,770254]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Obo","adm0name":"Central African Republic","adm1name":"Haut-Mbomou","iso_a2":"CF"},"coordinates":[573611,536709]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Bandar Seri Begawan","adm0name":"Brunei","adm1name":"Brunei and Muara","iso_a2":"BN"},"coordinates":[819259,533648]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Puerto Deseado","adm0name":"Argentina","adm1name":"Santa Cruz","iso_a2":"AR"},"coordinates":[316944,221825]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Rio Gallegos","adm0name":"Argentina","adm1name":"Santa Cruz","iso_a2":"AR"},"coordinates":[307731,198818]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Comodoro Rivadavia","adm0name":"Argentina","adm1name":"Chubut","iso_a2":"AR"},"coordinates":[312500,232962]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Mendoza","adm0name":"Argentina","adm1name":"Mendoza","iso_a2":"AR"},"coordinates":[308837,309913]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Sucre","adm0name":"Bolivia","adm1name":"Chuquisaca","iso_a2":"BO"},"coordinates":[318723,391910]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Riberalta","adm0name":"Bolivia","adm1name":"El Beni","iso_a2":"BO"},"coordinates":[316388,439649]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Bahia Blanca","adm0name":"Argentina","adm1name":"Ciudad de Buenos Aires","iso_a2":"AR"},"coordinates":[327041,275203]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Mar del Plata","adm0name":"Argentina","adm1name":"Ciudad de Buenos Aires","iso_a2":"AR"},"coordinates":[340055,279587]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Córdoba","adm0name":"Argentina","adm1name":"Córdoba","iso_a2":"AR"},"coordinates":[321710,318701]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Posadas","adm0name":"Argentina","adm1name":"Misiones","iso_a2":"AR"},"coordinates":[344763,342637]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Belmopan","adm0name":"Belize","adm1name":"Cayo","iso_a2":"BZ"},"coordinates":[253425,606926]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Bangui","adm0name":"Central African Republic","adm1name":"Bangui","iso_a2":"CF"},"coordinates":[551550,530587]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Maroua","adm0name":"Cameroon","adm1name":"Extrême-Nord","iso_a2":"CM"},"coordinates":[539790,567490]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Yaounde","adm0name":"Cameroon","adm1name":"Centre","iso_a2":"CM"},"coordinates":[531985,527636]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Tirana","adm0name":"Albania","adm1name":"Durrës","iso_a2":"AL"},"coordinates":[555051,749560]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Yerevan","adm0name":"Armenia","adm1name":"Erevan","iso_a2":"AM"},"coordinates":[623642,742780]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Baku","adm0name":"Azerbaijan","adm1name":"Baki","iso_a2":"AZ"},"coordinates":[638500,744048]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Kandahar","adm0name":"Afghanistan","adm1name":"Kandahar","iso_a2":"AF"},"coordinates":[682485,691989]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Phnom Penh","adm0name":"Cambodia","adm1name":"Phnom Penh","iso_a2":"KH"},"coordinates":[791428,573156]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Menongue","adm0name":"Angola","adm1name":"Cuando Cubango","iso_a2":"AO"},"coordinates":[549166,417825]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Huambo","adm0name":"Angola","adm1name":"Huambo","iso_a2":"AO"},"coordinates":[543772,429192]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"La Paz","adm0name":"Bolivia","adm1name":"La Paz","iso_a2":"BO"},"coordinates":[310688,406987]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Santa Cruz","adm0name":"Bolivia","adm1name":"Santa Cruz","iso_a2":"BO"},"coordinates":[324366,399546]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Oran","adm0name":"Algeria","adm1name":"Oran","iso_a2":"DZ"},"coordinates":[498272,716291]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Cotonou","adm0name":"Benin","adm1name":"Ouémé","iso_a2":"BJ"},"coordinates":[506994,542646]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Tamanrasset","adm0name":"Algeria","adm1name":"Tamanghasset","iso_a2":"DZ"},"coordinates":[515341,639705]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Ghardaia","adm0name":"Algeria","adm1name":"Ghardaïa","iso_a2":"DZ"},"coordinates":[510194,697202]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Sofia","adm0name":"Bulgaria","adm1name":"Grad Sofiya","iso_a2":"BG"},"coordinates":[564762,757604]},{"type":"Point","properties":{"scalerank":3,"labelrank":6,"name":"Minsk","adm0name":"Belarus","adm1name":"Minsk","iso_a2":"BY"},"coordinates":[576568,824056]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Thimphu","adm0name":"Bhutan","adm1name":"Thimphu","iso_a2":"BT"},"coordinates":[748997,667480]},{"type":"Point","properties":{"scalerank":3,"labelrank":7,"name":"Gaborone","adm0name":"Botswana","adm1name":"South-East","iso_a2":"BW"},"coordinates":[571977,358701]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Darwin","adm0name":"Australia","adm1name":"Northern Territory","iso_a2":"AU"},"coordinates":[863471,431104]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Alice Springs","adm0name":"Australia","adm1name":"Northern Territory","iso_a2":"AU"},"coordinates":[871888,364302]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Canberra","adm0name":"Australia","adm1name":"Australian Capital Territory","iso_a2":"AU"},"coordinates":[914247,295685]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Newcastle","adm0name":"Australia","adm1name":"New South Wales","iso_a2":"AU"},"coordinates":[921708,310126]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Adelaide","adm0name":"Australia","adm1name":"South Australia","iso_a2":"AU"},"coordinates":[884994,297758]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Townsville","adm0name":"Australia","adm1name":"Queensland","iso_a2":"AU"},"coordinates":[907694,390672]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Brisbane","adm0name":"Australia","adm1name":"Queensland","iso_a2":"AU"},"coordinates":[925091,342073]},{"type":"Point","properties":{"scalerank":3,"labelrank":3,"name":"Hobart","adm0name":"Australia","adm1name":"Tasmania","iso_a2":"AU"},"coordinates":[909152,250854]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Ouagadougou","adm0name":"Burkina Faso","adm1name":"Kadiogo","iso_a2":"BF"},"coordinates":[495759,578016]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Sarajevo","adm0name":"Bosnia and Herzegovina","adm1name":"Sarajevo","iso_a2":"BA"},"coordinates":[551063,764505]},{"type":"Point","properties":{"scalerank":3,"labelrank":5,"name":"Naypyidaw","adm0name":"Myanmar","adm1name":"Mandalay","iso_a2":"MM"},"coordinates":[766990,621835]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"San Juan","adm0name":"Puerto Rico","iso_a2":"PR"},"coordinates":[316305,613964]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Stanley","adm0name":"Falkland Islands","iso_a2":"FK"},"coordinates":[339306,198423]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Hamilton","adm0name":"Bermuda","iso_a2":"BM"},"coordinates":[320044,696043]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Nukualofa","adm0name":"Tonga","iso_a2":"TO"},"coordinates":[13276,379483]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Hargeysa","adm0name":"Somaliland","iso_a2":"-99"},"coordinates":[622403,561355]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Victoria","adm0name":"Seychelles","iso_a2":"SC"},"coordinates":[654027,477366]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Sao Tome","adm0name":"Sao Tome and Principe","iso_a2":"ST"},"coordinates":[518703,506692]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Apia","adm0name":"Samoa","iso_a2":"WS"},"coordinates":[22948,422713]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Valletta","adm0name":"Malta","iso_a2":"MT"},"coordinates":[540318,717403]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Male","adm0name":"Maldives","iso_a2":"MV"},"coordinates":[704166,529403]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Jerusalem","adm0name":"Israel","adm1name":"Jerusalem","iso_a2":"IL"},"coordinates":[597795,692987]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Praia","adm0name":"Cape Verde","iso_a2":"CV"},"coordinates":[434676,593090]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Nassau","adm0name":"The Bahamas","iso_a2":"BS"},"coordinates":[285138,653322]},{"type":"Point","properties":{"scalerank":3,"labelrank":0,"name":"Nicosia","adm0name":"Cyprus","iso_a2":"CY"},"coordinates":[592684,713060]},{"type":"Point","properties":{"scalerank":3,"labelrank":8,"name":"Kaohsiung","adm0name":"Taiwan","adm1name":"Kaohsiung City","iso_a2":"TW"},"coordinates":[834073,638807]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Shenzhen","adm0name":"China","adm1name":"Guangdong","iso_a2":"CN"},"coordinates":[816999,638339]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Zibo","adm0name":"China","adm1name":"Shandong","iso_a2":"CN"},"coordinates":[827911,722749]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Minneapolis","adm0name":"United States of America","adm1name":"Minnesota","iso_a2":"US"},"coordinates":[240962,771210]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Honolulu","adm0name":"United States of America","adm1name":"Hawaii","iso_a2":"US"},"coordinates":[61500,630960]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Seattle","adm0name":"United States of America","adm1name":"Washington","iso_a2":"US"},"coordinates":[160161,786555]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Phoenix","adm0name":"United States of America","adm1name":"Arizona","iso_a2":"US"},"coordinates":[188689,703435]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"San Diego","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[174494,699169]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"St. Louis","adm0name":"United States of America","adm1name":"Missouri","iso_a2":"US"},"coordinates":[249328,733620]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"New Orleans","adm0name":"United States of America","adm1name":"Louisiana","iso_a2":"US"},"coordinates":[249883,682432]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Dallas","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[230995,699169]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Maracaibo","adm0name":"Venezuela","adm1name":"Zulia","iso_a2":"VE"},"coordinates":[300939,568298]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Boston","adm0name":"United States of America","adm1name":"Massachusetts","iso_a2":"US"},"coordinates":[302578,755511]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Tampa","adm0name":"United States of America","adm1name":"Florida","iso_a2":"US"},"coordinates":[270943,670299]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Philadelphia","adm0name":"United States of America","adm1name":"Pennsylvania","iso_a2":"US"},"coordinates":[291189,741707]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Detroit","adm0name":"United States of America","adm1name":"Michigan","iso_a2":"US"},"coordinates":[269217,755511]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Anchorage","adm0name":"United States of America","adm1name":"Alaska","iso_a2":"US"},"coordinates":[83611,867412]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Hanoi","adm0name":"Vietnam","adm1name":"Thái Nguyên","iso_a2":"VN"},"coordinates":[794022,629340]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Ho Chi Minh City","adm0name":"Vietnam","adm1name":"H? Chí Minh city","iso_a2":"VN"},"coordinates":[796369,568595]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Ankara","adm0name":"Turkey","adm1name":"Ankara","iso_a2":"TR"},"coordinates":[591284,741276]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Budapest","adm0name":"Hungary","adm1name":"Budapest","iso_a2":"HU"},"coordinates":[553003,786140]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Sanaa","adm0name":"Yemen","adm1name":"Amanat Al Asimah","iso_a2":"YE"},"coordinates":[622791,595697]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Barcelona","adm0name":"Spain","adm1name":"Cataluña","iso_a2":"ES"},"coordinates":[506059,749902]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Bucharest","adm0name":"Romania","adm1name":"Bucharest","iso_a2":"RO"},"coordinates":[572494,767972]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Aleppo","adm0name":"Syria","adm1name":"Aleppo (Halab)","iso_a2":"SY"},"coordinates":[603244,719372]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Damascus","adm0name":"Syria","adm1name":"Damascus","iso_a2":"SY"},"coordinates":[600827,703198]},{"type":"Point","properties":{"scalerank":2,"labelrank":7,"name":"Zürich","adm0name":"Switzerland","adm1name":"Zürich","iso_a2":"CH"},"coordinates":[523744,785429]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Lisbon","adm0name":"Portugal","adm1name":"Lisboa","iso_a2":"PT"},"coordinates":[474591,734139]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Khartoum","adm0name":"Sudan","adm1name":"Khartoum","iso_a2":"SD"},"coordinates":[590367,597079]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Jeddah","adm0name":"Saudi Arabia","adm1name":"Makkah","iso_a2":"SA"},"coordinates":[608936,632204]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Makkah","adm0name":"Saudi Arabia","adm1name":"Makkah","iso_a2":"SA"},"coordinates":[610605,631690]},{"type":"Point","properties":{"scalerank":2,"labelrank":7,"name":"Oslo","adm0name":"Norway","adm1name":"Oslo","iso_a2":"NO"},"coordinates":[529855,859702]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Lahore","adm0name":"Pakistan","adm1name":"Punjab","iso_a2":"PK"},"coordinates":[706522,691704]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Karachi","adm0name":"Pakistan","adm1name":"Sind","iso_a2":"PK"},"coordinates":[686077,652070]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Durban","adm0name":"South Africa","adm1name":"KwaZulu-Natal","iso_a2":"ZA"},"coordinates":[586050,327795]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"St. Petersburg","adm0name":"Russia","adm1name":"City of St. Petersburg","iso_a2":"RU"},"coordinates":[584205,859834]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Guadalajara","adm0name":"Mexico","adm1name":"Jalisco","iso_a2":"MX"},"coordinates":[212967,627187]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Puebla","adm0name":"Mexico","adm1name":"Puebla","iso_a2":"MX"},"coordinates":[227216,617589]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Kano","adm0name":"Nigeria","adm1name":"Kano","iso_a2":"NG"},"coordinates":[523661,575822]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Warsaw","adm0name":"Poland","adm1name":"Masovian","iso_a2":"PL"},"coordinates":[558328,814281]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Pyongyang","adm0name":"North Korea","adm1name":"P'yongyang","iso_a2":"KP"},"coordinates":[849312,735898]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Dar es Salaam","adm0name":"Tanzania","adm1name":"Dar-Es-Salaam","iso_a2":"TZ"},"coordinates":[609072,464442]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Medan","adm0name":"Indonesia","adm1name":"Sumatera Utara","iso_a2":"ID"},"coordinates":[774021,525938]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"Dublin","adm0name":"Ireland","adm1name":"Dublin","iso_a2":"IE"},"coordinates":[482636,820698]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"Monrovia","adm0name":"Liberia","adm1name":"Montserrado","iso_a2":"LR"},"coordinates":[470001,542128]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Naples","adm0name":"Italy","adm1name":"Campania","iso_a2":"IT"},"coordinates":[539564,746684]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Milan","adm0name":"Italy","adm1name":"Lombardia","iso_a2":"IT"},"coordinates":[525564,774114]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Kuala Lumpur","adm0name":"Malaysia","adm1name":"Selangor","iso_a2":"MY"},"coordinates":[782494,523489]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Lanzhou","adm0name":"China","adm1name":"Gansu","iso_a2":"CN"},"coordinates":[788304,718341]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Nanning","adm0name":"China","adm1name":"Guangxi","iso_a2":"CN"},"coordinates":[800883,639925]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Guiyang","adm0name":"China","adm1name":"Guizhou","iso_a2":"CN"},"coordinates":[796439,662201]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Chongqing","adm0name":"China","adm1name":"Chongqing","iso_a2":"CN"},"coordinates":[796091,679885]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Fuzhou","adm0name":"China","adm1name":"Fujian","iso_a2":"CN"},"coordinates":[831382,659239]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Guangzhou","adm0name":"China","adm1name":"Guangdong","iso_a2":"CN"},"coordinates":[814786,641850]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Dongguan","adm0name":"China","adm1name":"Guangdong","iso_a2":"CN"},"coordinates":[815951,641281]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Bandung","adm0name":"Indonesia","adm1name":"Jawa Barat","iso_a2":"ID"},"coordinates":[798799,463554]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Surabaya","adm0name":"Indonesia","adm1name":"Jawa Timur","iso_a2":"ID"},"coordinates":[813191,461781]},{"type":"Point","properties":{"scalerank":2,"labelrank":7,"name":"Guayaquil","adm0name":"Ecuador","adm1name":"Guayas","iso_a2":"EC"},"coordinates":[277994,491576]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Medellin","adm0name":"Colombia","adm1name":"Antioquia","iso_a2":"CO"},"coordinates":[290064,541905]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Cali","adm0name":"Colombia","adm1name":"Valle del Cauca","iso_a2":"CO"},"coordinates":[287494,524872]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Havana","adm0name":"Cuba","adm1name":"Ciudad de la Habana","iso_a2":"CU"},"coordinates":[271205,641773]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Alexandria","adm0name":"Egypt","adm1name":"Al Iskandariyah","iso_a2":"EG"},"coordinates":[583188,689572]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Frankfurt","adm0name":"Germany","adm1name":"Hessen","iso_a2":"DE"},"coordinates":[524097,801532]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Hamburg","adm0name":"Germany","adm1name":"Hamburg","iso_a2":"DE"},"coordinates":[527772,821983]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Munich","adm0name":"Germany","adm1name":"Bayern","iso_a2":"DE"},"coordinates":[532147,789872]},{"type":"Point","properties":{"scalerank":2,"labelrank":7,"name":"Prague","adm0name":"Czech Republic","adm1name":"Prague","iso_a2":"CZ"},"coordinates":[540177,801445]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"Kuwait","adm0name":"Kuwait","adm1name":"Al Kuwayt","iso_a2":"KW"},"coordinates":[633267,678728]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Xian","adm0name":"China","adm1name":"Shaanxi","iso_a2":"CN"},"coordinates":[802479,707789]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Taiyuan","adm0name":"China","adm1name":"Shanxi","iso_a2":"CN"},"coordinates":[812619,729117]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Wuhan","adm0name":"China","adm1name":"Hubei","iso_a2":"CN"},"coordinates":[817411,685898]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Changsha","adm0name":"China","adm1name":"Hunan","iso_a2":"CN"},"coordinates":[813800,671798]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Kunming","adm0name":"China","adm1name":"Yunnan","iso_a2":"CN"},"coordinates":[785216,653255]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Zhengzhou","adm0name":"China","adm1name":"Henan","iso_a2":"CN"},"coordinates":[815730,710633]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Shenyeng","adm0name":"China","adm1name":"Liaoning","iso_a2":"CN"},"coordinates":[842910,752400]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Jinan","adm0name":"China","adm1name":"Shandong","iso_a2":"CN"},"coordinates":[824980,722008]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Tianjin","adm0name":"China","adm1name":"Tianjin","iso_a2":"CN"},"coordinates":[825549,736553]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Nanchang","adm0name":"China","adm1name":"Jiangxi","iso_a2":"CN"},"coordinates":[821883,674642]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Nanjing","adm0name":"China","adm1name":"Jiangsu","iso_a2":"CN"},"coordinates":[829938,694608]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Hangzhou","adm0name":"China","adm1name":"Zhejiang","iso_a2":"CN"},"coordinates":[833799,683943]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Hiroshima","adm0name":"Japan","adm1name":"Hiroshima","iso_a2":"JP"},"coordinates":[867890,708458]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Changchun","adm0name":"China","adm1name":"Jilin","iso_a2":"CN"},"coordinates":[848161,764605]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Baotou","adm0name":"China","adm1name":"Nei Mongol","iso_a2":"CN"},"coordinates":[805055,745571]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Harbin","adm0name":"China","adm1name":"Heilongjiang","iso_a2":"CN"},"coordinates":[851800,775772]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Sapporo","adm0name":"Japan","adm1name":"Hokkaido","iso_a2":"JP"},"coordinates":[892605,759924]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"Santo Domingo","adm0name":"Dominican Republic","adm1name":"Distrito Nacional","iso_a2":"DO"},"coordinates":[305828,614153]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Accra","adm0name":"Ghana","adm1name":"Greater Accra","iso_a2":"GH"},"coordinates":[499392,537610]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Delhi","adm0name":"India","adm1name":"Delhi","iso_a2":"IN"},"coordinates":[714522,674583]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Hyderabad","adm0name":"India","adm1name":"Andhra Pradesh","iso_a2":"IN"},"coordinates":[717993,607814]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Pune","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[705133,614509]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Nagpur","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[719688,630149]},{"type":"Point","properties":{"scalerank":2,"labelrank":7,"name":"Tripoli","adm0name":"Libya","adm1name":"Tajura' wa an Nawahi al Arba","iso_a2":"LY"},"coordinates":[536611,699587]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"Tel Aviv-Yafo","adm0name":"Israel","adm1name":"Tel Aviv","iso_a2":"IL"},"coordinates":[596577,694785]},{"type":"Point","properties":{"scalerank":2,"labelrank":7,"name":"Helsinki","adm0name":"Finland","adm1name":"Southern Finland","iso_a2":"FI"},"coordinates":[569256,861236]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Mashhad","adm0name":"Iran","adm1name":"Razavi Khorasan","iso_a2":"IR"},"coordinates":[665466,719608]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Jaipur","adm0name":"India","adm1name":"Rajasthan","iso_a2":"IN"},"coordinates":[710577,664222]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Kanpur","adm0name":"India","adm1name":"Uttar Pradesh","iso_a2":"IN"},"coordinates":[723105,661490]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Patna","adm0name":"India","adm1name":"Bihar","iso_a2":"IN"},"coordinates":[736466,656543]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Chennai","adm0name":"India","adm1name":"Tamil Nadu","iso_a2":"IN"},"coordinates":[722994,582279]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Ahmedabad","adm0name":"India","adm1name":"Dadra and Nagar Haveli","iso_a2":"IN"},"coordinates":[701605,641169]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Surat","adm0name":"India","adm1name":"Dadra and Nagar Haveli","iso_a2":"IN"},"coordinates":[702327,630327]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"København","adm0name":"Denmark","adm1name":"Hovedstaden","iso_a2":"DK"},"coordinates":[534893,834594]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"Abidjan","adm0name":"Ivory Coast","adm1name":"Lagunes","iso_a2":"CI"},"coordinates":[488772,536247]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Belem","adm0name":"Brazil","adm1name":"Pará","iso_a2":"BR"},"coordinates":[365327,496138]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Brasilia","adm0name":"Brazil","adm1name":"Distrito Federal","iso_a2":"BR"},"coordinates":[366894,411221]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Porto Alegre","adm0name":"Brazil","adm1name":"Rio Grande do Sul","iso_a2":"BR"},"coordinates":[357772,326699]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Curitiba","adm0name":"Brazil","adm1name":"Paraná","iso_a2":"BR"},"coordinates":[362994,354129]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Fortaleza","adm0name":"Brazil","adm1name":"Ceará","iso_a2":"BR"},"coordinates":[392828,482512]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Salvador","adm0name":"Brazil","adm1name":"Bahia","iso_a2":"BR"},"coordinates":[393106,427888]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Edmonton","adm0name":"Canada","adm1name":"Alberta","iso_a2":"CA"},"coordinates":[184717,821983]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Montréal","adm0name":"Canada","adm1name":"Québec","iso_a2":"CA"},"coordinates":[295596,774292]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Goiania","adm0name":"Brazil","adm1name":"Goiás","iso_a2":"BR"},"coordinates":[363050,405672]},{"type":"Point","properties":{"scalerank":2,"labelrank":1,"name":"Recife","adm0name":"Brazil","adm1name":"Pernambuco","iso_a2":"BR"},"coordinates":[403006,456885]},{"type":"Point","properties":{"scalerank":2,"labelrank":8,"name":"Brussels","adm0name":"Belgium","adm1name":"Brussels","iso_a2":"BE"},"coordinates":[512031,805888]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Dhaka","adm0name":"Bangladesh","adm1name":"Dhaka","iso_a2":"BD"},"coordinates":[751129,645275]},{"type":"Point","properties":{"scalerank":2,"labelrank":6,"name":"Luanda","adm0name":"Angola","adm1name":"Luanda","iso_a2":"AO"},"coordinates":[536756,452367]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Algiers","adm0name":"Algeria","adm1name":"Alger","iso_a2":"DZ"},"coordinates":[508468,722530]},{"type":"Point","properties":{"scalerank":2,"labelrank":2,"name":"Chittagong","adm0name":"Bangladesh","adm1name":"Chittagong","iso_a2":"BD"},"coordinates":[754993,637022]},{"type":"Point","properties":{"scalerank":2,"labelrank":3,"name":"Perth","adm0name":"Australia","adm1name":"Western Australia","iso_a2":"AU"},"coordinates":[821772,315413]},{"type":"Point","properties":{"scalerank":2,"labelrank":5,"name":"Rangoon","adm0name":"Myanmar","adm1name":"Yangon","iso_a2":"MM"},"coordinates":[767123,604161]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"San Francisco","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[159952,728479]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Denver","adm0name":"United States of America","adm1name":"Colorado","iso_a2":"US"},"coordinates":[208372,740162]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Houston","adm0name":"United States of America","adm1name":"Texas","iso_a2":"US"},"coordinates":[235161,681396]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Miami","adm0name":"United States of America","adm1name":"Florida","iso_a2":"US"},"coordinates":[277150,657506]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Atlanta","adm0name":"United States of America","adm1name":"Georgia","iso_a2":"US"},"coordinates":[265550,705153]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Chicago","adm0name":"United States of America","adm1name":"Illinois","iso_a2":"US"},"coordinates":[256244,752549]},{"type":"Point","properties":{"scalerank":1,"labelrank":6,"name":"Caracas","adm0name":"Venezuela","adm1name":"Distrito Capital","iso_a2":"VE"},"coordinates":[314114,566941]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Kiev","adm0name":"Ukraine","adm1name":"Kiev","iso_a2":"UA"},"coordinates":[584762,803519]},{"type":"Point","properties":{"scalerank":1,"labelrank":8,"name":"Dubai","adm0name":"United Arab Emirates","adm1name":"Dubay","iso_a2":"AE"},"coordinates":[653549,654203]},{"type":"Point","properties":{"scalerank":1,"labelrank":6,"name":"Tashkent","adm0name":"Uzbekistan","adm1name":"Tashkent","iso_a2":"UZ"},"coordinates":[692480,749478]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Madrid","adm0name":"Spain","adm1name":"Comunidad de Madrid","iso_a2":"ES"},"coordinates":[489762,744077]},{"type":"Point","properties":{"scalerank":1,"labelrank":7,"name":"Geneva","adm0name":"Switzerland","adm1name":"Genève","iso_a2":"CH"},"coordinates":[517055,778486]},{"type":"Point","properties":{"scalerank":1,"labelrank":7,"name":"Stockholm","adm0name":"Sweden","adm1name":"Stockholm","iso_a2":"SE"},"coordinates":[550264,856349]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Bangkok","adm0name":"Thailand","adm1name":"Bangkok Metropolis","iso_a2":"TH"},"coordinates":[779207,586190]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Lima","adm0name":"Peru","adm1name":"Lima","iso_a2":"PE"},"coordinates":[285967,433351]},{"type":"Point","properties":{"scalerank":1,"labelrank":8,"name":"Dakar","adm0name":"Senegal","adm1name":"Dakar","iso_a2":"SM"},"coordinates":[451458,591912]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Johannesburg","adm0name":"South Africa","adm1name":"Gauteng","iso_a2":"ZA"},"coordinates":[577855,349685]},{"type":"Point","properties":{"scalerank":1,"labelrank":8,"name":"Amsterdam","adm0name":"Netherlands","adm1name":"Noord-Holland","iso_a2":"NL"},"coordinates":[513651,814873]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Casablanca","adm0name":"Morocco","adm1name":"Grand Casablanca","iso_a2":"MA"},"coordinates":[478837,703790]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Seoul","adm0name":"South Korea","adm1name":"Seoul","iso_a2":"KR"},"coordinates":[852771,727288]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Manila","adm0name":"Philippines","adm1name":"Metropolitan Manila","iso_a2":"PH"},"coordinates":[836056,591250]},{"type":"Point","properties":{"scalerank":1,"labelrank":2,"name":"Monterrey","adm0name":"Mexico","adm1name":"Nuevo León","iso_a2":"MX"},"coordinates":[221300,656809]},{"type":"Point","properties":{"scalerank":1,"labelrank":8,"name":"Auckland","adm0name":"New Zealand","adm1name":"Auckland","iso_a2":"NZ"},"coordinates":[985452,286413]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Berlin","adm0name":"Germany","adm1name":"Berlin","iso_a2":"DE"},"coordinates":[537221,815891]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Urumqi","adm0name":"China","adm1name":"Xinjiang Uygur","iso_a2":"CN"},"coordinates":[743258,764249]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Chengdu","adm0name":"China","adm1name":"Sichuan","iso_a2":"CN"},"coordinates":[789078,686432]},{"type":"Point","properties":{"scalerank":1,"labelrank":2,"name":"Osaka","adm0name":"Japan","adm1name":"Osaka","iso_a2":"JP"},"coordinates":[876272,710604]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Kinshasa","adm0name":"Congo (Kinshasa)","adm1name":"Kinshasa City","iso_a2":"CD"},"coordinates":[542536,479077]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"New Delhi","adm0name":"India","adm1name":"Delhi","iso_a2":"IN"},"coordinates":[714444,674156]},{"type":"Point","properties":{"scalerank":1,"labelrank":1,"name":"Bangalore","adm0name":"India","adm1name":"Karnataka","iso_a2":"IN"},"coordinates":[715438,581569]},{"type":"Point","properties":{"scalerank":1,"labelrank":6,"name":"Athens","adm0name":"Greece","adm1name":"Attiki","iso_a2":"GR"},"coordinates":[565920,729759]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Baghdad","adm0name":"Iraq","adm1name":"Baghdad","iso_a2":"IQ"},"coordinates":[623310,702242]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Addis Ababa","adm0name":"Ethiopia","adm1name":"Addis Ababa","iso_a2":"ET"},"coordinates":[607494,558246]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Tehran","adm0name":"Iran","adm1name":"Tehran","iso_a2":"IR"},"coordinates":[642839,716065]},{"type":"Point","properties":{"scalerank":1,"labelrank":2,"name":"Vancouver","adm0name":"Canada","adm1name":"British Columbia","iso_a2":"CA"},"coordinates":[157990,796647]},{"type":"Point","properties":{"scalerank":1,"labelrank":2,"name":"Toronto","adm0name":"Canada","adm1name":"Ontario","iso_a2":"CA"},"coordinates":[279383,763627]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Buenos Aires","adm0name":"Argentina","adm1name":"Ciudad de Buenos Aires","iso_a2":"AR"},"coordinates":[337779,299727]},{"type":"Point","properties":{"scalerank":1,"labelrank":5,"name":"Kabul","adm0name":"Afghanistan","adm1name":"Kabul","iso_a2":"AF"},"coordinates":[692169,709221]},{"type":"Point","properties":{"scalerank":1,"labelrank":7,"name":"Vienna","adm0name":"Austria","adm1name":"Wien","iso_a2":"AT"},"coordinates":[545457,790287]},{"type":"Point","properties":{"scalerank":1,"labelrank":3,"name":"Melbourne","adm0name":"Australia","adm1name":"Victoria","iso_a2":"AU"},"coordinates":[902702,280666]},{"type":"Point","properties":{"scalerank":1,"labelrank":8,"name":"Taipei","adm0name":"Taiwan","adm1name":"Taipei City","iso_a2":"TW"},"coordinates":[837688,653040]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Los Angeles","adm0name":"United States of America","adm1name":"California","iso_a2":"US"},"coordinates":[171717,706100]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Washington, D.C.","adm0name":"United States of America","adm1name":"District of Columbia","iso_a2":"US"},"coordinates":[286079,735187]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"New York","adm0name":"United States of America","adm1name":"New York","iso_a2":"US"},"coordinates":[294495,746150]},{"type":"Point","properties":{"scalerank":0,"labelrank":5,"name":"London","adm0name":"United Kingdom","adm1name":"Westminster","iso_a2":"GB"},"coordinates":[499670,809838]},{"type":"Point","properties":{"scalerank":0,"labelrank":5,"name":"Istanbul","adm0name":"Turkey","adm1name":"Istanbul","iso_a2":"TR"},"coordinates":[580577,748253]},{"type":"Point","properties":{"scalerank":0,"labelrank":5,"name":"Riyadh","adm0name":"Saudi Arabia","adm1name":"Ar Riyad","iso_a2":"SA"},"coordinates":[629918,650712]},{"type":"Point","properties":{"scalerank":0,"labelrank":3,"name":"Cape Town","adm0name":"South Africa","adm1name":"Western Cape","iso_a2":"ZA"},"coordinates":[551202,303771]},{"type":"Point","properties":{"scalerank":0,"labelrank":2,"name":"Moscow","adm0name":"Russia","adm1name":"Moskva","iso_a2":"RU"},"coordinates":[604482,835030]},{"type":"Point","properties":{"scalerank":0,"labelrank":2,"name":"Mexico City","adm0name":"Mexico","adm1name":"Distrito Federal","iso_a2":"MX"},"coordinates":[224631,619915]},{"type":"Point","properties":{"scalerank":0,"labelrank":2,"name":"Lagos","adm0name":"Nigeria","adm1name":"Lagos","iso_a2":"NG"},"coordinates":[509415,542902]},{"type":"Point","properties":{"scalerank":0,"labelrank":3,"name":"Rome","adm0name":"Italy","adm1name":"Lazio","iso_a2":"IT"},"coordinates":[534670,752939]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Beijing","adm0name":"China","adm1name":"Beijing","iso_a2":"CN"},"coordinates":[823294,741286]},{"type":"Point","properties":{"scalerank":0,"labelrank":5,"name":"Nairobi","adm0name":"Kenya","adm1name":"Nairobi","iso_a2":"KE"},"coordinates":[602262,497125]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Jakarta","adm0name":"Indonesia","adm1name":"Jakarta Raya","iso_a2":"ID"},"coordinates":[796742,468148]},{"type":"Point","properties":{"scalerank":0,"labelrank":5,"name":"Bogota","adm0name":"Colombia","adm1name":"Bogota","iso_a2":"CO"},"coordinates":[294207,531960]},{"type":"Point","properties":{"scalerank":0,"labelrank":3,"name":"Cairo","adm0name":"Egypt","adm1name":"Al Qahirah","iso_a2":"EG"},"coordinates":[586799,682758]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Shanghai","adm0name":"China","adm1name":"Shanghai","iso_a2":"CN"},"coordinates":[837317,689669]},{"type":"Point","properties":{"scalerank":0,"labelrank":2,"name":"Tokyo","adm0name":"Japan","adm1name":"Tokyo","iso_a2":"JP"},"coordinates":[888192,716142]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Mumbai","adm0name":"India","adm1name":"Maharashtra","iso_a2":"IN"},"coordinates":[702374,617394]},{"type":"Point","properties":{"scalerank":0,"labelrank":3,"name":"Paris","adm0name":"France","adm1name":"Île-de-France","iso_a2":"FR"},"coordinates":[506476,794237]},{"type":"Point","properties":{"scalerank":0,"labelrank":3,"name":"Santiago","adm0name":"Chile","adm1name":"Región Metropolitana de Santiago","iso_a2":"CL"},"coordinates":[303697,306556]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Kolkata","adm0name":"India","adm1name":"West Bengal","iso_a2":"IN"},"coordinates":[745340,637999]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Rio de Janeiro","adm0name":"Brazil","adm1name":"Rio de Janeiro","iso_a2":"BR"},"coordinates":[379925,368910]},{"type":"Point","properties":{"scalerank":0,"labelrank":1,"name":"Sao Paulo","adm0name":"Brazil","adm1name":"São Paulo","iso_a2":"BR"},"coordinates":[370480,365156]},{"type":"Point","properties":{"scalerank":0,"labelrank":3,"name":"Sydney","adm0name":"Australia","adm1name":"New South Wales","iso_a2":"AU"},"coordinates":[919952,303771]},{"type":"Point","properties":{"scalerank":0,"labelrank":0,"name":"Singapore","adm0name":"Singapore","iso_a2":"SG"},"coordinates":[788482,512389]},{"type":"Point","properties":{"scalerank":0,"labelrank":0,"name":"Hong Kong","adm0name":"Hong Kong S.A.R.","iso_a2":"HK"},"coordinates":[817174,636873]}]}},"arcs":[[[85470,47819],[-4327,823],[2549,633],[1778,-1456]],[[135926,62848],[-3045,936],[3281,69],[-236,-1005]],[[54258,21344],[-9164,857],[3638,778],[5526,-1635]],[[965673,41772],[-3773,-978],[-481,1285],[4254,-307]],[[964121,46628],[6302,-1202],[-5445,-735],[-1844,-1198],[-1422,1933],[2409,1202]],[[405738,34787],[-7075,156],[2628,1209],[4447,-1365]],[[313161,34004],[-3575,90],[1434,1245],[2141,-1335]],[[334072,28722],[-2358,-3579],[-10237,1183],[-4755,2157],[12074,239],[-141,1922],[5030,1437],[387,-3359]],[[316184,30299],[-4542,3151],[4985,-464],[1416,-1955],[-1859,-732]],[[58185,31902],[-3469,-220],[-10900,3103],[279,1928],[2416,1619],[4480,-1060],[5441,-2972],[1753,-2398]],[[374381,37806],[4168,-45],[2102,-3902],[-1562,-4233],[-15721,-2674],[-1627,-838],[-12193,-508],[-513,1781],[2648,2728],[3047,-191],[5438,3920],[-1094,1165],[1427,4014],[3162,3306],[6265,1552],[3599,-570],[4781,-2400],[-384,-1842],[-3543,-1263]],[[304627,32657],[-4026,1396],[3443,3321],[8515,3087],[2085,-125],[-7008,-4897],[-3009,-2782]],[[146206,62619],[-2132,1749],[2510,-580],[-378,-1169]],[[149084,70536],[2927,-2396],[3427,-1125],[571,-2044],[-2879,102],[-6552,2931],[-99,2426],[2605,106]],[[442757,66979],[329,-3590],[-2221,2981],[1967,2192],[-75,-1583]],[[165121,67753],[789,-1381],[-2196,-2064],[-4990,-31],[652,2234],[-1354,1679],[7099,-437]],[[175726,65330],[-1773,486],[2764,1288],[-991,-1774]],[[167919,65653],[-704,1752],[2341,30],[-1637,-1782]],[[227524,78675],[1407,176],[346,-1810],[1641,1998],[2068,-264],[-1873,-2157],[3305,1133],[94,-2024],[-1323,-990],[-6544,175],[-4965,1629],[-5749,813],[380,889],[5676,880],[698,-1240],[3382,1673],[1457,-881]],[[293460,71648],[-612,-3038],[-3684,1650],[-324,1466],[1780,1570],[2508,-435],[332,-1213]],[[246776,71151],[-1415,3309],[2396,79],[-981,-3388]],[[492964,85456],[1224,-315],[-1920,-2052],[-2094,2867],[2790,-500]],[[295259,86241],[-1384,-1711],[-5476,-1234],[-900,1132],[5678,2100],[2082,-287]],[[331597,134082],[2154,-324],[-1032,-773],[-1122,1097]],[[346762,142020],[-615,875],[1989,-263],[-1374,-612]],[[338949,137923],[941,-643],[-3790,-1122],[890,1194],[1959,571]],[[396935,184408],[2158,-1100],[1466,-3035],[-797,-612],[-1169,2107],[-2754,2105],[1096,535]],[[301693,184601],[1024,-467],[-337,-1554],[-917,846],[-1320,-412],[-728,1500],[594,965],[1684,-878]],[[302801,179654],[1594,-243],[38,-1520],[-1488,614],[-144,1149]],[[306380,179352],[2229,-586],[1717,-1406],[651,-2298],[-3469,2828],[-319,-1650],[-1578,1759],[769,1353]],[[313667,177961],[-2751,-400],[-642,1425],[2933,19],[460,-1044]],[[320696,180552],[2039,-51],[-2284,-1052],[245,1103]],[[341609,129267],[-3451,-2313],[-2179,-2791],[726,-1717],[-1874,1058],[-1395,-508],[-3047,-3257],[-1787,-37],[349,-1175],[-1577,-865],[1081,-1407],[-1525,-1607],[1294,-1653],[2573,1177],[965,-347],[-1286,-2116],[-1121,1137],[-1232,-897],[-2240,348],[-63,-2623],[-1536,2467],[-1375,-83],[-903,-2230],[897,-1298],[-2587,397],[-947,-2416],[-1156,-666],[-545,-4764],[2065,-225],[-1544,-998],[584,-1459],[3231,-1115],[783,1786],[946,-3389],[2793,-3213],[1360,-3174],[-1158,-1450],[2425,-745],[-1267,-2429],[1800,194],[457,-2810],[-2251,-1870],[2070,555],[496,-1517],[-3393,-1171],[4270,-326],[-31,-2093],[1690,-5031],[-2665,-315],[-2575,1070],[1032,-2087],[2106,-663],[-67,-1532],[-2598,-957],[2929,-1237],[-631,-1407],[-3401,220],[853,-2498],[-2168,746],[-3378,-1584],[1927,-885],[-2913,-825],[2545,-940],[-8353,-3329],[-8150,-1998],[-2197,-1800],[-4731,-581],[-9638,1015],[-5338,-287],[2615,-3822],[2394,-1180],[7041,-689],[-1110,-1801],[-4335,-1679],[-8139,1406],[-7943,1116],[-2331,-791],[11134,-3252],[-1212,-1842],[-6731,-459],[-6434,2433],[-3207,-290],[1397,-1855],[4442,-1929],[2136,-2381],[1120,1072],[11082,-29],[1299,-1786],[-1462,-1638],[-8618,-553],[4123,-1006],[4913,426],[3252,-4193],[6131,-680],[5588,1767],[2163,-1407],[15247,-3939],[-3239,-2255],[2769,-2775],[11279,1090],[-5213,-2019],[4265,-3381],[-1082,-1441],[5760,-694],[6007,3662],[9354,3791],[15626,1826],[5841,-325],[229,-3344],[6962,1430],[6210,5761],[7394,2462],[4340,-1077],[5229,2449],[16661,2835],[14135,653],[-165,1725],[-15693,1016],[-1018,2583],[-7440,-388],[-9014,2692],[162,1813],[3037,3740],[2810,2439],[5591,1573],[8693,4678],[7985,2447],[4971,1126],[7877,497],[2629,1133],[6063,360],[-1235,1121],[4029,5380],[4517,-435],[3053,2783],[-3264,-47],[-2139,1786],[2563,3243],[3542,-156],[65,2310],[3025,-308],[4754,2204],[1497,3028],[-3305,1707],[-565,1328],[1912,346],[1926,-1356],[2601,2544],[-182,1210],[2412,-1467],[1623,-2955],[2591,748],[-445,3592],[5382,1348],[968,-853],[-1472,-2780],[9182,30],[2215,-667],[2630,994],[1458,-2647],[3828,3022],[4929,1792],[6954,1448],[6356,954],[766,818],[2351,-695],[1718,1719],[5727,-3230],[1383,-224],[3790,4224],[2103,-1715],[5512,114],[2161,712],[345,-1147],[3932,-847],[855,1483],[2122,-19],[191,-3609],[5015,349],[1762,3465],[1836,-1282],[-367,-993],[2070,-993],[2291,2403],[1200,-261],[1447,-2627],[4839,-755],[6702,2586],[3033,1676],[3821,440],[3451,1334],[1023,2230],[-1172,3259],[1539,2280],[2976,-77],[-1054,-2352],[2173,28],[2114,-3476],[3783,173],[1100,-939],[2860,-80],[1986,-1077],[2349,3438],[441,2716],[3525,2323],[3545,1323],[1141,1354],[5222,1297],[805,800],[3916,899],[446,2070],[2342,-835],[-794,-970],[4253,-1312],[-49,1621],[1636,1741],[-2095,1085],[2172,605],[3627,-1499],[-612,4443],[4311,2516],[4965,954],[3544,-340],[2118,-970],[4098,-3159],[-2888,-76],[682,-2060],[-657,-1724],[2047,1235],[2044,250],[3083,-1276],[1426,-1514],[3420,591],[6127,-1555],[2809,826],[12858,-2259],[3024,869],[1571,-4273],[-1244,-1616],[265,-2930],[-2009,-838],[489,-2924],[-2512,172],[-2528,-2582],[1869,-887],[3000,580],[637,-627],[-1048,-3579],[-2321,-2110],[-1681,-3625],[-2397,-7146],[2091,-539],[1804,1271],[1245,3381],[3153,833],[4783,4447],[1745,5434],[2376,1842],[172,1775],[3111,2091],[4116,-889],[1298,1881],[5351,3002],[2524,4686],[1560,940],[7581,2544],[3576,515],[3280,2894],[3405,-277],[6304,2209],[4773,-206],[3675,1306],[2922,562],[5252,-1077],[2432,1115],[1948,-767],[4332,778],[1719,-638],[1495,828],[1670,-1205],[5640,1853],[1626,2411],[3332,509],[3547,-728],[6766,-2503],[2178,-355],[1706,-1146],[4661,-1449],[3220,2279],[790,2650],[6090,1640],[1697,-769],[1742,-2551],[2704,-1189],[902,-1247],[-1004,-1521],[-3563,-1089],[99,-1358],[7463,2333],[6254,-577],[3917,954],[-3040,-1277],[-410,-1015],[6541,1657],[1931,1371],[5592,1533],[2498,-239],[2139,1638],[2221,-789],[2433,-3279],[2472,-402],[2240,459],[1400,3394],[3363,1643],[2442,-264],[4482,916],[2040,-1159],[307,-1183],[2951,2071],[3338,-1847],[933,588],[3474,-1210],[3060,-179],[1829,-837],[5758,-542],[1985,-1221],[2897,807],[502,-1269],[1973,-300],[-1887,-3867],[686,-625],[2608,1622],[2354,11],[2368,-2017],[738,-2395],[3786,-581],[7255,486],[195,-2248],[3909,205],[1488,-753],[1732,758],[222,2246],[1153,-404],[3668,-3594],[5447,-1684],[1254,758],[5142,-2021],[1183,-2684],[2298,-2028],[1076,-3019],[2395,-842],[-686,2039],[1765,1897],[1872,-1873],[2929,653],[6177,-911],[2618,-864],[1674,-2210],[5528,-2650],[759,1254],[1177,-2665],[-1764,-470],[-490,-3867],[-3692,1281],[3083,-2039],[-788,-1906],[-6637,-574],[1278,-1122],[-1721,-1230],[-3155,-287],[-1426,-1698],[-604,2942],[-1055,-1043],[72,-2782],[1465,-3349],[-4355,178],[-1305,813],[-1837,-2351],[-196,-2037],[-4508,-993],[2758,-413],[2533,-2617],[-146,-5329],[2379,-4958],[2227,-1783],[-1232,-2017],[1807,-543],[2192,1620],[683,-1562],[3875,-1260],[-6732,-503],[-5542,-1744],[-2450,2089],[539,-2873],[-2113,303],[-3595,-5214],[1840,-898],[-5515,-2446],[5469,-9],[-201,-5425],[6531,-3114],[2386,-1902],[-6658,-1793],[9622,802],[10723,-4212],[-1382,-1756],[7520,-437],[3056,-1235],[21397,-3698],[-975899,-1624],[13312,-1639],[11050,-486],[17983,-1689],[-1464,2223],[-18283,1673],[-3840,4281],[-7229,-28],[-4485,2139],[-13607,3256],[6236,312],[9455,-2260],[6113,167],[4921,-1581],[13891,-447],[5600,1164],[-697,1285],[6167,882],[6804,3147],[-4743,3015],[2114,1425],[-8545,2257],[1401,929],[23349,1550],[-6814,3241],[986,1206],[5287,469],[391,1749],[-6429,1364],[-4446,1801],[-8662,1639],[-3498,1952],[6044,2229],[-8259,352],[-3428,2496],[798,3681],[5561,304],[6393,-717],[1248,-1120],[4837,-59],[5559,-2202],[4339,1985],[-1156,2117],[8042,-2268],[-205,4378],[-4416,1840],[-3502,-326],[-2925,758],[3706,1547],[6763,-1896],[-1382,1923],[7801,3176],[3457,432],[4044,-1512],[-969,1186],[4255,1972],[5758,814],[2970,-378],[892,1797],[2406,863],[5241,-957],[3961,511],[6273,-746],[5355,1020],[6971,29],[4067,-346],[11702,700],[2836,1553],[9862,-371],[874,2764],[3562,-593],[-1672,-5291],[6706,1123],[-305,3096],[1738,484],[2426,-1059],[19,-2037],[-3230,-2505],[6086,-305],[7369,-943],[4771,1357],[8870,-72],[1859,-1745],[6893,1374],[-5321,1880],[986,2118],[-3148,174],[-1288,2736],[-2921,829],[990,1585],[3959,-834],[5803,865],[-3124,1236],[-7374,484],[-1943,2973],[2810,349],[-127,-1346],[12213,-273],[5167,-1634],[2544,527],[2751,-550],[5570,797],[3667,-834],[1737,2023],[2651,523],[594,1164],[2591,-981],[-625,-2195],[2829,220],[1008,-959],[3435,959],[2279,-1836],[7771,-2103],[2430,703],[129,2508],[2572,-588],[-294,2783],[2557,-861],[3094,-2761],[4326,565],[-551,-2273],[4877,1217],[3603,-362],[2964,1491],[11411,2029],[3206,1606],[2331,4409],[-1757,3338],[-123,2779],[-1066,3768],[-1431,2382],[-846,3480],[3712,118],[1102,1489],[-1154,1777],[1659,3678],[-991,1278],[1236,2948],[-2172,-119],[-18,2574],[1495,758],[1342,-1529],[-152,2974],[1887,538],[639,3049],[5054,3881],[-667,1785],[772,862],[1948,-628],[-169,1166],[4133,2002],[2133,3147],[3761,1497],[1772,1593],[4118,1929],[1025,-868]],[[291702,91617],[1526,-1313],[-1142,-1361],[-2438,493],[126,1270],[1928,911]],[[574603,87746],[-2366,49],[821,1781],[1545,-1830]],[[305413,94809],[1762,-2334],[3003,-7541],[263,-5724],[-2689,-4219],[-3706,-771],[-5067,-32],[-1997,1551],[1300,788],[4849,-542],[-2847,1455],[2991,1270],[-3995,1571],[-1857,-1661],[-1959,514],[-1008,-1985],[-3770,1728],[168,1561],[2502,-139],[568,1513],[5488,284],[-2358,1232],[3728,-107],[1456,949],[3788,421],[-3386,898],[-32,1334],[2036,1042],[1971,-218],[-1970,1411],[-2202,-217],[-2037,1346],[308,3237],[-875,2499],[4558,1257],[1016,-2371]],[[545062,89959],[-1454,1389],[2573,136],[-1119,-1525]],[[300040,91791],[-2198,317],[141,1374],[2057,-1691]],[[327784,91653],[-1583,3208],[2088,-1160],[-505,-2048]],[[311143,104968],[-2536,-1599],[-503,2021],[2074,3560],[1404,1054],[-383,-2416],[779,-682],[-835,-1938]],[[780503,115614],[-1975,438],[1703,1334],[272,-1772]],[[324498,122769],[956,-605],[-2508,-1556],[-1480,811],[2185,2590],[847,-1240]],[[326873,123038],[-1170,-280],[1161,2714],[751,-739],[-742,-1695]],[[339316,125233],[1657,-257],[-125,-1598],[-1602,-201],[-1462,1576],[1311,1947],[221,-1467]],[[345755,130448],[-2362,-1562],[-19,1204],[2381,358]],[[10618,245580],[-434,-1826],[-1428,1329],[1862,497]],[[23739,421499],[-1273,266],[-371,856],[1226,-132],[418,-990]],[[21295,424942],[302,-2008],[-863,75],[-674,1628],[1235,305]],[[330522,564757],[-2484,-386],[1130,1182],[96,1602],[-474,950],[1994,835],[-322,-1010],[60,-3173]],[[849139,563455],[-543,1210],[478,1904],[65,-3114]],[[789066,566277],[-128,-2142],[-468,2025],[596,117]],[[322640,570662],[-189,-1442],[-1347,557],[1536,885]],[[756952,567249],[-417,1163],[437,867],[-20,-2030]],[[846040,571919],[987,176],[231,-3479],[667,-3091],[-738,614],[108,-1981],[-683,799],[17,3634],[-948,842],[-319,3624],[678,-1138]],[[840266,573530],[1840,-471],[-394,-2483],[-685,-1734],[-1590,-1190],[-581,-975],[-68,2449],[388,4569],[-389,1507],[1479,-1672]],[[847887,578938],[822,-1994],[-83,-3834],[551,-2248],[-1307,-116],[-879,2448],[-91,1286],[-1388,2776],[-251,1928],[2626,-246]],[[273324,548160],[-691,714],[342,994],[349,-1708]],[[465204,548773],[-1181,797],[934,393],[247,-1190]],[[825863,554717],[108,1623],[1486,3123],[639,628],[2525,5965],[731,1306],[-72,1607],[667,2969],[71,-2329],[438,-2379],[-1110,-1777],[-260,-1130],[-1136,-858],[-965,-3911],[-1237,-2245],[-1885,-2592]],[[846093,562701],[-650,-930],[-1178,-37],[-329,1146],[988,1883],[1123,-643],[46,-1419]],[[842029,558417],[-1578,2481],[-421,1250],[167,1586],[1068,743],[-108,2469],[462,2268],[760,636],[863,-1263],[-1126,-5430],[406,-3005],[-493,-1735]],[[842694,560701],[45,3066],[903,3001],[909,4739],[35,-4076],[-275,-1594],[-858,-1756],[-759,-3380]],[[65314,628731],[1381,-1039]],[[66695,627692],[-1247,-825],[-134,1864]],[[63295,630407],[1393,-358]],[[64688,630049],[-410,-585]],[[64278,629464],[-983,943]],[[297147,630270],[-382,-1263],[-1379,-247],[383,1501],[1378,9]],[[61668,631836],[456,-883]],[[62124,630953],[-1320,65]],[[60804,631018],[-453,1580]],[[60351,632598],[863,688]],[[61214,633286],[454,-1450]],[[67829,617353],[-833,346],[-465,4026],[635,1565]],[[67166,623290],[-33,1550]],[[67133,624840],[1759,-1667],[1095,-2784]],[[69987,620389],[-1404,-1566]],[[68583,618823],[-754,-1470]],[[663116,624502],[-224,703],[675,2033],[185,-974],[-636,-1762]],[[833610,576804],[595,-920],[-843,-25],[248,945]],[[329773,595059],[-554,1636],[447,355],[107,-1991]],[[434877,593631],[-948,408],[71,1386],[877,-1794]],[[328918,599549],[-472,327],[31,1737],[544,-500],[-103,-1564]],[[436338,600914],[626,-404],[-355,-1084],[-271,1488]],[[329646,600873],[-541,-14],[141,1651],[400,-1637]],[[430083,605116],[-464,856],[993,22],[-529,-878]],[[433089,603196],[-652,-766],[-151,1094],[803,-328]],[[331038,590589],[-1093,1836],[836,-409],[257,-1427]],[[902059,583271],[-257,1004],[809,839],[-552,-1843]],[[843655,577513],[901,-1899],[14,-1270],[-1429,2631],[-1037,-1605],[218,3897],[1333,-1754]],[[839149,577913],[-474,-140],[616,1904],[-142,-1764]],[[649342,579583],[1178,161],[899,-659],[-1060,-1138],[-1475,-109],[-785,1131],[609,1082],[634,-468]],[[757562,573062],[-524,1998],[447,2022],[451,6775],[570,1110],[32,-1738],[-521,-1836],[288,-2392],[-743,-5939]],[[835289,584576],[1384,-280],[889,-1784],[50,-2921],[-845,-2484],[-874,1735],[-439,2714],[-1007,3242],[842,-222]],[[838651,584936],[555,-457],[-304,-1530],[-488,733],[237,1254]],[[845425,585481],[-874,183],[517,2455],[534,-1224],[-177,-1414]],[[836392,615002],[2067,-1895],[836,1133],[426,-497],[-414,-3828],[328,-2141],[695,-1602],[-1067,-5569],[-1499,-1490],[32,-1561],[-596,-2046],[842,-3478],[-130,-1516],[422,-2178],[1142,-1088],[-33,1291],[808,1032],[1015,-424],[1043,-2982],[247,1862],[1492,-1553],[-855,-911],[748,-2230],[-95,-941],[994,-443],[-230,-2777],[-507,727],[198,1343],[-1771,756],[-411,2356],[-1578,2760],[-353,-124],[575,-3753],[-1673,3171],[-819,884],[-1595,-1762],[-1008,1448],[-567,-475],[-55,2272],[847,1808],[-94,1319],[-846,980],[15,-2358],[-417,-177],[-991,2357],[-529,5845],[-340,1011],[170,1885],[915,-1652],[638,1030],[-234,1823],[287,2526],[-139,4044],[669,5152],[1395,636]],[[285385,614067],[2532,-1810],[386,-1412],[-872,-280],[-912,637],[-978,-1534],[-1564,963],[-848,1859],[-613,159],[215,1363],[954,439],[1700,-384]],[[300613,621536],[615,1050],[1677,110],[2291,-1646],[479,212],[604,-2208],[1151,169],[-829,-1243],[2608,-1262],[960,-1738],[-1061,-2329],[-594,1123],[-2322,211],[-1145,-1136],[-1283,500],[-1064,-373],[-1022,-3715],[-1035,2328]],[[300643,611589],[-810,1122],[-2268,-455],[-1413,589],[-1388,-1240],[-1484,1803],[532,1875],[1767,-831],[2228,-519],[1227,1423],[-1287,2350],[299,2189],[-1925,1289],[774,1452],[1335,-17],[1159,-926],[1224,-157]],[[316307,613993],[1390,-376],[23,-824],[-973,-1588],[-3405,118],[106,2992],[2859,-322]],[[808024,623158],[347,-1993],[-533,-578],[-694,-2287],[-336,-2512],[-2588,-3138],[-2272,1878],[-183,2207],[161,2551],[1619,2505],[107,849],[3787,1378],[585,-860]],[[782522,517031],[-293,-2149],[-423,2086],[716,63]],[[856816,516873],[-656,1455],[1067,1778],[59,-2210],[-470,-1023]],[[767954,518699],[-1820,1751],[-7,1543],[1698,-2375],[129,-919]],[[852267,528613],[-164,3048],[455,-1522],[-291,-1526]],[[524265,526983],[585,-775],[-673,-2393],[-637,243],[725,2925]],[[800877,526576],[-599,88],[-269,2003],[551,936],[531,-1270],[-214,-1757]],[[722171,562852],[753,-97],[1273,-2547],[1838,-5539],[175,-1852],[1217,-4921],[-35,-2293],[-622,-2820],[-715,-1092],[-1822,-1551],[-1270,182],[-477,849],[-655,4006],[-421,7325],[280,-93],[712,6195],[58,2856],[-289,1392]],[[839146,542801],[-790,1099],[694,752],[638,-603],[-542,-1248]],[[836553,540712],[645,-436],[-1280,-627],[635,1063]],[[760805,545187],[-572,2089],[486,138],[86,-2227]],[[850016,559939],[520,-263],[351,-2559],[-495,-1288],[621,-850],[195,-3857],[375,-921],[32,-2545],[-1083,-2341],[-7,-3217],[-1014,6065],[-1176,-3185],[520,-1955],[222,-2886],[-1056,-2052],[-54,2375],[-647,-963],[-2285,2149],[-375,1014],[-257,3491],[615,2386],[-662,1589],[-1321,849],[-283,-2372],[-818,1735],[-704,-1014],[-143,1144],[-816,-294],[-894,-3961],[-589,-213],[466,4990],[570,1291],[1832,1138],[59,1054],[1158,1806],[1152,-1603],[-267,-2218],[1235,1015],[704,2232],[778,-257],[381,2425],[757,-613],[191,938],[803,-73],[-236,3877],[297,533],[1348,-2596]],[[293373,191181],[2597,-1316],[-2276,373],[-321,943]],[[297435,187868],[1994,-1930],[-993,-1578],[-1099,46],[541,1191],[-1503,-469],[-26,1273],[-1476,1087],[1107,804],[1455,-424]],[[309361,192779],[809,-1466],[-416,-2138],[909,-269],[424,-1527],[1984,-2877],[2941,-2866],[2935,-857],[-464,-1184],[-3236,-913],[-1165,634],[-3583,637],[-1203,-214]],[[309296,179739],[-2314,-31],[-659,870],[-2149,-578],[-3916,1864],[3356,866],[-469,2753],[934,1521],[421,-2130],[-694,-110],[1280,-2214],[1186,435],[1358,-1491],[580,893],[-2622,1763],[-446,2062],[2212,1665],[-790,864],[-1260,-497],[-1027,1255],[2672,4235],[920,-1043],[1492,88]],[[291370,215386],[-1109,-2334],[-314,2042],[1423,292]],[[292868,216836],[-988,-204],[-764,3862],[1030,736],[722,-4394]],[[692179,213769],[973,802],[366,-1722],[1815,1223],[654,-847],[-471,-1377],[-1307,505],[-376,-838],[1464,-553],[-645,-741],[-2561,1059],[-1028,-720],[-37,3468],[799,2426],[354,-2685]],[[290249,215820],[-391,1050],[251,3053],[470,303],[647,-3572],[-977,-834]],[[291514,206740],[-1095,-280],[340,1964],[1252,-581],[-497,-1103]],[[293121,213543],[-329,-5088],[-1015,2806],[-294,-1892],[-1344,362],[1288,3087],[-272,1106],[1085,2241],[630,-293],[251,-2329]],[[293575,234020],[-1047,171],[1054,2902],[-7,-3073]],[[295179,241703],[-688,-593],[711,-3699],[-1033,-1222],[-1441,4013],[1446,1532],[547,1508],[458,-1539]],[[297260,239419],[-1010,-314],[-264,1138],[659,1814],[1232,-1269],[-617,-1369]],[[967067,227084],[268,-1231],[-1997,-1118],[728,3310],[1001,-961]],[[911110,269175],[825,-1352],[-531,-1637],[-941,2321],[647,668]],[[899799,267051],[58,3154],[534,-2192],[-592,-962]],[[882212,292985],[1250,-98],[-1663,-1893],[-1925,247],[-323,1684],[1931,926],[730,-866]],[[980875,260159],[1729,1721],[1569,-181],[-648,-2429],[594,-1843],[-2050,-4606],[-900,-2717],[-1351,-2240],[919,-3080],[-2461,-127],[-2048,-1421],[-856,-4987],[-1205,-4186],[211,-1106],[-992,-415],[-2036,-3618],[-1633,-468],[-1990,150],[-536,1439],[-1408,1004],[-2641,-29],[-607,2288],[1326,1658],[-712,-70],[810,2487],[3725,6172],[1222,534],[5375,6307],[1419,3113],[650,3597],[1456,2073],[358,2948],[1392,2542],[967,-1955],[352,-2555]],[[295074,247915],[-1706,677],[494,2092],[480,6418],[1414,-598],[158,-3377],[-884,-707],[980,-2078],[-936,-2427]],[[902895,263078],[668,100],[2874,-2332],[1867,1014],[1292,-55],[1427,1315],[901,-992],[24,-6475],[-244,415],[-804,-3569],[157,-3464],[-542,-373],[-590,2218],[-628,-479],[-1315,-4065],[-904,615],[-1403,-227],[-136,1013],[-1408,2663],[-802,4122],[882,-732],[-1669,4208],[-748,3930],[201,1828],[900,-678]],[[981302,297748],[1597,-542],[1323,-1306],[723,-3193],[-526,70],[1142,-3174],[-222,-3150],[1603,-901],[675,-1233],[-227,4300],[1154,-2856],[449,-3809],[2034,-1712],[1572,-600],[1870,2583],[1464,-813],[-746,-5090],[-810,-1013],[-187,-3065],[-1392,939],[-1260,-1697],[433,-1811],[-744,-2871],[-2387,-6253],[-1869,-2354],[-401,1145],[-1473,758],[1465,3956],[254,1969],[-680,1997],[-2986,2625],[-356,2012],[1644,1226],[554,1052],[327,3314],[591,2495],[-950,4187],[-1103,3587],[591,-649],[-25,2144],[-1320,1314],[77,-934],[-2301,5750],[205,1120],[-1346,3324],[716,-474],[848,-2367]],[[989070,274777],[-712,800],[-39,-1605],[751,805]],[[814396,350367],[-607,1599],[50,1558],[557,-3157]],[[925214,352158],[-109,3303],[566,1604],[301,-833],[-758,-4074]],[[970372,392507],[-965,408],[435,1423],[530,-1831]],[[956117,384770],[648,-214],[2100,-2884],[1308,-2951],[1780,-2193],[1775,-2683],[-468,-1694],[-1659,1700],[-224,785],[-2372,2554],[-874,1396],[-2105,4796],[91,1388]],[[654992,378293],[-1207,389],[-143,2190],[974,-13],[376,-2566]],[[965001,379352],[-1022,1410],[476,1475],[546,-2885]],[[660142,383355],[-745,-111],[7,1630],[752,1414],[377,-1319],[-391,-1614]],[[995222,401798],[865,-1656],[211,-2544],[-1411,-1003],[-868,-28],[-1461,1051],[-159,1268],[983,2383],[1840,529]],[[85218,399912],[-715,-265],[-90,1205],[805,-940]],[[967903,400788],[220,-1515],[-1018,519],[798,996]],[[967490,407932],[-1022,639],[653,871],[369,-1510]],[[999997,408927],[-1056,-2128],[-142,-1301],[1007,1350],[-8,-1333],[-1412,-367],[-603,556],[-1377,-1561],[-581,1115],[2929,3187],[1243,482]],[[967907,405311],[-737,-148],[49,1245],[688,-1097]],[[887521,406531],[-968,-999],[370,1625],[598,-626]],[[292158,198837],[809,-2144],[-889,-1651],[-704,2853],[784,942]],[[336527,200971],[2427,-678],[384,-1926],[-2348,-1344],[85,-965],[-1508,483],[-555,-1720],[-487,2267],[1420,1421],[582,2462]],[[332538,199833],[2828,204],[-1815,-3211],[-2125,-1297],[-764,777],[2009,1690],[-917,2454],[784,-617]],[[937461,461289],[-1069,40],[-1789,3604],[61,629],[1731,-2063],[1066,-2210]],[[816234,462622],[-1038,-670],[-1196,39],[-873,824],[395,1024],[3074,159],[-362,-1376]],[[609712,468136],[213,-1261],[-916,666],[-142,2038],[324,1239],[521,-2682]],[[873713,466550],[-503,-2207],[-824,269],[302,3514],[1025,-1576]],[[359296,503937],[-758,-1160],[-191,1316],[949,-156]],[[840680,473499],[-1007,-298],[240,3272],[925,881],[161,-1868],[-319,-1987]],[[856320,482947],[-826,-571],[106,1008],[720,-437]],[[852391,486423],[1017,-1796],[7,-1434],[-1509,-1129],[-1412,1447],[-421,2423],[2318,489]],[[860429,487739],[1734,-732],[1336,-3442],[-152,-1703],[-2182,2269],[-489,875],[-1044,-747],[-1990,904],[-923,-684],[-798,1532],[-587,-2065],[-118,1682],[890,2109],[3414,447],[909,-445]],[[924903,476538],[-792,1528],[-227,2983],[-1129,2898],[-3795,4698],[-2,829],[3762,-4961],[2325,-4120],[322,-1463],[-464,-2392]],[[823064,481800],[-583,-1101],[-209,2098],[275,2135],[424,524],[93,-3656]],[[933215,465101],[-662,-1042],[-1043,836],[-394,2453],[-1167,1996],[-146,3118],[1012,-1043],[1036,-3108],[989,-1396],[375,-1814]],[[874296,470906],[25,-2747],[-874,-973],[-800,1869],[1286,3441],[363,-1590]],[[929575,472530],[-298,1907],[520,-634],[-222,-1273]],[[911181,470199],[-679,1180],[820,-136],[-141,-1044]],[[869239,469751],[523,3503],[14,-1573],[-537,-1930]],[[610736,475651],[-34,-2071],[-573,-669],[74,2616],[533,124]],[[839007,472500],[-651,1077],[437,1070],[214,-2147]],[[921986,479261],[561,501],[801,-761],[-16,-2314],[-395,-1325],[-679,-290],[360,-2092],[-771,-1232],[-973,73],[-794,-2176],[-2224,-2112],[-2155,-83],[-751,1258],[-710,-295],[-2015,2149],[-157,1304],[1817,357],[683,-523],[1994,742],[229,2446],[170,-2335],[535,-632],[1817,662],[1038,2747],[957,456],[-353,3461],[1031,14]],[[842164,477754],[-552,-3760],[615,-520],[-1083,-2356],[-631,750],[504,1982],[501,4547],[646,-643]],[[917878,488947],[-752,10],[-567,1104],[738,531],[581,-1645]],[[277412,487103],[-256,1303],[874,163],[-618,-1466]],[[908520,493104],[924,-385],[-538,-929],[-1833,-158],[305,1390],[1142,82]],[[778958,485860],[-614,1162],[115,1206],[499,-2368]],[[800575,486958],[-639,-1329],[-331,803],[-677,-729],[143,3810],[1135,-182],[600,-1380],[-231,-993]],[[778344,488478],[-525,-466],[-9,1833],[534,-1367]],[[777341,490836],[-656,507],[221,1150],[435,-1657]],[[850152,490195],[-534,2216],[361,387],[173,-2603]],[[876315,495287],[2021,-375],[1920,-857],[-1845,-557],[-2058,1335],[-38,454]],[[862092,494702],[182,-1665],[-475,-452],[-1418,1072],[1711,1045]],[[850066,494114],[854,-196],[-2497,-684],[245,814],[1398,66]],[[847137,494613],[974,-620],[-2507,-1155],[-103,1882],[1636,-107]],[[855980,494879],[-1643,-402],[-471,495],[707,1851],[1407,-1944]],[[794571,494827],[889,-4713],[1257,-643],[-574,-1908],[153,-1045],[-1856,1463],[-591,3813],[-1814,822],[1257,3056],[901,129],[378,-974]],[[846913,510614],[-1282,-3109],[-1872,-978],[-1356,125],[-508,943],[-3446,-291],[-1156,345],[-1147,-315],[-868,432],[-976,-388],[-616,-1674],[-317,-2150],[234,-2688],[1167,-2307],[416,-1959],[1018,-216],[1349,3264],[1251,-460],[862,1044],[1691,11],[785,1093],[579,-461],[-5,-2107],[-913,781],[-672,-556],[-836,-2261],[-2188,-3051],[-1029,-493],[1389,-2284],[1528,-5150],[-404,-2487],[1734,-2894],[56,-1422],[-2176,-1132],[-113,-1490],[-1347,190],[-283,1057],[365,2893],[-1956,3181],[390,2304],[-178,2943],[-935,15],[-1110,-2282],[507,-3877],[-206,-2242],[160,-3149],[-391,-3133],[419,-2636],[-1329,80],[-651,-686],[-948,1591],[655,5931],[33,2307],[-566,3311],[-1181,-368],[-506,2257],[-80,2320],[857,1671],[638,3278],[-36,3089],[552,2971],[566,1339],[369,-1073],[-340,4582],[929,4627],[738,1722],[541,-982],[1098,2793],[1467,-441],[421,-868],[2347,-295],[1265,-996],[1071,462],[1581,-532],[1186,1090],[1988,4022],[679,-1179],[-958,-3002]],[[842256,497778],[70,-1292],[859,308],[-860,-1420],[-241,1615],[-723,-1675],[49,2398],[846,66]],[[246466,504866],[1312,-4604],[-294,-1117],[-1294,-453],[-241,1287],[937,1426],[-690,1611],[270,1850]],[[775454,494184],[-804,677],[-757,2759],[742,1672],[1106,-4219],[-287,-889]],[[804750,497722],[-785,-359],[134,1516],[651,-1157]],[[876063,500858],[1418,-441],[1338,-2182],[-736,-728],[-754,578],[-1266,2773]],[[863893,496923],[-610,356],[-303,1752],[1113,-50],[-200,-2058]],[[356020,496222],[796,4646],[811,642],[259,-743],[-321,-2103],[-1545,-2442]],[[362142,503359],[1422,389],[1469,-403],[579,-718],[-445,-2655],[-1081,-4037],[-677,412],[-170,-1107],[-758,522],[-829,-1652],[-1952,14],[-765,4607],[383,4370],[1103,926],[1721,-668]],[[249070,500146],[-577,563],[758,1137],[-181,-1700]],[[854352,502828],[105,-1727],[765,-1175],[-1161,15],[-381,2786],[672,101]],[[790205,502734],[322,-781],[-630,-1138],[-295,1157],[603,762]],[[863369,504693],[1288,-863],[-54,-1280],[-700,30],[-1065,1628],[185,-1249],[-1255,516],[161,640],[1440,578]],[[943750,449782],[612,-952],[1066,69],[1290,-2614],[-2681,423],[-644,1535],[357,1539]],[[918134,449344],[722,-423],[293,-1478],[-1271,254],[256,1647]],[[917625,448471],[-654,782],[584,579],[70,-1361]],[[919668,445353],[599,376],[-182,-1410],[-755,605],[-508,2272],[846,-1843]],[[833367,449176],[774,-1617],[591,-155],[913,-2154],[-1093,-1519],[-817,556],[-1511,2527],[-1433,394],[-351,1111],[630,800],[2297,57]],[[923974,451638],[933,-1480],[-685,253],[-248,1227]],[[946525,455461],[690,-1766],[-148,-1107],[595,-965],[453,-3719],[-1245,2564],[-770,4909],[425,84]],[[847805,449005],[-589,-957]],[[847216,448048],[-1585,-3456],[-1583,-1156],[-593,193],[-152,2040],[333,2085],[744,1433]],[[844380,449187],[613,692]],[[844993,449879],[1049,597]],[[846042,450476],[873,1108]],[[846915,451584],[381,648]],[[847296,452232],[420,1251],[1740,923],[2265,193],[963,852],[915,-645],[-1058,-1722],[-1479,-1436],[-2707,-1885],[-550,-758]],[[828450,455439],[2071,-118],[394,-1958],[-1809,-1116],[-277,1078],[-524,-983],[-3135,-1532],[-759,549],[130,2808],[916,979],[1117,-351],[664,-1687],[1191,707],[-1167,1480],[-165,1141],[1006,161],[347,-1158]],[[824001,453685],[-732,-1867],[-1396,611],[561,479],[-44,1822],[947,1382],[924,-1083],[-260,-1344]],[[937191,453094],[-491,880],[464,854],[27,-1734]],[[841063,453697],[-1913,-788],[-1230,-912],[-658,496],[-1054,-714],[-1346,792],[-1782,-330],[25,2443],[1923,1213],[2317,-1999],[1450,726],[823,-1005],[1988,2802],[76,-1050],[-619,-1674]],[[820688,456402],[712,-1494],[-1029,-1234],[-271,-1078],[-1095,2187],[-636,296],[-383,1535],[2702,-212]],[[844234,455707],[-1032,-1744],[-897,215],[1516,2015],[413,-486]],[[845239,455369],[-613,-1316],[-262,1170],[875,146]],[[846042,456487],[1526,-379],[-78,-877],[-1812,-544],[364,1800]],[[938231,455887],[155,-2192],[-726,2027],[-904,-266],[558,1956],[917,-1525]],[[944108,454156],[-2598,2924],[-1351,2939],[769,-353],[1042,-1774],[2038,-2508],[100,-1228]],[[885819,455018],[-923,472],[648,802],[275,-1274]],[[935243,457777],[-352,2068],[689,-882],[-337,-1186]],[[852224,459289],[-914,-1674],[-1010,394],[-861,-596],[492,1903],[661,-257],[1100,799],[532,-569]],[[884820,455700],[-665,-779],[-1690,-39],[-7,886],[895,3677],[800,1203],[1317,285],[610,-1811],[-564,-2151],[-696,-1271]],[[798260,469125],[814,-1233],[1745,-292],[1063,-3113],[4857,-929],[863,2814],[653,217],[506,-1382],[1072,123],[1520,-1452],[1255,-197],[709,-2239],[0,-1469],[1261,-982],[2284,505],[1038,-1556],[-159,-3019],[546,-2159],[-3695,2861],[-1596,-726],[-3247,617],[-2508,922],[-1579,1534],[-2103,1100],[-1501,224],[-804,-770],[-1484,432],[-1757,1495],[-2305,611],[178,1865],[-2877,1613],[843,1923],[226,2018],[574,1198],[2084,-1092],[614,1151],[910,-613]],[[864792,457323],[-664,798],[481,2337],[1065,2120],[-51,-3042],[-831,-2213]],[[890964,489271],[993,-25]],[[891957,489246],[3074,-2798],[1926,-1404],[1677,-655],[1409,-2089],[1283,-246],[1695,-3103],[685,-214],[1201,-2594],[-60,-3432],[1828,-1269],[1753,-1793],[951,-187],[1181,-2160],[121,-2056],[-2018,-351],[-440,-1227],[637,-2662],[1484,-2952],[1118,-1346],[334,-2670],[567,-832],[367,-2116],[1846,-114],[-124,-1990],[758,-1074],[1382,-431],[-589,-858],[313,-1227],[2203,-1447],[-614,-297],[558,-1248],[-909,-811],[-842,459],[-729,1329],[-3808,993],[-1707,683],[-1002,2343],[-1087,1699],[-147,1945],[-743,202],[-1842,5623],[-846,734],[-2097,891],[-304,1009],[-985,381],[-790,-1170],[-909,539],[123,-1601],[-1178,-336],[265,-1183],[-1441,-656],[-1074,231],[1120,-1198],[780,-1939],[-72,-943],[-1998,-2173],[-1160,934],[-3045,-303]],[[892036,450086],[-580,807]],[[891456,450893],[-2749,5829],[-1526,-521],[-1471,261],[645,3305],[-945,1989],[835,302],[-1245,1717],[734,310],[-1183,3050],[-396,2337],[-639,1618],[-15,1249],[-2698,3204],[-1307,626],[-1776,1705],[-2178,475],[-1226,1513],[-132,1425],[-1223,53],[-812,758],[-891,2687],[-1124,-4135],[-778,-193],[-596,2317],[322,905],[-329,1518],[-2167,2999],[721,641],[1373,-645],[1293,2081],[1161,-646],[822,925],[48,1712],[-2665,-1011],[-1819,180],[-789,1492],[-259,2552],[-1768,985],[-827,-187],[726,3374],[1519,898],[901,1479],[1379,565],[2355,-2176],[1394,-108],[757,-3354],[-393,-2432],[139,-2809],[845,-3775],[465,1751],[208,-2351],[392,145],[538,-2513],[1249,-70],[2102,4515],[407,1835],[1259,448],[910,1020],[-131,1094],[1895,2119],[2344,-1824],[3166,-3302],[2314,-577],[347,-956]],[[897718,433893],[-190,-2068],[809,-1993],[514,-4761],[-106,-1763],[577,-3600],[571,-676],[1420,1368],[486,-1543],[1777,-2670],[-45,-3161],[518,-3435],[-89,-2071],[1322,-3935],[622,-3347],[-260,-3778],[836,-1664],[-101,-1703],[512,-1408],[2604,-1773],[1381,-2909],[1902,-1635],[790,-1990],[-559,-587],[1448,-3229],[692,-2687],[394,-4022],[1052,-1736],[282,2288],[1291,-2338],[620,-101],[220,-5225],[1827,-3284],[1116,-1117],[630,-2350],[907,-1214],[551,-2367],[720,-1363],[18,-1520],[680,-1632],[-224,-2013],[91,-5276],[1274,-6198],[80,-3637],[-712,-2583],[-211,-3567],[-672,-3974],[-240,-5163],[-1068,-3619],[-247,-2331],[-1826,-2737],[-1448,-4028],[-32,-2048],[-889,-2194],[-750,-5218],[-341,-216],[-1034,-3669],[-653,-5995],[-76,-4047],[-1762,-1621],[-2878,-169],[-1071,-613],[-1337,-1688],[-1496,-2633],[-1568,-215],[484,-833],[-185,-1808],[-671,1658],[-2115,1957],[-290,1764],[-925,-1559],[445,2426],[-636,1134],[-1376,-1404],[748,-433],[-1565,-1495],[-1563,-2124],[-2575,2187],[-2463,1068],[-836,-546],[-1148,1698],[-1066,288],[-2342,4636],[204,3458],[-859,3350],[-1609,3057],[622,1383],[-1864,-1749],[-937,176],[907,3486],[-59,1545],[-1113,3518],[-1104,-5766],[-2245,-573],[363,1919],[1047,15],[285,4456],[1217,3448],[-221,2242],[390,631],[-560,1605],[-969,-2194],[-569,-2582],[-2241,-2373],[-1500,-3738],[300,-1676],[-975,25],[-1158,2132],[609,-8],[-735,3995],[-824,1661],[-271,1766],[-1361,967],[-558,2467],[372,1186],[-1897,2166],[-942,-5],[-1263,1348],[-1508,-302],[-1371,1842],[-1604,1188],[-1002,-641],[-1814,147],[-3288,-732],[-2440,-2155],[-2078,-1171],[-3896,-195],[-3219,-3470],[-1756,-1461],[-1322,-4189],[-1229,-900],[-1195,578],[-1741,-599],[-249,696],[-1823,282],[-2740,-808],[-1568,-68],[-1121,-2332],[-1542,-661],[-2111,-3003],[-1537,-658],[-2959,651],[-1472,1143],[-724,1593],[-1994,1601],[-40,4387],[521,-759],[927,664],[466,2005],[43,8877],[-1449,5252],[-507,3507],[-98,4636],[-919,3329],[-252,1948],[-1035,2739],[-380,4344],[-885,2960],[-1046,2550],[156,1847],[535,-2681],[753,1339],[-733,1383],[-538,2283],[883,-696],[1048,-3335],[348,617],[-4,2595],[-1510,5181],[-703,3207],[376,4164],[567,1864],[105,2338],[-311,2286],[765,4139],[459,654],[50,-3877],[655,839],[626,2366],[712,1222],[1659,1447],[1541,2733],[1933,2231],[1943,-400],[2202,2049],[1534,671],[981,1581],[1337,-255],[1695,763],[1896,1448],[837,1109],[871,2201],[945,3729],[1465,2606],[-344,406],[-214,3880],[755,2034],[801,1082],[695,2079],[476,-2525],[1169,-3898],[153,3037],[445,832],[-800,2235],[664,1766],[361,-1124],[752,613],[902,-334],[340,1312],[-542,2106],[161,1568],[863,1234],[868,-930],[-622,1668],[650,761],[785,-519],[-491,2400],[1114,1372],[863,-798],[815,3649],[1071,-942],[927,2470],[2137,-2672],[1464,-3298],[-361,-3423],[514,184],[-221,1513],[840,1511],[1613,-571],[438,-1634],[143,1711],[1021,-1590],[6,1711],[588,130],[-417,1503],[-889,1084],[920,2443],[359,2412],[1169,1604],[-255,2042],[1262,3119],[681,-752],[18,1130],[1160,1774],[407,-1239],[2264,538],[439,-646],[608,1540],[123,2288],[-553,933],[-949,-55],[-894,1359],[1253,399],[1166,-1786],[951,312],[445,-1498],[1997,-748],[924,-1041],[1371,137],[1355,-1405],[1957,2345],[-611,-1929],[653,-4],[897,-1668],[26,1789],[751,1031],[1131,-2324],[-1140,-2574],[-212,-2612],[-463,518],[-1019,-986],[173,-2997],[-295,-2031],[-1328,-3585],[348,-1436],[1874,-2387],[239,-987],[1148,-683],[2775,-3245],[1504,-2875],[2125,-1072],[662,-2544],[2188,-2215],[1320,462],[886,1245],[377,2369],[703,2183],[536,3416],[110,2750],[483,3251],[-286,3475],[200,1879],[-339,2105],[481,3189],[-89,1871],[852,832],[-644,2678],[730,2694],[603,5627],[800,1417],[1057,-3552],[99,-3048],[851,-789]],[[858420,409078],[-1074,-97],[-319,-2064],[567,-1663],[189,2004],[637,1820]],[[637606,431064],[1109,-3794],[656,-5734],[171,-4098],[687,-3872],[-760,-3406],[-879,2979],[-674,-648],[174,-3021],[329,-1060],[-177,-3313],[-633,-1291],[-284,-1859],[113,-3269],[-2419,-15161],[-712,-5281],[-1229,-6617],[-973,-8346],[-1057,-5407],[-1247,-2148],[-1583,-477],[-1807,-1972],[-1092,119],[-839,1238],[-1298,640],[-862,1365],[-966,3779],[-115,3649],[211,1258],[-901,3811],[-365,4959],[654,4105],[829,1050],[308,1857],[912,2880],[459,2711],[122,2923],[-583,2094],[-16,1982],[-536,2678],[-169,5314],[1228,4081],[152,2877],[1203,253],[716,1135],[1198,-57],[283,1058],[1754,594],[1529,2868],[418,-1144],[167,1615],[963,2647],[237,-1712],[797,2650],[-106,1037],[617,2426],[123,2158],[599,-730],[1503,2678],[316,1964],[-344,2755],[1169,2318],[920,-2088]],[[965034,409358],[1178,-2096],[-1076,-624],[-694,3969],[592,-1249]],[[963182,416876],[179,-1958],[737,1314],[344,-3259],[-1226,-862],[-641,4627],[607,138]],[[623545,433141],[79,-1630],[-791,1097],[712,533]],[[879762,422936],[499,-2898],[-1552,482],[248,2055],[805,361]],[[620738,434208],[-665,886],[462,2033],[203,-2919]],[[946044,434821],[-1591,1293],[-9,638],[1600,-1931]],[[862386,435524],[408,-814],[-1319,-47],[60,2056],[517,832],[334,-2027]],[[862828,437320],[1124,247],[681,856],[751,-1463],[-222,-895],[-1411,-2006],[-1219,1829],[-398,2387],[694,-955]],[[926487,436727],[-435,-494],[-488,1395],[923,-901]],[[841524,440086],[-287,873],[1460,1700],[130,-1045],[-1303,-1528]],[[949208,443178],[1084,-394],[743,-2189],[-694,-7],[-1626,1529],[-647,2145],[1140,-1084]],[[362655,504051],[-1073,110],[1194,895],[-121,-1005]],[[791051,503675],[-922,-79],[267,1226],[655,-1147]],[[361838,506306],[-280,-1548],[-1390,216],[185,1116],[1485,216]],[[518498,505431],[-531,633],[607,1049],[-76,-1682]],[[359927,505541],[-549,-502],[758,3125],[-209,-2623]],[[770781,513396],[1251,-2909],[-154,-2047],[-534,-192],[-1680,4913],[1117,235]],[[786187,509140],[-1594,847],[265,1426],[1329,-2273]],[[786573,509872],[-1291,1088],[245,662],[1046,-1750]],[[790514,511922],[-28,-2276],[-593,2071],[621,205]],[[784519,510583],[-552,2119],[596,-673],[-44,-1446]],[[784699,513362],[21,-760],[-1169,993],[-101,752],[1249,-985]],[[768033,535698],[974,267],[1957,-406],[1002,-1931],[945,-2757],[164,-1906],[3958,-5390],[2014,-5485],[1195,-1831],[-164,1744],[605,87],[1196,-3342],[856,-425],[1034,-2148],[867,-2841],[1056,-378],[602,-1324],[-1434,-1633],[2019,1648],[562,-85],[855,-2567],[-995,-1414],[7,-2025],[805,-2092],[1777,-899],[430,-4627],[916,-1621],[-491,-1733],[187,-1098],[806,1264],[1545,-797],[1284,-3639],[-397,-1800],[81,-2506],[-277,-1954],[156,-5016],[-197,-3951],[-549,-729],[-748,1481],[-744,-1161],[-1228,1334],[-105,-2276],[-2140,4887],[-2534,3608],[-1060,1887],[-1138,3276],[-1525,2560],[-1278,3432],[-751,2629],[20,1242],[-1025,3763],[-495,2800],[-1245,3038],[-729,2466],[-1219,1477],[-1006,6771],[-645,2414],[-2399,2703],[-305,2893],[-554,762],[-1174,3554],[-1456,1429],[-2639,5599],[-801,3096],[527,2043],[1237,-678],[811,-1304],[997,-385]],[[854812,509742],[412,-95],[768,2870],[1475,1516],[39,-2761],[-1121,-1360],[-106,-849],[974,-1088],[801,-1977],[-2546,1514],[-267,-1027],[251,-3239],[767,-2863],[-576,151],[-985,2750],[48,3140],[-426,1194],[145,2124],[-521,2392],[588,3506],[1124,2105],[-416,-2169],[347,-2969],[-997,-1883],[222,-982]],[[826676,529600],[-104,-224]],[[826572,529376],[-280,-510],[866,-2292],[-1682,-298],[502,-2638],[716,-766],[1268,-4423],[-771,-1724],[809,-1926],[1551,-2268],[961,-1995],[-1250,-999],[-940,360],[-792,1329],[33,-1584],[-494,-602],[-619,-2925],[-165,-3316],[277,-2649],[-895,-918],[-2680,-5089],[413,0],[292,-4300],[-491,-65],[-265,-3584],[-836,-2775],[-3508,-3405],[-466,4698],[-221,-623],[-1011,1202],[-795,-1050],[-516,1543],[-742,-301],[-859,1855],[-174,-1503],[-1030,-1264],[-876,471],[-1286,-1253],[2,2816],[-1264,732],[-1216,-814],[-989,1064],[-754,-556],[-633,6154],[-319,496],[165,2749],[-644,2296],[-1434,1654],[-415,2767],[377,1755],[-869,1922],[-108,2597],[472,4157],[841,2529],[696,622]],[[804524,516729],[987,-1836],[1014,12],[2081,-1888],[471,4377],[-72,1754],[559,-322],[0,1497],[790,1301],[2804,1284],[854,797],[1115,3173],[1328,2978],[360,2071]],[[816815,531927],[345,-11]],[[817160,531916],[1018,792],[1253,1764],[87,-727]],[[819518,533745],[315,0]],[[819833,533745],[652,196],[575,1549],[-452,1297],[513,1127],[536,-398],[949,3515],[990,2323],[708,2699],[454,-1881],[598,1832],[459,-1730],[978,-1204],[-79,-3157],[1074,667],[310,-1131],[1331,-1602],[1746,-1063],[-253,-1849],[-1278,-809],[-1143,147],[-209,-950],[1046,-1933],[-178,-828],[-1360,-665],[-742,518],[-382,-815]],[[190458,968527],[-4765,717],[7618,2063],[2826,-2488],[-5679,-292]],[[232764,969972],[3660,-1102],[-556,-2089],[-5284,-1107]],[[230584,965674],[-3516,3693]],[[227068,969367],[120,2224]],[[227188,971591],[5576,-1619]],[[785790,974253],[-1312,-2479],[3501,1864],[4092,-1962],[463,-1890],[-4426,-1432],[-6987,-393],[-3116,-1285],[-2208,374],[4412,4417],[890,2493],[3018,1337],[1673,-1044]],[[771316,979611],[-62,-2356],[2624,1727],[4069,-1630],[-1727,-5585],[-5233,-46],[-7679,1540],[-1590,1871],[-3189,551],[5324,3564],[7463,364]],[[215066,972034],[2424,1181],[5817,-2936],[-394,-1660]],[[222913,968619],[1625,-2642]],[[224538,965977],[-3944,205]],[[220594,966182],[-1356,1790],[-4603,1051]],[[214635,969023],[-4425,-602],[-1865,1475]],[[208345,969896],[3420,6]],[[211765,969902],[-1076,2786]],[[210689,972688],[-1849,-1082]],[[208840,971606],[-1995,1335],[411,1725]],[[207256,974666],[6871,-549],[939,-2083]],[[244762,985385],[3451,-3194]],[[248213,982191],[8245,-1313]],[[256458,980878],[-687,-1627]],[[255771,979251],[2624,-1204],[-882,-1861]],[[257513,976186],[4576,185]],[[262089,976371],[995,-2388]],[[263084,973983],[-6467,-3153]],[[256617,970830],[-3122,-546]],[[253495,970284],[-137,-2320]],[[253358,967964],[-3461,2456]],[[249897,970420],[1472,-2391]],[[251369,968029],[-6645,199]],[[244724,968228],[-6288,4486]],[[238436,972714],[7831,2173]],[[246267,974887],[-4106,527]],[[242161,975414],[-6338,-948]],[[235823,974466],[-4639,5012]],[[231184,979478],[5909,-516],[-4783,1448]],[[232310,980410],[2276,435]],[[234586,980845],[-1622,1924],[6125,-783]],[[239089,981986],[-4409,1652]],[[234680,983638],[680,964],[7938,1644]],[[243298,986246],[1464,-861]],[[451076,977642],[-3846,679],[4238,522],[-392,-1201]],[[225579,978561],[-137,-1445],[-3477,1075]],[[221965,978191],[1004,1336],[2610,-966]],[[757453,976808],[-4327,1302],[834,855],[6603,-857],[-3110,-1300]],[[658551,980752],[4,-1518],[-3767,60],[3763,1458]],[[375376,991018],[-5243,1567],[980,2038],[4581,-1724],[-318,-1881]],[[558049,980153],[3866,-1188],[5578,1843],[7120,-1187],[938,-1501],[-4326,-2983],[-4704,-1237],[-3006,1289],[-5568,-83],[-3296,1145],[3082,933],[-5720,72],[-1304,998],[3022,1109],[753,2051],[3565,-1261]],[[639661,984167],[3960,-1420],[-7799,-1886],[226,-1224],[-3736,-300],[-2745,1116],[10094,3714]],[[768129,985046],[3624,-1644],[-1824,-3301],[-9173,-1369],[-6524,2065],[4830,2564],[-548,1168],[7598,1730],[2017,-1213]],[[660989,979403],[-2448,2197],[3904,-173],[-1456,-2024]],[[631783,983731],[-3613,-2411],[-3435,975],[7048,1436]],[[672688,983619],[-3102,-2466],[-4852,610],[799,1748],[7155,108]],[[651996,985285],[8265,-1918],[-5505,-918],[-4572,1045],[1812,1791]],[[676038,982821],[-2371,721],[6338,2224],[1765,-1579],[-5732,-1366]],[[662839,984844],[660,-1553],[-4580,1408],[3920,145]],[[660583,987833],[-998,-2432],[-4817,313],[5815,2119]],[[57298,634654],[-1158,649],[1215,1053]],[[57355,636356],[-57,-1702]],[[270661,632517],[-809,-757],[-637,2059],[1022,586],[424,-1888]],[[272673,641945],[1831,-612],[1468,257],[2704,-2133],[1113,-1987],[1637,-242],[2916,-3373],[388,440],[2360,-3479],[2921,-1717],[-130,-1547],[2112,-491],[1026,-1577],[960,-547],[-237,-1259],[-2883,-1105],[-2411,572],[-4324,-795],[449,1343],[1249,1927],[-349,1400],[-2132,424],[-1371,2005],[-405,2736],[-518,612],[-1017,-391],[-2003,1124],[-1636,1901],[-1285,-63],[-2374,873],[-726,1111],[891,468],[-407,1258],[-2318,61],[-1782,-2763],[-1448,-313],[-362,-1345],[-1310,-989],[490,1766],[-97,1805],[879,1701],[2186,1786],[3212,1321],[733,-163]],[[241809,926906],[-91,-2428]],[[241718,924478],[2771,-3358]],[[244489,921120],[20,-1462]],[[244509,919658],[-2531,-2195]],[[241978,917463],[2711,-811],[3769,-159]],[[248458,916493],[-1896,-1297]],[[246562,915196],[2137,-2499]],[[248699,912697],[-293,-2305]],[[248406,910392],[1025,-1287],[1495,4485]],[[250926,913590],[1694,1491],[2820,-2692],[523,-3228],[-1372,126],[420,-3095]],[[255011,906192],[2581,-3447]],[[257592,902745],[2028,1968]],[[259620,904713],[1623,3296]],[[261243,908009],[1207,4132]],[[262450,912141],[1821,1802],[-1457,935]],[[262814,914878],[-79,3659],[3260,-87]],[[265995,918450],[1601,-800]],[[267596,917650],[3587,-343],[-1058,-874]],[[270125,916433],[3962,-2218]],[[274087,914215],[-1748,-1400]],[[272339,912815],[1879,-1341]],[[274218,911474],[-3531,-1249]],[[270687,910225],[1600,-3463]],[[272287,906762],[1962,-2382]],[[274249,904380],[-548,-2311],[-3261,-2857]],[[270440,899212],[-2449,-1296]],[[267991,897916],[-1103,1838],[-2571,2071]],[[264317,901825],[2833,-4376]],[[267150,897449],[-1813,-656]],[[265337,896793],[-2677,2121]],[[262660,898914],[-3309,-35]],[[259351,898879],[1640,-3014],[-3468,-3956],[-1884,-35],[-4943,3478]],[[250696,895352],[-3500,176],[3393,-1357],[2261,-2301]],[[252850,891870],[5407,-890]],[[258257,890980],[-703,-2203]],[[257554,888777],[-2293,-3809]],[[255261,884968],[-1977,-1132]],[[253284,883836],[-3751,-80]],[[249533,883756],[-215,-1996],[-1945,-320],[-2489,650],[3041,-2050]],[[247925,880040],[-345,-2403],[-1605,-840]],[[245975,876797],[-2534,90],[981,-1652]],[[244422,875235],[-1510,36]],[[242912,875271],[66,-2240]],[[242978,873031],[-2928,-1341]],[[240050,871690],[750,-1036]],[[240800,870654],[-2080,-2662]],[[238720,867992],[-21,-1061],[-1607,-4281]],[[237092,862650],[-386,-2742],[-7,-4061]],[[236699,855847],[319,-2357]],[[237018,853490],[1073,-913]],[[238091,852577],[-125,-2480]],[[237966,850097],[767,2741]],[[238733,852838],[2161,-22]],[[240894,852816],[2329,-8777]],[[243223,844039],[-794,-2022]],[[242429,842017],[4484,1823]],[[246913,843840],[1442,-99]],[[248355,843741],[2226,-1441]],[[250581,842300],[2340,-771]],[[252921,841529],[2426,-2274]],[[255347,839255],[1427,-2435]],[[256774,836820],[5235,-2697]],[[262009,834123],[1710,-1869]],[[263719,832254],[3196,172]],[[266915,832426],[3703,-983]],[[270618,831443],[995,-1986],[-552,-2712],[768,-3188]],[[271829,823557],[-331,-5075]],[[271498,818482],[1914,-3518]],[[273412,814964],[61,-774]],[[273473,814190],[2477,-2833]],[[275950,811357],[499,-2672],[1041,-145]],[[277490,808540],[1081,-979]],[[278571,807561],[1044,3024],[828,-973]],[[280443,809612],[381,-1561]],[[280824,808051],[478,1760],[-685,1399]],[[280617,811210],[1350,3072]],[[281967,814282],[-644,2225]],[[281323,816507],[-1082,6455]],[[280241,822962],[469,729]],[[280710,823691],[-2003,5079],[2100,1089],[2829,2104],[1572,1889]],[[285208,833852],[1874,3270]],[[287082,837122],[363,3553]],[[287445,840675],[-148,2809]],[[287297,843484],[-1622,4963]],[[285675,848447],[-3773,3931]],[[281902,852378],[157,1131]],[[282059,853509],[1939,3002]],[[283998,856511],[96,1753]],[[284094,858264],[1096,715]],[[285190,858979],[55,1456]],[[285245,860435],[-1331,3540]],[[283914,863975],[522,1098]],[[284436,865073],[-1607,-36]],[[282829,865037],[1853,4366]],[[284682,869403],[-1203,1219]],[[283479,870622],[-335,3516]],[[283144,874138],[1932,1287]],[[285076,875425],[4321,-1521]],[[289397,873904],[3253,-620]],[[292650,873284],[2822,1440]],[[295472,874724],[3900,-3689]],[[299372,871035],[2231,-3985],[3176,-535],[511,-1116],[1732,774],[-927,-6315]],[[306095,859858],[629,-1599],[-285,-1975]],[[306439,856284],[783,-23]],[[307222,856261],[-366,-2776]],[[306856,853485],[2936,-271]],[[309792,853214],[669,-2514]],[[310461,850700],[1845,-1100]],[[312306,849600],[2672,1987]],[[314978,851587],[2782,3329]],[[317760,854916],[556,4055]],[[318316,858971],[1319,2706]],[[319635,861677],[1421,-477],[-969,-944]],[[320087,860256],[1347,308]],[[321434,860564],[2066,-4332]],[[323500,856232],[94,-1290]],[[323594,854942],[1756,-2623]],[[325350,852319],[-1433,-1304],[2172,261]],[[326089,851276],[-1036,-2388]],[[325053,848888],[1374,360]],[[326427,849248],[1631,-1734]],[[328058,847514],[-192,-1478],[-1352,-888]],[[326514,845148],[1677,-478]],[[328191,844670],[-351,-790]],[[327840,843880],[1788,-1406],[-105,-1954]],[[329523,840520],[-1919,107]],[[327604,840627],[1770,-2004]],[[329374,838623],[-67,-2163],[1715,-223]],[[331022,836237],[1364,-1026]],[[332386,835211],[413,-1800]],[[332799,833411],[-1180,-2492]],[[331619,830919],[2384,1477]],[[334003,832396],[2116,-949]],[[336119,831447],[602,-1843]],[[336721,829604],[2272,222]],[[338993,829826],[1550,-1809]],[[340543,828017],[-2075,-1304]],[[338468,826713],[-1338,-1782]],[[337130,824931],[-3305,-1274]],[[333825,823657],[-1407,-3367]],[[332418,820290],[2798,2237]],[[335216,822527],[1861,1979],[2011,745]],[[339088,825251],[-733,738]],[[338355,825989],[2156,-387]],[[340511,825602],[780,-2198]],[[341291,823404],[-1090,-1138],[544,-774]],[[340745,821492],[1363,1602]],[[342108,823094],[2030,-901]],[[344138,822193],[867,-2225]],[[345005,819968],[-117,-4172]],[[344888,815796],[403,-2191]],[[345291,813605],[-3558,-4029],[-2204,-189],[-2058,-775]],[[337471,808612],[-1820,-3052]],[[335651,805560],[-2541,-3112]],[[333110,802448],[-3360,-312]],[[329750,802136],[-1208,-580]],[[328542,801556],[-541,763]],[[328001,802319],[-2211,408]],[[325790,802727],[-5979,-155]],[[319811,802572],[-1113,263]],[[318698,802835],[-3408,-641]],[[315290,802194],[-1238,-1292],[-1197,-3822]],[[312855,797080],[-1900,-543],[-2425,-2535]],[[308530,794002],[-2069,-3731]],[[306461,790271],[-2296,918],[2015,-1517]],[[306180,789672],[-362,-1575]],[[305818,788097],[-2223,-4102],[-1561,-2038]],[[302034,781957],[-1700,-646]],[[300334,781311],[-3059,-2827]],[[297275,778484],[-1377,-2793]],[[295898,775691],[-1559,-1400]],[[294339,774291],[178,-929]],[[294517,773362],[-2042,-2022]],[[292475,771340],[3197,2496]],[[295672,773836],[1107,3465]],[[296779,777301],[3496,3685],[1777,736]],[[302052,781722],[2060,1637]],[[304112,783359],[4257,7361]],[[308369,790720],[3962,3441]],[[312331,794161],[3840,2117],[1819,314]],[[317990,796592],[1909,-441]],[[319899,796151],[1596,-1599]],[[321495,794552],[-242,-2954]],[[321253,791598],[-2529,-2381],[-1854,993]],[[316870,790210],[-2160,-986]],[[314710,789224],[2375,-660]],[[317085,788564],[508,-1273]],[[317593,787291],[1847,891],[829,-721]],[[320269,787461],[-581,-2111]],[[319688,785350],[-1130,-1585]],[[318558,783765],[1354,-239],[-206,-1024]],[[319706,782502],[1012,-3836],[1737,-442]],[[322455,778224],[-390,-856],[2121,-1596]],[[324186,775772],[1645,-67],[605,-704]],[[326436,775001],[1465,1460]],[[327901,776461],[1287,-1074]],[[329188,775387],[1280,-2341]],[[330468,773046],[-4118,-2655]],[[326350,770391],[-2201,-1192]],[[324149,769199],[-827,242]],[[323322,769441],[-437,-1166]],[[322885,768275],[-662,939],[-2397,-4604]],[[319826,764610],[-2432,-1819]],[[317394,762791],[-1077,1499]],[[316317,764290],[73,3279]],[[316390,767569],[1610,2165]],[[318000,769734],[-380,163]],[[317620,769897],[3683,3252]],[[321303,773149],[-65,-1013]],[[321238,772136],[2739,1343],[-4291,59]],[[319686,773538],[1662,2730]],[[321348,776268],[-4361,-3630]],[[316987,772638],[-2744,-922]],[[314243,771716],[-701,605]],[[313542,772321],[-185,-2926]],[[313357,769395],[-1799,-588]],[[311558,768807],[-605,-1137]],[[310953,767670],[-1094,730]],[[309859,768400],[-227,-1475],[-1192,1038]],[[308440,767963],[-297,-1992]],[[308143,765971],[-2055,-1928]],[[306088,764043],[-436,491],[-2133,-4651]],[[303519,759883],[-7,-2374]],[[303512,757509],[-863,-2003]],[[302649,755506],[855,-606],[622,-2521]],[[304126,752379],[1425,135]],[[305551,752514],[147,-883]],[[305698,751631],[-1968,-846],[-122,1070]],[[303608,751855],[-1299,-1336]],[[302309,750519],[-615,1812],[-149,-2023]],[[301545,750308],[-2282,-960],[-1832,-39]],[[297431,749309],[-1829,-1560],[-1788,-2452],[-41,-899]],[[293773,744398],[789,-757],[-607,-3565],[-1718,-4294]],[[292237,735782],[-2027,2893]],[[290210,738675],[285,1774],[965,1148]],[[291460,741597],[-1426,-2030]],[[290034,739567],[543,-3247]],[[290577,736320],[990,-3492],[-1557,-5167]],[[290010,727661],[-1114,-2176]],[[288896,725485],[939,4088]],[[289835,729573],[-532,105]],[[289303,729678],[-106,2275],[-896,34]],[[288301,731987],[-146,1414]],[[288155,733401],[731,10]],[[288886,733411],[-652,3495]],[[288234,736906],[1008,1891],[-1523,-1693],[-223,-4105]],[[287496,732999],[130,-2125]],[[287626,730874],[-2186,1904]],[[285440,732778],[-200,-582]],[[285240,732196],[2122,-1790]],[[287362,730406],[793,-1190]],[[288155,729216],[2,-3179]],[[288157,726037],[-965,-204]],[[287192,725833],[910,-1599]],[[288102,724234],[-323,-965]],[[287779,723269],[1111,135],[324,-4366]],[[289214,719038],[-2343,-1292]],[[286871,717746],[2650,-342]],[[289521,717404],[-4,-1498]],[[289517,715906],[-1111,-1735]],[[288406,714171],[-996,914]],[[287410,715085],[-1228,-296],[1282,-1114]],[[287464,713675],[-11,-2922]],[[287453,710753],[-1714,-411]],[[285739,710342],[-1714,-2505]],[[284025,707837],[-491,-2046]],[[283534,705791],[-1328,-131],[-1210,-1146]],[[280996,704514],[-1078,-3193]],[[279918,701321],[-2201,-3349]],[[277717,697972],[-947,-706]],[[276770,697266],[-1416,-2791]],[[275354,694475],[-669,-895]],[[274685,693580],[-487,-3641],[-425,-381]],[[273773,689558],[-173,-2774]],[[273600,686784],[707,-5555]],[[274307,681229],[971,-4407]],[[275278,676822],[1044,-3340]],[[276322,673482],[-873,1609],[249,-2232]],[[275698,672859],[1451,-6955]],[[277149,665904],[514,-3782],[-327,-4090]],[[277336,658032],[-578,-3241]],[[276758,654791],[-1026,-1036]],[[275732,653755],[-1039,-109]],[[274693,653646],[-8,1358]],[[274685,655004],[-699,2748],[-974,901]],[[273012,658653],[-419,2677]],[[272593,661330],[-481,693]],[[272112,662023],[-76,2012]],[[272036,664035],[-620,-123]],[[271416,663912],[-960,3873]],[[270456,667785],[653,1842],[-497,729],[-226,-1423]],[[270386,668933],[-507,756]],[[269879,669689],[508,3791]],[[270387,673480],[25,2380]],[[270412,675860],[-1775,3343],[-1122,2809]],[[267515,682012],[-971,1054]],[[266544,683066],[-941,-1164]],[[265603,681902],[-2600,-1346]],[[263003,680556],[-97,1158]],[[262906,681714],[-1117,1726]],[[261789,683440],[-1941,1375]],[[259848,684815],[-3710,-636]],[[256138,684179],[-614,2384]],[[255524,686563],[-345,-1940]],[[255179,684623],[-2138,287]],[[253041,684910],[-1154,-414],[-744,-1062],[-1769,1264],[-522,-1416]],[[248852,683282],[661,-659]],[[249513,682623],[1216,846]],[[250729,683469],[1064,-2083],[-1018,-1191]],[[250775,680195],[576,-1180]],[[251351,679015],[1383,-1287]],[[252734,677728],[-939,-786],[-1454,2298],[-487,-158]],[[249854,679082],[-446,-1934]],[[249408,677148],[-463,1127]],[[248945,678275],[-1031,-974],[-1498,937],[127,998],[-1802,2243]],[[244741,681479],[-1021,-1654]],[[243720,679825],[-1140,239],[-1402,1077],[-1967,184]],[[239211,681325],[250,991]],[[239461,682316],[-849,-1818]],[[238612,680498],[-1885,-726]],[[236727,679772],[101,1198],[-781,-283]],[[236047,680687],[374,-1965]],[[236421,678722],[-1070,-2410]],[[235351,676312],[-1612,-1917]],[[233739,674395],[-2185,406],[609,-1490]],[[232163,673311],[-1703,-2153]],[[230460,671158],[-707,-2508]],[[229753,668650],[-739,-4166],[424,-3382]],[[229438,661102],[711,-2577]],[[230149,658525],[-218,-2228],[-784,-3382],[-447,-3701],[-527,-10459],[611,-6048],[1434,-5857],[1848,-4415],[463,-3117],[1301,-3490],[1776,-319],[1065,-1103],[700,-2013],[992,121],[1769,1394],[1854,226],[486,847],[2045,617],[474,-1453],[748,-83],[718,995],[-448,1572],[1937,2740],[128,2237],[516,1078],[65,3818],[364,2684],[1481,1571],[2614,827],[2076,1195],[2446,-1000],[340,1034],[845,-1184],[-112,-3179],[-990,-2239],[-684,-2400],[98,-1206],[-710,-1549],[730,-318],[-650,-1369],[438,-382],[-978,-6036],[-564,1513],[68,1863],[-733,-2171]],[[254734,614156],[551,-2078],[-485,-3032],[-115,-5789],[-1061,-2281],[-552,-2116]],[[253072,598860],[807,-749],[26,1103],[1016,-1311]],[[254921,597903],[1695,1071],[1975,-874],[1529,124],[1591,1301],[834,-612],[1417,535],[1150,-1113],[828,122],[1392,-3568],[317,877],[1358,-2223]],[[269007,593543],[-402,-1132],[318,-2737],[-624,-2035],[-431,-4006],[-30,-3870],[-489,-979],[205,-2829],[-412,-968],[492,-1297],[-601,-2026],[628,-2268]],[[267661,569396],[538,-2674],[1861,-4718],[596,-550]],[[270656,561454],[536,-878],[352,-2352],[1288,-440],[1182,-1047],[1434,632],[1976,1912],[1528,2298],[2980,-1135],[1171,-1007],[1968,-3424]],[[285071,556013],[1756,-3888],[-495,3387],[1788,2461],[392,1638],[1378,1095],[-113,1655],[650,5220],[1671,2955],[1084,-715],[149,-1326],[949,3409],[2071,-266],[1644,2467],[1241,1049],[387,1774],[1170,1371],[1256,-502],[347,-1712],[-507,-1093]],[[301889,574992],[-1770,-1729],[996,-4999],[-184,-1673],[-1245,-3722],[978,-2843],[206,-1559],[1080,314],[589,1319],[92,2119],[-927,3305],[-439,3051],[208,1099],[3436,2422],[965,423],[189,1349],[-1043,-281],[-261,1548],[785,1729],[482,-1080],[553,-3055],[1108,229],[1124,-514],[1192,-1604],[719,-3959],[745,-123],[2452,821],[2061,128],[1098,-2218],[2008,-1112],[773,166],[1840,2131],[904,594],[-1209,457],[2226,48],[2206,631],[2286,-52],[-1390,-1150],[-1482,-92],[576,-1175],[-96,-1641],[627,711],[540,-2329],[558,1196],[801,-1492],[673,957],[777,-1549],[1436,-1614],[-724,-1573],[-540,-2932],[-1031,-17],[874,-1108],[1397,1077],[1280,217],[896,-471]],[[333284,555367],[2271,-2812],[1593,-3133],[415,-1304],[-379,-4877],[552,2066],[1201,-387],[2201,-4080],[-13,-3251]],[[341125,537589],[625,2633],[2862,-1170],[309,985],[2763,158],[2165,-1069],[-283,-2660]],[[349566,536466],[858,2508],[1091,-1296],[1542,-820],[2337,-4193],[362,-1666],[395,796],[369,-3017]],[[356520,528778],[293,1479],[909,-1288],[465,-4809],[1038,-6348],[611,-2256],[1394,-1005],[162,-2944],[-1099,-1939],[-1450,-3930],[-1296,-1526],[-337,-1822],[-829,-2189],[-51,-1518],[-832,-2254],[-580,216],[-1208,-1121],[1991,-207],[2923,3845],[-63,-1052],[633,-3830],[797,-1504],[1122,1088],[777,-560],[1126,1153],[-583,-5125],[869,4031],[611,514],[780,2026],[687,-748],[37,2776],[930,2417],[1991,656],[1630,-906],[539,-1131],[1106,-360],[1596,-1875],[516,-50],[360,-2140],[702,1487],[1181,-1655],[316,-1818],[-347,-1900],[631,1216],[-794,-5771],[788,1172],[359,2424],[562,248],[-241,-1873],[722,1339],[1327,483],[208,746],[1232,-527],[1909,-1937],[1037,269],[1549,-1123],[2344,832],[1417,-390],[4135,-5071],[1186,-2956],[1174,-2226],[901,-716],[354,-1181],[1622,-1097],[1696,256],[1195,-446],[873,-2590],[687,-4899],[508,-5301],[-81,-4047],[-898,-5683],[-508,-1778],[-1408,-3208],[-1530,-4216],[-1498,-1993],[-1318,-4010],[-768,-3571],[-1531,-4409],[-720,-666],[-534,1971],[-446,-985],[46,-2116],[-706,-2612],[225,-3039],[-142,-3281],[497,-7164],[-679,-5328],[-251,-3272],[170,-2299],[-924,-1697],[-703,-3848],[111,-3780],[-837,-2749],[-1097,-4903],[-1092,-1994],[-717,-3552],[165,-2457],[-374,-972],[-1620,-1334],[-763,-1606],[-172,-2171],[-2544,-118],[-355,1444],[-384,-1587],[-1783,478],[-2142,-859],[187,-1295],[-1060,-636],[-1424,-2495],[-1411,42],[-2486,-2612],[-750,-1522],[-2054,-2987],[-907,-2483],[-554,856],[-804,-1300],[808,-627],[-400,-1295],[-369,-5255],[344,-2921],[-246,-2144],[61,-3067],[-497,-2961],[-1310,-1753],[-1319,-2915],[-798,-2593],[-739,-3702],[-890,-2796],[-1478,-3452],[-2465,-3759],[-66,1686],[1062,330],[1407,2576],[35,1309],[1311,2457],[120,2768],[-1420,-755],[-850,-4078],[-1413,-1962],[-614,-2972],[183,-1672],[-595,-1612],[-863,-4135],[-1995,-3581]],[[351748,304813],[-1152,-3781],[-1065,-1720],[-2038,-1553],[-2141,931],[-1235,-783],[-2048,1370],[-877,1329],[-1829,-148],[-1586,3347],[102,4325],[781,1710],[-330,2500]],[[338330,312340],[84,-2889],[-705,-902],[-340,-3270],[429,-3137],[-369,-611],[672,-2295],[1444,-1250],[1278,-1741],[402,-1881],[-542,-1270],[247,-2511],[1575,-1673],[72,-2517],[-2010,-5292],[-420,-2021],[-1756,-2074],[-4581,-2384],[-3566,-917],[-1362,-35],[-2145,865],[221,-2313],[670,-773],[-216,-2676],[-432,-414],[-389,-2729],[502,-1888],[-413,-1281],[-1567,-1296],[-2261,-240],[-2999,1993],[-779,-396],[319,-4067],[-113,-2387],[1520,-1779],[-164,-865],[1306,125],[-355,1048],[1203,618],[554,-1733],[-268,-2363],[-1217,-332],[-538,1713],[-906,242],[-1046,-1348],[1966,-1244],[-1851,-1924],[-828,-1993],[125,-2481],[-340,-2539],[-796,-1090],[25,-2053],[-1532,255],[-2087,-1733],[-1709,-4223],[-18,-2223],[2184,-3913],[2162,-521],[724,-1488],[-545,-2854],[345,-678],[-1620,-2377],[-1777,-1691],[-1811,-3667],[-274,-3627],[-1316,-1455],[-1140,2086],[683,-2402],[-1438,-1330],[-726,-3621],[476,-2683],[-919,-671],[1232,-918],[1315,-3804]],[[309879,194532],[-2215,896],[-888,-1280],[-3429,-2057],[-558,-5987],[-839,-617],[-2435,1488],[-365,2243],[1570,125],[1627,2293],[-647,481],[-2973,-2904],[-252,-1222],[-1400,1288],[1047,2929],[3235,851],[-3148,452],[-1327,-3229],[-1452,1405],[1123,769],[-2149,402],[-781,3089],[1281,-689],[417,927],[1519,-309],[1800,2170],[-2483,-1815],[-2259,2307],[741,371],[94,1697],[-2552,1590],[-778,2262],[1136,114],[115,1784],[1262,-2471],[13,1734],[-1234,1726],[822,1300],[119,2195],[1109,-116],[-1319,1322],[-43,3594],[585,2171],[-842,-216],[-306,2754],[2708,30],[-295,2006],[-625,-1623],[-1138,-89],[-845,1433],[1379,3080],[-431,2336],[-463,-578],[-1848,1693],[-1420,-61],[2034,2669],[-395,1687],[2525,640],[334,2069],[590,-172],[-712,-4058],[1074,1612],[-322,-3067],[413,474],[188,3084],[-379,1759],[1468,747],[-674,686],[229,1540],[1732,1446],[209,1764],[-1670,1586],[745,3182],[-290,1045],[620,2411],[258,4425],[985,-785],[-192,2682],[-901,428],[428,1477],[-959,686],[-630,-1405],[-1370,228],[-641,3698],[823,6137],[720,1737],[511,3346],[-850,5081],[188,1934],[-547,2025],[167,3022],[1071,128],[272,2835],[676,1765],[697,4767],[1111,2900],[614,5515],[940,3038],[-218,3303],[419,744],[474,3452],[-668,7212],[-21,4971],[747,1110],[235,2923],[-565,4285],[925,3250],[371,3854],[1128,8282],[-186,3230],[745,3623],[-358,3130],[240,5110],[265,1279],[-543,1170],[69,1845],[643,1234],[678,8031],[-304,4548],[136,5452],[-751,8647]],[[304393,396029],[-2552,3929],[-542,2300],[-1608,1728],[-990,1745],[-907,554],[-2864,2735],[-894,1424],[-2659,2965],[-1193,3037],[-1112,1575],[-1229,4565],[535,2060],[-1801,6912],[-890,1708],[-188,2352],[-1147,2225],[-286,2672],[-1248,4430],[-1602,8720],[-695,2411],[-1014,2220],[-1068,4555],[-968,2471],[-2866,3512],[140,1448],[581,316],[-792,3507],[164,825],[-633,2123],[148,2057],[1346,3503],[1317,2033]],[[276876,484646],[1119,1764],[533,3027],[-769,1335],[-772,-2091],[-1884,3066],[535,667],[-87,4107],[-280,1804],[968,1368],[199,2841],[969,2146],[301,2467],[-176,2219],[964,1156],[2338,1341],[111,1476]],[[280945,513339],[-460,998],[645,1333],[601,-444],[-112,3158],[1381,1074],[1250,2315],[1647,6128],[-973,872],[391,3918],[-320,4114],[-369,716],[792,1440],[-611,2350],[304,1942],[-1503,4294]],[[283608,547547],[-747,1863],[-699,3064],[855,1888],[-2702,3658],[-986,53],[-683,-919],[-175,-1513],[-1718,-1818],[-248,-1254],[1241,-3418],[-1188,-1334],[-1129,-325],[-868,3758],[-170,-1383],[-792,594],[-620,2467],[-1411,1027],[-1234,65],[-555,-1489]],[[269779,552531],[-786,3066],[-854,703],[495,-1782],[-1229,1235],[269,2494],[-717,1428],[-2121,2193],[-156,1498],[-1522,2116],[1046,-2581],[-633,-1417],[-556,1358],[-862,542],[-572,2936],[454,2055],[-669,904],[454,975]],[[261820,570254],[-601,1595],[-1412,2411],[-795,2479],[-2533,4425],[917,448]],[[257396,581612],[-422,2214],[-903,274]],[[256071,584100],[-324,-1295],[-2600,608],[-1141,1154],[-1462,486],[-809,1045]],[[249735,586098],[-1421,1141],[-1498,-20],[-1869,1793],[-1156,1879]],[[243791,590891],[-1594,3514],[-3075,5421],[-2071,878],[-664,1278],[-1566,-2624],[-2081,-1668],[-826,-244],[-1873,1525],[-1582,341],[-1067,1419],[-1060,583],[-672,1363],[-2579,1095],[-927,1190],[-2287,1659],[-2090,2672],[-686,1604],[-2368,833],[-2062,1555],[-1307,2980],[-2850,2850],[-1510,3949],[-520,2427],[1136,1146],[-645,1170],[710,2030],[78,2201],[-618,755],[-605,2191],[9,2008],[-405,1781],[-1696,3365],[-1896,4861],[-1229,2038],[43,765],[-1221,745],[-712,2132],[478,359],[-1987,2304],[-772,333],[395,1499],[-861,-835],[-523,797],[-113,1810],[627,1615],[-786,2400],[-756,-44],[-525,2230],[-1483,1442],[-383,1962],[238,1246],[-1643,609],[-973,2471],[-579,513],[-1338,3248],[-171,1485],[-1430,4241],[-1034,4787],[177,2286],[-1603,987],[-900,1680],[-560,-723],[-2178,2330],[399,-1502],[-257,-2907],[691,-3848],[-45,-1593],[770,-2416],[1714,-2742],[710,-2611],[818,-758],[310,-1700],[620,-519],[380,-3544],[1124,-1793],[904,-2632],[392,-2373],[187,1192],[627,-1020],[659,-3449],[112,-1989],[716,-1557],[991,-4374],[51,-2649],[811,-1428],[290,1446],[667,-1007],[1672,-4114],[-103,-1572],[-1293,-1949],[-452,709],[-769,3551],[-2934,4290],[-1815,3028],[47,3840],[-893,4299],[-1788,2188],[-375,2151],[-1230,-1333],[-673,1453],[-1679,1491],[-263,1261],[-1379,2434],[1296,-343],[748,527],[700,3278],[-269,1062],[-2357,4615],[-1888,2203],[-394,3242],[-501,657],[-184,2309],[-1667,4507],[115,1695],[-631,867],[-778,3175]],[[174644,697459],[-943,4516],[-1703,2527],[-916,129]],[[171082,704631],[-267,1620],[-1770,561]],[[169045,706812],[-1284,1813]],[[167761,708625],[-2431,318]],[[165330,708943],[-454,642]],[[164876,709585],[31,2941]],[[164907,712526],[-630,1712]],[[164277,714238],[-2825,5721],[-10,3601]],[[161442,723560],[-1429,1591]],[[160013,725151],[-330,3344],[1232,-1740]],[[160915,726755],[-875,2858],[1857,435]],[[161897,730048],[-2130,443],[-104,-1673]],[[159663,728818],[-1141,1357],[-525,2333]],[[157997,732508],[-1611,2713],[-511,5649],[-1220,2318]],[[154655,743188],[-132,1417]],[[154523,744605],[663,2836]],[[155186,747441],[179,2455]],[[155365,749896],[-796,4376]],[[154569,754272],[-513,4088],[1086,5207]],[[155142,763567],[249,6434]],[[155391,770001],[361,4735]],[[155752,774736],[49,3585]],[[155801,778321],[1918,-169]],[[157719,778152],[-677,696]],[[157042,778848],[-1689,49]],[[155353,778897],[-108,4478]],[[155245,783375],[-734,3693]],[[154511,787068],[-681,1455]],[[153830,788523],[-247,2821]],[[153583,791344],[2040,-1255]],[[155623,790089],[2642,-516]],[[158265,789573],[683,333]],[[158948,789906],[557,-5003]],[[159505,784903],[623,465],[-108,2659],[419,1127],[-1186,2693]],[[159253,791847],[344,1760]],[[159597,793607],[-677,1367]],[[158920,794974],[-724,0]],[[158196,794974],[-795,2762]],[[157401,797736],[-1545,209]],[[155856,797945],[-35,2883]],[[155821,800828],[-659,-1117]],[[155162,799711],[-753,-87],[-1027,1435]],[[153382,801059],[-768,2925]],[[152614,803984],[-3864,212],[1534,798]],[[150284,804994],[-1721,237]],[[148563,805231],[-319,1130]],[[148244,806361],[-1182,-282]],[[147062,806079],[-1807,1681]],[[145255,807760],[176,1939]],[[145431,809699],[-623,1758]],[[144808,811457],[214,3046]],[[145022,814503],[-863,-2968]],[[144159,811535],[-708,2195]],[[143451,813730],[699,4431]],[[144150,818161],[-721,-480]],[[143429,817681],[-797,2477],[-1191,731]],[[141441,820889],[-93,1622],[1590,-1307]],[[142938,821204],[-1159,2494]],[[141779,823698],[-903,-2657]],[[140876,821041],[-776,-838],[-2144,2799]],[[137956,823002],[811,2426],[-1073,1704],[2414,6170]],[[140108,833302],[-1178,-614]],[[138930,832688],[-111,3136]],[[138819,835824],[-33,-3497]],[[138786,832327],[-492,-1612]],[[138294,830715],[-1003,-1519]],[[137291,829196],[-763,226],[-550,2074]],[[135978,831496],[591,1033]],[[136569,832529],[-848,3942]],[[135721,836471],[-1788,-716]],[[133933,835755],[-554,-2023]],[[133379,833732],[-667,1102],[1054,2601]],[[133766,837435],[-2970,5257]],[[130796,842692],[-1535,739]],[[129261,843431],[-245,3098]],[[129016,846529],[-1818,3185],[-1265,901]],[[125933,850615],[-1300,2714],[-643,3415],[-387,-1286],[1259,-5305]],[[124862,850153],[-2288,517],[-760,2339]],[[121814,853009],[-2340,1455]],[[119474,854464],[2577,-3446],[-1447,-1231]],[[120604,849787],[-2671,1992]],[[117933,851779],[-2246,2998]],[[115687,854777],[-4019,2719]],[[111668,857496],[796,900],[-18,1888],[-1937,-1719]],[[110509,858565],[-1741,131]],[[108768,858696],[-2296,1310]],[[106472,860006],[-3544,753]],[[102928,860759],[-3338,-478],[-2094,1888]],[[97496,862169],[584,1979]],[[98080,864148],[-1548,-1712],[-1808,581]],[[94724,863017],[-2054,2483]],[[92670,865500],[155,1366]],[[92825,866866],[-2243,-1243],[-1708,299],[704,1484]],[[89578,867406],[-2237,-2466],[1647,-1883]],[[88988,863057],[-1297,-2937]],[[87691,860120],[-2679,691]],[[85012,860811],[-563,-1987]],[[84449,858824],[-2732,-1219]],[[81717,857605],[-980,-1869]],[[80737,855736],[-2232,-359],[-311,1290],[1251,652],[-1261,1574]],[[78184,858893],[1116,2491]],[[79300,861384],[265,3083],[2543,1781],[2357,-176]],[[84465,866072],[-980,1780],[-1852,41],[-3117,-2313]],[[78516,865580],[-46,-923],[-2510,-3061]],[[75960,861596],[-292,-1880]],[[75668,859716],[-1255,-464]],[[74413,859252],[-2436,-2840]],[[71977,856412],[-250,-1231]],[[71727,855181],[2343,-1763]],[[74070,853418],[-1904,-2162]],[[72166,851256],[-630,-1976]],[[71536,849280],[-2112,-849],[-2141,-2654]],[[67283,845777],[-1945,-1424]],[[65338,844353],[-420,-1884]],[[64918,842469],[-3444,-2160],[-1857,-1836]],[[59617,838473],[-700,-2064]],[[58917,836409],[-2649,-848]],[[56268,835561],[-2100,-1816],[-2726,-826]],[[51442,832919],[60,1370]],[[51502,834289],[-1708,-2902]],[[49794,831387],[-1299,613],[-369,-1458],[-1835,-933]],[[46291,829609],[156,1675]],[[46447,831284],[1035,437]],[[47482,831721],[2081,3103]],[[49563,834824],[2616,1788]],[[52179,836612],[2565,-1280]],[[54744,835332],[-686,1192],[658,1823]],[[54716,838347],[3644,3235]],[[58360,841582],[3480,4076]],[[61840,845658],[594,5174]],[[62434,850832],[1060,2703]],[[63494,853535],[-2444,-1407]],[[61050,852128],[-2094,1554]],[[58956,853682],[-36,-2735]],[[58920,850947],[-817,171],[-1633,2615]],[[56470,853733],[-694,-540]],[[55776,853193],[-1229,1370]],[[54547,854563],[-2774,-2261]],[[51773,852302],[-2177,-150]],[[49596,852152],[1169,889],[-831,2901]],[[49934,855942],[541,1805],[-1646,4120]],[[48829,861867],[788,2379],[-1521,-2468],[319,-1655],[-3712,-1083],[-2099,2944],[-1920,1407],[1524,2078]],[[42208,865469],[2985,-1789]],[[45193,863680],[860,991]],[[46053,864671],[-4875,1234]],[[41178,865905],[833,718]],[[42011,866623],[-2265,1262],[-1324,2079]],[[38422,869964],[1541,1295]],[[39963,871259],[-262,1369]],[[39701,872628],[1425,2211],[1153,45]],[[42279,874884],[-183,1894]],[[42096,876778],[2048,2730],[2081,-1279]],[[46225,878229],[2049,1303],[939,1561]],[[49213,881093],[3288,170],[891,1546]],[[53392,882809],[-581,2562]],[[52811,885371],[-1186,1629]],[[51625,887000],[1341,313]],[[52966,887313],[-707,2043],[-1591,-638]],[[50668,888718],[-2910,-2619]],[[47758,886099],[-2518,1268]],[[45240,887367],[-3294,-756]],[[41946,886611],[-3455,724]],[[38491,887335],[-757,2036]],[[37734,889371],[-1425,1365],[2031,880]],[[38340,891616],[-3351,690],[-1901,1397]],[[33088,893703],[2816,1300]],[[35904,895003],[4514,3156]],[[40418,898159],[4783,1224]],[[45201,899383],[-850,-2375]],[[44351,897008],[940,-780],[5219,-179]],[[50510,896049],[1003,1348],[-1036,531]],[[50477,897928],[-2165,3101]],[[48312,901029],[1639,-653]],[[49951,900376],[300,-1330]],[[50251,899046],[3496,-1106]],[[53747,897940],[1079,1182]],[[54826,899122],[-1672,583],[-1483,-705]],[[51671,899000],[-1273,880]],[[50398,899880],[651,1653]],[[51049,901533],[-3832,284]],[[47217,901817],[-1997,997]],[[45220,902814],[-1123,2436],[-3503,2600]],[[40594,907850],[-3890,1860],[1128,389],[476,2725]],[[38308,912824],[5296,304],[3170,2675],[580,2193],[2441,2391],[2993,846]],[[52788,921233],[4671,3400],[3655,-196],[4246,3331],[6320,-3593]],[[71680,924175],[2673,779],[2778,-724]],[[77131,924230],[-661,-929]],[[76470,923301],[3461,-1391]],[[79931,921910],[5432,486],[4343,-1680],[5230,-340],[1738,-896]],[[96674,919480],[5497,638],[5029,-2744]],[[107200,917374],[1127,-14]],[[108327,917360],[5056,-801],[2927,-2154]],[[116310,914405],[7684,-2700]],[[123994,911705],[-1429,1308],[514,2335]],[[123079,915348],[4976,1286],[-762,-1632]],[[127293,915002],[2808,1073]],[[130101,916075],[898,1283]],[[130999,917358],[4733,1519]],[[135732,918877],[1772,1400]],[[137504,920277],[2361,-862]],[[139865,919415],[-3643,-2166],[-2508,-490]],[[133714,916759],[-4211,-3927]],[[129503,912832],[1868,-424],[-35,1565]],[[131336,913973],[2585,2091],[2153,-21]],[[136074,916043],[118,-1301],[1714,2648],[3456,1339]],[[141362,918729],[384,-1004]],[[141746,917725],[3576,3246],[-853,1857]],[[144469,922828],[2368,-1982]],[[146837,920846],[1462,-3015],[3403,-2258]],[[151702,915573],[516,2842]],[[152218,918415],[2103,1669],[888,-2492]],[[155209,917592],[-837,-1840]],[[154372,915752],[2268,-13]],[[156640,915739],[1622,2564]],[[158262,918303],[4151,-203]],[[162413,918100],[3441,-2104],[3955,-969]],[[169809,915027],[2149,-1268],[5654,-1220],[1190,803]],[[178802,913342],[3382,-1855],[1248,-1543]],[[183432,909944],[-2225,-763],[-1858,-2181]],[[179349,907000],[4868,-1198]],[[184217,905802],[3463,-90]],[[187680,905712],[4527,874],[1954,948]],[[194161,907534],[1310,-1538]],[[195471,905996],[2882,-840]],[[198353,905156],[1843,-2750]],[[200196,902406],[-1574,-204]],[[198622,902202],[2182,-2087],[233,1559]],[[201037,901674],[1306,-719]],[[202343,900955],[-2266,5037]],[[200077,905992],[587,1778],[3713,998]],[[204377,908768],[1785,1931]],[[206162,910699],[-2297,-1002]],[[203865,909697],[-2809,-156]],[[201056,909541],[-318,-933]],[[200738,908608],[-2645,614]],[[198093,909222],[1036,1975]],[[199129,911197],[5970,1833],[1735,-1193]],[[206834,911837],[309,-1542]],[[207143,910295],[3430,-2530]],[[210573,907765],[1999,496]],[[212572,908261],[2172,-1797],[3159,-703]],[[217903,905761],[3052,868]],[[220955,906629],[3637,-686],[2041,494]],[[226633,906437],[2659,-1127]],[[229292,905310],[690,1410]],[[229982,906720],[-2511,285],[-1500,2729]],[[225971,909734],[3444,788],[1904,-2579]],[[231319,907943],[2097,1113]],[[233416,909056],[-1115,-4119]],[[232301,904937],[639,-1671]],[[232940,903266],[1171,265]],[[234111,903531],[1016,-1990]],[[235127,901541],[265,1670]],[[235392,903211],[-1089,2813]],[[234303,906024],[528,1682]],[[234831,907706],[1666,121],[3381,3503]],[[239878,911330],[-2552,1651]],[[237326,912981],[1336,1328]],[[238662,914309],[-527,1892],[-4941,2227]],[[233194,918428],[-1376,2940],[1852,1313]],[[233670,922681],[-1862,1539],[398,2754]],[[232206,926974],[2336,374],[-854,1401]],[[233688,928749],[1864,1958]],[[235552,930707],[1789,446]],[[237341,931153],[4468,-4247]],[[300051,207169],[-2273,162],[-1078,1268],[-288,-1355],[935,-2423],[-49,1561],[2753,787]],[[299149,209912],[125,889],[-1858,1013],[-276,-1382],[2009,-520]],[[302316,229071],[-1891,1251],[-1754,-1661],[-682,497],[-140,-2140],[1434,1704],[1323,512],[1710,-163]],[[309232,235614],[-815,831],[338,-2143],[477,1312]],[[297313,260123],[1023,603],[-694,1240],[-329,-1843]],[[352392,311289],[-108,-176]],[[352284,311113],[-1241,-2707],[224,-2392]],[[351267,306014],[47,-79]],[[351314,305935],[328,2335],[1093,2133],[674,-457],[498,2232],[-353,1780],[-1162,-2669]],[[327139,322385],[-161,1110],[-989,404],[-932,-1013],[701,-1336],[1381,835]],[[349258,362140],[-575,-4635],[-661,-1153],[536,-871],[-254,-1439]],[[348304,354042],[1138,1142],[-491,355],[329,3186],[-22,3415]],[[357177,383706],[1398,630],[-63,1361],[-1365,-992],[30,-999]],[[375077,393444],[-787,942],[-393,-2275],[1180,1333]],[[307956,408551],[363,-118]],[[308319,408433],[-123,-1621]],[[308196,406812],[119,-148]],[[308315,406664],[1162,1474],[-670,661],[92,1293],[-1567,2729]],[[307332,412821],[-125,120]],[[307207,412941],[-782,1344],[-698,-797],[-202,-2544],[1566,-1193],[-100,-903],[965,-297]],[[386332,449873],[-955,-620],[1,-1374],[-2042,457],[-987,-1134],[-18,-1639],[-573,-510],[-130,-2422],[-562,101],[197,-1444],[-1153,-2421],[1101,-162],[107,1778],[988,2462],[591,-1433],[-573,2822],[1189,2400],[2077,501],[251,1197],[713,268],[-222,1173]],[[361918,478518],[-11,-1010],[1199,-4197],[-183,2487],[-773,6122],[-664,-2804],[432,-598]],[[347518,533051],[241,742],[-889,541],[-578,-2791],[1280,431],[-54,1077]],[[264404,571429],[-1236,3095],[-1495,1921],[-282,-2397],[1061,-2885],[1724,-1068],[228,1334]],[[260823,577661],[-529,957],[-819,-406],[762,-1350],[586,799]],[[253117,597801],[-1417,-1326],[859,-263],[558,1589]],[[214693,624354],[-509,739],[-1329,-346],[1629,-807],[209,414]],[[229370,654923],[-756,-1152],[-164,-7372],[197,3327],[723,5197]],[[223732,665013],[790,-2918]],[[224522,662095],[147,1097],[-937,1821]],[[276031,664008],[-361,1749],[-824,-1134],[869,-1739],[316,1124]],[[178856,701850],[-936,1451],[-314,-514],[758,-1695],[492,758]],[[256497,710969],[1819,-777],[-1046,1001],[-773,-224]],[[184345,716995],[-1002,2446],[-638,-1149],[-571,1110],[-1069,-685],[1689,-686],[611,942],[980,-1978]],[[255397,721963],[-173,-1865],[581,-993],[-408,2858]],[[193423,729241],[-1653,-4426],[1470,3320],[183,1106]],[[311702,775814],[-211,-115]],[[311491,775699],[1149,-785]],[[312640,774914],[-32,205]],[[312608,775119],[-906,695]],[[276535,778914],[1280,-720],[1530,442],[-605,728],[-2205,-450]],[[287558,783985],[198,1824],[-1143,423],[945,-2247]],[[285352,786669],[38,-1448],[902,834],[-940,614]],[[305332,797621],[505,623],[-1163,1887],[-1107,-2007],[556,-676],[646,1365],[563,-1192]],[[255471,800710],[-1691,1871],[-706,-1305],[-186,-3291],[694,508],[1473,-716],[416,2933]],[[204952,786635],[-300,2367],[-1680,-1540],[-2177,-222],[2364,-334],[1329,1284],[464,-1555]],[[216258,786530],[2076,-365],[547,822],[-2660,-124],[-373,1705],[-374,-2070],[784,32]],[[292969,791821],[1197,-526],[-1333,1584],[62,966],[-2380,-2160],[2023,-479],[431,615]],[[299848,791779],[775,925],[-1541,348],[766,-1273]],[[241895,791656],[1342,-252],[-997,1806],[-1634,-469],[1900,-587],[-611,-498]],[[277277,793902],[869,-1058],[1584,-150],[-1498,1579],[-955,-371]],[[236909,794206],[1774,1729],[-1590,1716],[1090,153],[-791,2728],[-137,-1229],[-1568,-600],[125,-942],[1165,961],[-1591,-2895],[-35,-1486],[1558,-135]],[[289481,768262],[-51,62]],[[289430,768324],[-700,-759]],[[288730,767565],[-2596,-1733],[313,-777]],[[286447,765055],[-914,-398]],[[285533,764657],[-1043,963]],[[284490,765620],[-4103,-1205]],[[280387,764415],[-1412,-1521],[-570,-1532]],[[278405,761362],[1213,-722]],[[279618,760640],[345,264]],[[279963,760904],[445,254]],[[280408,761158],[2439,649]],[[282847,761807],[1794,-755],[1535,60]],[[286176,761112],[2069,1618],[-157,1767]],[[288088,764497],[604,887]],[[288692,765384],[-782,637]],[[287910,766021],[1571,2241]],[[296185,766268],[405,1760],[224,3722],[-656,-811],[27,-4671]],[[279310,767227],[793,829],[-583,1473],[-210,-2302]],[[221420,771418],[-171,4958],[-770,-1223],[788,-1524],[153,-2211]],[[188961,747632],[-2295,3939],[-448,-1716],[1643,-4008],[1100,1785]],[[269232,755176],[1352,232]],[[270584,755408],[-154,1442]],[[270430,756850],[-395,631]],[[270035,757481],[-1858,-5463],[2711,-2040]],[[270888,749978],[2103,669]],[[272991,750647],[1374,1537]],[[274365,752184],[2483,1602],[3126,3047],[921,1445]],[[280895,758278],[-523,1818]],[[280372,760096],[25,-1444]],[[280397,758652],[-3232,-520]],[[277165,758132],[-641,-953],[-2648,97],[-1340,-2209],[-1992,-1373],[-1399,271],[87,1211]],[[264522,776026],[3593,-2701]],[[268115,773325],[515,-1768],[-3,-2116]],[[268627,769441],[-101,-1826]],[[268526,767615],[-1523,-2444]],[[267003,765171],[399,-1990]],[[267402,763181],[979,1761]],[[268381,764942],[1210,848]],[[269591,765790],[788,-1261]],[[270379,764529],[684,-4957]],[[271063,759572],[1758,1833],[212,4762]],[[273033,766167],[1140,2761],[54,1145],[-1164,2733],[1113,-15]],[[274176,772791],[103,-1306]],[[274279,771485],[1011,-1465]],[[275290,770020],[1860,-1609]],[[277150,768411],[327,1762]],[[277477,770173],[1086,-270],[-1024,2062]],[[277539,771965],[124,1381]],[[277663,773346],[-857,337]],[[276806,773683],[-1300,3269],[-2164,92]],[[273342,777044],[-52,905]],[[273290,777949],[-3917,367],[-2962,1064]],[[266411,779380],[-204,1195]],[[266207,780575],[-931,-470]],[[265276,780105],[321,2503]],[[265597,782608],[-1074,776]],[[264523,783384],[492,1679],[-1166,1888],[443,1843]],[[264292,788794],[-2565,120],[-905,1422],[-812,3086]],[[260010,793422],[-2298,265]],[[257712,793687],[-2889,1172]],[[254823,794859],[85,-2231]],[[254908,792628],[-749,1398]],[[254159,794026],[-755,-1481]],[[253404,792545],[-1184,-783]],[[252220,791762],[-963,-2595]],[[251257,789167],[-159,-126]],[[251098,789041],[-3757,-2772]],[[247341,786269],[-3076,-4176],[1571,-312]],[[245836,781781],[1536,1111]],[[247372,782892],[581,-1569]],[[247953,781323],[744,-620],[3094,1764],[1957,2074]],[[253748,784541],[560,-2551]],[[254308,781990],[647,889]],[[254955,782879],[1606,-740],[777,-1790],[2002,-423],[1404,1385]],[[260744,781311],[3234,497]],[[263978,781808],[-185,-1570]],[[263793,780238],[1964,-86]],[[265757,780152],[279,-1823]],[[266036,778329],[689,-1315],[-1813,540],[-473,-1056]],[[264439,776498],[-1622,1388],[-3271,-1340]],[[259546,776546],[-1141,-944]],[[258405,775602],[-23,1149]],[[258382,776751],[-2355,-5774],[-386,-2314]],[[255641,768663],[1013,1747]],[[256654,770410],[742,-421]],[[257396,769989],[-1568,-9070],[335,-2911],[-45,-3211]],[[256118,754797],[1069,-3288],[943,77],[1224,1433]],[[259354,753019],[925,2999]],[[260279,756018],[215,2861]],[[260494,758879],[-889,4365]],[[259605,763244],[64,2586]],[[259669,765830],[620,1522]],[[260289,767352],[163,2308],[1724,2799],[187,-2494],[482,2993]],[[262845,772958],[1135,685]],[[263980,773643],[-40,2219]],[[263940,775862],[582,164]],[[304140,818688],[-1150,-344],[1394,-1772],[-244,2116]],[[65144,846949],[-1377,-1292],[582,-619],[795,1911]],[[66775,847565],[-2377,1168],[-43,-804],[2420,-364]],[[204838,846536],[-2252,-1307],[-60,-1422],[2764,1356],[-452,1373]],[[159718,836983],[-4078,-138],[-2403,3863],[2182,-4682],[2444,-3138],[-1641,2784],[123,902],[1602,-244],[1771,653]],[[294473,836638],[-896,1402],[-1408,66],[1583,-1987],[721,519]],[[287370,837457],[1688,160],[-1186,2129],[-502,-2289]],[[226959,845332],[-1977,-3354],[-895,360],[-623,-1640],[1940,770],[1679,1606],[709,1631],[-833,627]],[[217431,847801],[-739,1057],[-2498,-3777],[890,-3675],[-1994,-3118],[2283,1800],[1750,3923],[-717,578],[1529,2656],[-504,556]],[[224031,819892],[-1513,331],[-906,2234],[-20,2267],[993,1014],[-1359,95],[100,-3610],[-1444,-985],[2419,-1946],[1424,-267],[306,867]],[[288610,823079],[-1655,-222],[1641,2468],[-731,426],[-1104,-1648],[-2553,-1367],[1384,-1103],[3449,1143],[-435,-1247],[3825,2261],[-3126,-11],[-695,-700]],[[238232,823064],[293,934],[-2229,-387],[1936,-547]],[[307358,825126],[-1409,1693],[-785,-1418],[2194,-275]],[[249008,823252],[2079,32],[-1092,680],[-987,-712]],[[324512,825405],[-1589,-197],[-1719,3075],[-270,-2147],[-2602,-1459],[-1492,1564],[-1366,3216],[-533,-1128],[360,-2072],[598,1214],[2607,-3549],[-532,-2308],[1414,-406],[-675,2221],[1942,1945],[2048,-1578],[1809,1609]],[[239103,829583],[-1539,-201],[-796,-2306],[3100,2177],[-765,330]],[[210011,831510],[-625,870],[-1711,-523],[0,-1552],[2336,1205]],[[296716,831144],[381,-900],[2277,1985],[-2658,-1085]],[[200587,831811],[341,3160],[-1051,-1398],[710,-1762]],[[198441,839505],[801,-1889],[-1089,-3686],[1506,2928],[134,1892],[-1352,755]],[[72301,858817],[-990,131],[-4216,-1503],[284,-1203],[3138,692],[1784,1883]],[[189398,851653],[-1446,1288],[-156,-1182],[1679,-1461],[-77,1355]],[[214341,850290],[-320,1200],[-1828,-734],[-127,-2899],[1236,-540],[-55,1497],[1094,1476]],[[196575,855010],[3878,528],[1660,785],[-6410,1314],[-4764,-5137],[519,-1174],[1843,746],[1460,2225],[1814,713]],[[45922,862634],[-1068,118],[227,-1580],[841,1462]],[[237151,884822],[-4381,983],[1163,-1590],[3218,607]],[[200152,884438],[1232,-1804],[1100,618],[-602,1404],[-1730,-218]],[[197160,876811],[-4173,820],[-2413,-1321],[-2524,-3480],[-3534,-686],[-2077,2354],[-1088,-236],[-1597,1370],[-2081,700],[2129,-1535],[-52,-1359],[1818,-2182],[-2561,-761],[-1090,-3303],[-2260,868],[-1431,-1376],[5015,-1535],[4558,940],[432,1804],[1965,529],[2115,1386],[1725,2991],[2287,2467],[2858,615],[-4271,-195],[1696,1256],[3781,-660],[773,529]],[[227864,875283],[1455,-753],[-604,2622],[-2211,-210],[1360,-1659]],[[174114,879194],[-2171,2142],[-1464,-1382],[2469,-1326],[1166,566]],[[223316,877713],[1289,-177],[-572,1443],[-717,-1266]],[[216848,879267],[-278,-1756],[2068,-1532],[1129,1719],[-638,1275],[1133,2222],[-3414,-1928]],[[75167,922797],[-1865,997],[-1794,-452],[1671,-1071],[1988,526]],[[235418,889826],[-1894,-1],[1059,-1426],[835,1427]],[[170371,889803],[926,-1297],[190,2480],[-1558,109],[442,-1292]],[[183061,892047],[4868,-1480],[-3543,1706],[-1325,-226]],[[172660,898241],[-5222,-665],[-2009,496],[1003,1534],[2743,1510],[-2168,714],[-5842,-2330],[-8562,-2301],[683,-1816],[882,1255],[3065,-105],[1057,709],[4826,-967],[-4470,-3583],[97,-1239],[-1648,-959],[3904,-918],[1368,2385],[2457,1431],[313,-1871],[-2558,-2428],[670,-314],[4694,3443],[-1244,1057],[1024,1162],[4406,-528],[-460,1256],[1442,2062],[-451,1010]],[[205669,860170],[-968,611],[-187,-1600],[1155,989]],[[215141,862093],[1461,-2328],[380,1831],[-1041,2430],[-800,-1933]],[[180383,862136],[-2062,-446],[1417,-860],[645,1306]],[[211567,863464],[469,1666],[-2297,-644],[1828,-1022]],[[246263,806680],[1135,-86],[1416,976],[-493,565],[-2058,-1455]],[[227891,803055],[-791,2144],[-1435,1635],[524,1471],[-1651,3160],[-1053,-372],[1063,-1009],[428,-2393],[1539,-5501],[1376,865]],[[297500,807728],[-464,1449],[-2292,-2425],[-666,-3424],[3422,4400]],[[310402,809224],[-777,2114],[-1676,-587],[-28,-2263],[1127,-2089],[1084,1348],[270,1477]],[[223127,810497],[-459,3731],[448,3890],[-2325,1609],[-1629,-536],[773,-1941],[439,1346],[1102,-643],[766,-1808],[-463,-2670],[306,-2135],[1042,-843]],[[232297,805260],[230,3092],[-1539,3645],[-982,4778],[-2150,7742],[-137,2549],[-772,-2633],[1109,-1345],[-2860,764],[-863,-3896],[1196,-2873],[1726,-2635],[1012,-2317],[909,1444],[685,-1585],[-8,-2498],[522,1313],[897,-656],[-665,-1799],[140,-4678],[985,40],[565,1548]],[[549943,856209],[1470,468],[-621,-1769],[-4158,-3105],[-647,-4580],[-77,-4409],[-1475,-5009],[-3563,-524],[-1386,-1786],[-115,-2583],[-3578,87],[257,1673],[-1309,3550],[1046,1924],[-1283,1709],[-1907,4808],[-1288,4490],[-210,3570],[535,-247]],[[531634,854476],[-1539,872],[-736,2390],[-1083,-3423],[-1559,-375],[-4034,-4745],[-1945,-736],[-2530,608],[-2356,2371],[-263,2898],[1352,-846],[-409,2542],[1256,1551],[-2893,-2338],[-641,356],[481,2466],[2520,1121],[-1338,185],[2188,3226],[-1013,-363],[-1830,-3085],[-1066,-935],[369,5343],[-653,2776],[2710,469],[2933,-155],[-1065,684],[-3701,-584],[-893,1928],[933,308],[-1141,1334],[598,2662],[3360,2673],[3717,-152],[985,1100],[-3515,-417],[635,1522],[3033,790],[1331,1316],[-504,1315],[3607,530],[784,-1359],[2170,391],[-97,970],[1793,1065],[-178,1447],[-1033,-1651],[-2808,-1472],[-859,1617],[2641,3694],[2662,1931],[-96,1372],[1862,1204],[315,2306],[2350,3925],[257,2429],[2493,2835],[3683,755],[-2654,55],[1340,1886],[1399,-731],[-449,1862],[-1451,-535],[742,1664],[2665,1616],[702,-2026],[1087,4379],[2052,1029],[5018,5619],[6210,1572],[-214,1305],[3691,837],[1593,-2260],[337,1506],[2891,2692],[957,1816],[2788,-920],[-1374,-1782],[-638,-2626],[4064,4762],[217,-2979],[2696,2473],[115,1562],[2209,-687],[-629,-4073],[1232,2796],[1369,599],[5116,-3473],[-2828,-1055],[-3180,289],[2278,-998],[96,-1165],[3428,20]],[[585749,918146],[1619,-556],[1477,1564],[2659,-1195],[-2125,-461],[559,-1156],[3629,-1000],[6038,-702],[5202,-2960],[1943,-1993],[7045,-3805],[1090,-2984],[-472,-2272],[-1854,-2249],[-3423,-1864],[-2477,-401],[-8011,1964],[-1914,1276],[-4651,1378],[-610,1440],[-2875,442],[3672,-3732],[398,-1198],[2089,-617],[1871,-2137],[-1055,-2777],[1103,-2428],[183,-2524],[2160,-1076],[1994,-2224],[2992,-1123],[1747,1259],[-326,1744],[-2139,523],[-1820,2600],[985,1926],[1792,-380],[1337,-1361],[4857,-1786],[1908,1194],[-1796,3384],[51,1470],[2431,2165],[2178,948],[2041,2348],[2841,-617],[2420,-2411],[1067,3929],[-547,2535],[-1415,917],[1231,4391],[-157,1964],[-2125,1667],[4650,-181],[2261,-582],[2218,-3738],[-3227,-540],[-1637,-2410],[1730,-980],[1177,-1969],[1407,-313],[3232,1041],[608,3603],[2786,872],[5447,3665],[4181,1530],[4050,2297],[358,-3321],[-1862,-994],[4447,-389],[1546,2168],[1738,480],[3009,-562],[2906,1989],[2456,689],[870,-1586],[-754,-1742],[2218,-132],[-4,1685],[1648,134],[1234,1527],[-2119,3579],[2348,1544],[6515,-1044],[7565,-3785]],[[683568,913720],[921,-525]],[[684489,913195],[1628,-440],[3802,-3313],[2137,3770],[-1660,97],[-1492,3039],[-3338,1062],[1335,6394],[-1284,348],[264,2874],[3755,2373],[2138,5847],[1684,1349],[5153,96],[3644,-1317],[-521,-3626],[-1979,-3149],[1859,-2350],[330,-4111],[-641,-1080],[161,-7077],[791,-1571],[2044,-1426],[-1135,-2330],[36,-1874],[-4447,-6544],[-1700,-1258],[-2951,1762],[-2399,-339],[503,-1242],[3181,-1401],[4382,-565],[1390,1860],[3818,2575],[785,2481],[1930,2087],[-1050,3876],[523,1958],[5221,1346],[2165,-3014],[-178,-4094],[1390,-1121],[3465,-1],[-3706,964],[213,2598],[917,408],[-956,3814],[-4583,1967],[-3295,-856],[-2326,143],[-1159,3510],[2176,5162],[-3492,5133],[1626,2370],[3668,1777],[-571,3952],[1619,-92],[1033,-2964],[-1372,-2860],[235,-2794],[2162,-730],[4109,-301],[2772,-1030],[-1042,1614],[-2032,268],[-3247,1682],[-777,1866],[2331,726],[1887,-1131],[1894,653],[-2114,1421],[2809,1202],[2609,-86],[3724,-1726],[2079,-2032],[4097,15],[-190,-1948],[-1652,-948],[-179,-4244],[1696,2437],[448,-2219],[-968,-2149],[2928,1948],[-1624,3301],[1167,2908],[-3855,3810],[-3768,1485],[-882,3542],[205,2858],[8226,581],[8462,1349],[1218,-415],[-3340,-1963],[3592,723],[248,1562],[-2852,3226],[2908,-352],[-3997,1668],[2391,220],[2833,2650],[6982,2734],[9347,1558],[-1606,1308],[4455,456],[4166,-414],[1172,-1130],[6013,2082],[3232,-632],[-2833,2042],[5662,264],[405,2757],[5948,3767],[2455,616],[5222,-1431],[-1417,-1488],[-3284,-805],[7609,-399],[1356,-639],[-2197,-2093],[2737,-375],[1121,1235],[8576,27],[5992,-2794],[1261,-4744],[-2226,-2581],[-8568,-4106],[-4558,-3720],[-2580,-434],[-3005,-1853],[-1335,-2793],[2138,1794],[3535,200],[5847,1772],[2813,1531],[-3098,-48],[1413,1747],[5238,-1828],[3524,-363],[-719,-1115],[6058,1441],[8645,-669],[-55,-2033],[3667,-1586],[9472,-142],[1282,1412],[-880,2012],[3009,1315],[6012,-2489],[1330,1261],[5159,-2117],[-805,-1749],[1810,-1125],[-2311,-1007],[2759,-1301],[-1324,-1398],[-3143,2100],[5440,-7788],[3876,-2235],[1124,940],[3033,6073],[2145,-2577],[3546,-617],[3283,1443],[5443,-2391],[762,2010],[3031,-719],[2152,277],[-955,3003],[1372,1252],[-2661,-274],[1180,1971],[4106,538],[-1031,1795],[3759,-1002],[4040,-134],[7603,-1516],[-5788,-1087],[6736,258],[-2457,-569],[264,-2725],[4048,3445],[6221,-970],[1054,-1902],[-2313,-280],[2813,-1688],[4226,-1327],[2574,-2681],[3570,269],[5836,1278],[5950,-333],[4697,-2309],[773,-2014],[-483,-3109],[2995,-1057],[347,-3011],[1472,-1143],[-79,2810],[2330,1597],[4955,416],[982,-653],[6586,-647],[2067,1424],[1449,-965],[425,-1812],[2799,-1137],[831,-1739],[2578,233],[1271,1302],[-1147,3188],[-1171,256],[905,2850],[5757,-825],[1994,-856],[7863,216],[2269,-1270],[5343,-1533],[3200,-2392]],[[999999,913406],[0,-23201]],[[999999,890205],[-1534,-1453],[-2578,-1298],[-2142,676],[-1582,1760],[-307,-1347],[-2798,1032],[1860,-1991],[1824,884],[126,-1953],[1446,-1316],[767,842],[1169,-2365],[396,-2517],[1497,-2075],[663,-2979],[-1249,-2174],[-3060,1343],[-2019,308],[-7159,-3859],[-3033,-1372],[-1366,-1833],[-765,369],[-1287,-2412],[-4957,-3714],[-715,-2781],[-1023,602],[-2100,3133],[-3025,-131],[-3260,-1581],[-1757,-2575],[-543,633],[600,2995],[-3521,-2288],[-182,-1409],[-1784,1169],[-1657,-100],[-1028,-1222],[-381,-3154],[-746,-1674],[-2397,-3393],[-504,-2194],[1408,-1841],[698,1066],[1377,-1536],[-1207,-1950],[65,-3236],[1259,-732],[221,-2698],[-801,-1113],[-1164,1112],[1139,1715],[-1527,-727],[-1121,-1834],[-988,-4334],[1045,-3589],[-1055,-1299],[-2647,50],[-1940,-2088],[-641,-2401],[504,-3875],[-1220,639],[-2714,-2157],[-404,-3368],[-1000,-2934],[-3766,-4979],[-567,2028],[-496,7096],[-739,2945],[-1330,11009],[-181,2867],[449,4287],[739,3691],[2072,2708],[690,1859],[-515,1670],[1830,304],[600,1306],[1512,33],[2295,2362],[2252,4166],[2798,2961],[2496,3111],[694,1588],[2693,2150],[2048,793],[-435,644],[1255,1886],[561,5617],[1087,1057],[2217,139],[-3169,1200],[-2567,-862],[-895,-4499],[-1713,-767],[-4518,-5384],[-1646,-680],[570,2293],[-1635,-408],[258,1985],[1206,2972],[-2125,-438],[-1321,1202],[-2796,-1000],[-1669,269],[-2193,-1887],[-139,-1232],[-2157,-2935],[-2451,-2372],[-1884,-3219],[-397,-1806],[2825,-997],[-19,-1008],[-1950,157],[-1242,-836],[-1806,825],[-1329,-1633],[-1338,517],[-2983,-896],[-572,1229],[3166,835],[-2152,1781],[-2274,191],[-2846,1268],[-2349,-1410],[325,-1479],[-1824,779],[-2064,-863],[-2715,1116],[-1682,-1532],[-1047,1274],[-6562,-256],[-3241,-2195],[-752,-1507],[-2972,-3159],[-661,-2361],[-4957,-5024],[-2696,-4895],[-4212,-4663],[-2536,-2423],[-14,-1255],[1651,-875],[2626,220],[-217,-4839],[1211,104],[-164,1818],[2052,-1077],[-1703,-2178],[1435,-111],[2308,1531],[352,2969],[1733,-752],[1077,498],[1777,-2752],[2931,-3724],[-614,-999],[-31,-3833],[875,-1126],[-328,-1526],[-1207,-1782],[-680,-2297],[-587,-4066],[114,-5627],[-963,-6354],[-2217,-3770],[-1031,-2986],[-1152,-1933],[-694,-3043],[-1809,-4295],[-2450,-3835],[-1837,-4040],[-743,-685],[-1087,-3191],[-979,-1832],[-3949,-4122],[-1526,-788],[-1253,1060],[-1126,44],[17,2549],[-1231,-1294],[-199,949],[-1768,-3728],[-1247,180],[-62,-2097]],[[863019,755336],[-639,-4],[-1947,-3493],[-132,-5065],[-3900,-4866],[-2046,-1504],[-482,-3402],[1088,-733],[1634,-2729]],[[856595,733540],[678,-2651],[1990,-5341],[385,-3155],[-196,-4087],[472,-9],[-428,-3275],[-569,-1872],[-1953,-479],[-186,-1366],[-1133,898],[-892,-399],[-229,-1566],[-855,-1345],[-216,1729],[-971,-1873],[-1017,-739],[-741,2127],[721,146],[-647,2703],[548,2920],[636,722],[-493,2354],[-145,3126],[-752,1049],[1001,881],[1057,-672],[-588,1703],[-312,3485]],[[851760,728554],[-733,572],[-704,-803],[-965,1436],[-1143,-1543],[-1026,1224],[718,743],[-1544,429],[1046,2533],[673,643],[-424,1222],[700,2469],[-134,1412],[-1627,1371],[-380,-847],[-768,2304]],[[845449,741719],[-712,-966],[-2983,-992],[-1936,-1821],[-1902,-2969],[-1351,-790],[-159,1121],[1593,1113],[385,1646],[-1508,-11],[671,2726],[788,626],[1316,3503],[-1155,1779],[-1901,351],[-1932,-3972],[-2466,-1945],[-1018,-2930],[-869,-1431],[-1706,-589],[-713,946],[-712,-547],[-630,-3017],[1269,-2617],[2569,-833],[415,-2027],[-379,-2189],[1381,-1223],[3612,4201],[2473,-2213],[1156,406],[1696,-747],[-1091,-3371],[-949,745],[-2619,-2142],[-858,-2545],[-1740,-1820],[-1737,-3316],[-634,-3276],[2779,-2505],[843,-4073],[1016,-3683],[-48,-2104],[1521,-1715],[7,-982],[1191,-1815],[95,-1163],[-2480,983],[-1260,1401],[-933,-828],[1475,104],[2626,-3934],[604,-2386],[-974,-450],[-1471,-1675],[-490,-1206],[-1032,196],[510,-1508],[1461,998],[1440,-1911],[1125,-644],[-1601,-2286],[1208,719],[-65,-2790],[-557,719],[-749,-741],[603,-715],[-527,-2187],[353,-1628],[-1399,-451],[-1420,-4205],[-132,-1555],[-725,-1311],[-534,-2520],[-568,-363],[-161,1398],[-518,-1334],[676,-1700],[-1162,-1656],[433,-303],[-73,-3765],[-1240,274],[-658,-2876],[-756,-553],[-213,-1512],[-1314,277],[-86,-2257],[-1190,-2425],[-448,22],[-2325,-2883],[-441,-2417],[-608,210],[-2093,-1555],[-840,583],[-950,-1188],[-562,821],[-271,-1341],[-800,71]],[[817405,638260],[69,-246]],[[817474,638014],[-64,-1208],[-701,1282]],[[816709,638088],[-1100,2071],[2,1576],[-803,-1277],[616,-1884],[65,-1758]],[[815489,636816],[-446,-704]],[[815043,636112],[-2304,-2379],[-1784,431],[-948,-1721],[-1628,-281],[-1249,-1763],[-434,735],[-640,-2841],[919,-2016],[-1078,-1508],[-920,2121],[-308,3020],[693,2068],[-1075,340],[-1284,-579],[-1671,2751],[126,-1382],[-1535,-968]],[[799923,632140],[-1563,-1322],[-682,-1991],[-1336,305],[194,-1571],[-654,-2643],[-1483,-2073],[-1006,-5763],[740,-2748],[1697,-3294],[-56,-1344],[1948,-4868],[1816,-3409],[543,51],[1791,-5021],[409,-626],[732,-3921],[607,-5093],[-88,-3419],[422,-1916],[59,-2111],[-628,274],[-56,-5457],[-589,-2301],[-461,-124],[-1526,-2258],[-2805,-3175],[-708,1553],[-291,-1645],[-1216,-501],[892,-1077],[-527,-1521],[-1275,2144],[1030,-2372],[-360,-1571],[-1519,2634],[782,-1938],[155,-1640],[-1854,-1799],[-1073,-2749],[-956,-187],[208,5975],[687,1747],[-180,986],[-1012,607],[-659,1430]],[[790072,566398],[-1359,1039],[-1124,107],[527,1691],[-527,1520],[-1054,-1380],[-77,3240],[-531,1458]],[[785927,574073],[-944,2940],[-150,-555],[-1405,2504],[-863,933],[-774,-418],[-1616,567],[276,4250],[-852,529],[-1773,-996],[201,-1822],[-350,-2106],[70,-3077],[-1005,-4194],[-390,-3396],[-895,-3376],[-11,-3470],[647,-3083],[917,596],[502,-1193],[156,-2617],[885,-2386],[484,-4894],[-822,1693],[738,-3201],[1651,-1937],[1334,26],[837,-2314],[837,-1377]],[[783612,541699],[665,-416],[540,-1834],[1244,-2000],[1204,-3997],[147,-2707],[-296,-3698],[254,-1472],[-39,-3481],[1035,-2089],[1129,-5081],[-117,-2121],[-1338,502],[-596,-711],[-342,1283],[-1750,1832],[-3976,6100],[12,2182],[-1624,4224],[-280,4064],[-728,5542],[-25,2349],[-623,2712]],[[778108,542882],[-1175,2576],[-276,2837],[-662,98],[-855,3055],[-1311,2704],[-438,-983],[-508,1450],[370,5139],[920,5332]],[[774173,565090],[-389,-921],[-272,3797],[586,1843],[183,3583],[-292,869],[167,2884],[-335,5549],[-918,3387],[-266,-509],[-137,3044],[-800,4132],[-283,6023],[-350,853],[137,2596],[-716,387],[-549,3193],[-578,1513],[-171,-1697],[-796,-2767],[-2387,-2339],[-1038,-2644],[-156,1839],[-1084,-1273],[-138,2159],[-643,-1649],[162,2929],[-1378,-2265],[1014,9200],[-439,3746],[-943,3836],[-129,2596],[-321,-2297],[-622,754],[-590,2030],[922,-776],[481,1199],[-1766,3658],[-1001,98],[180,1793],[-956,-486],[-1107,2940]],[[756455,627897],[-745,2269],[-133,3021],[-509,3223],[-957,3887],[-914,-1604],[-571,-101],[-974,3181],[-444,-2263],[429,-2924],[-997,-2539],[-443,340],[384,1596],[-710,-793],[-200,2161],[-194,-2394],[-1273,-1554],[-722,898],[-118,1306]],[[747364,635607],[1,-2601],[-852,-413],[-287,3185],[-158,-2739],[-1466,204],[387,2639],[-1439,-2880],[-1605,-905],[-669,-1564],[322,-3179],[-625,-2292],[-1308,-2333],[-1957,-1342],[-320,1202],[-825,-1629],[774,34],[-1863,-2969],[-1852,-4934],[-1250,-1320],[-1266,-2730],[-1681,-1985],[-865,-2002],[-64,-2229],[-1380,-1365],[-1322,45],[-854,-3428],[-923,809],[-980,-1091],[-667,-3773],[311,-2939],[-150,-2166],[642,-5041],[-315,-3976],[-1031,-4156],[-290,-2450],[264,-2242],[-29,-5179],[-1243,-99],[-1096,-3690],[-46,-2456],[-1550,-969],[-637,-1268],[-367,-3000],[-1507,-1814],[-1530,1949],[-1148,2935],[-637,3255],[-227,2814],[289,-30],[-1178,5107],[-552,3422],[-1464,4122],[-1184,6042],[-277,3497],[-801,4900],[-1203,3437],[-48,1909],[-1266,3894],[-385,2403],[-504,6884],[-793,6287],[375,2003],[-475,-270],[-98,3224],[-366,1844],[593,4338],[-187,3282],[-557,2042],[1136,1408],[-1331,-18],[436,1632],[-1093,1287],[-748,-2169],[602,-1730],[-663,-2224],[-2752,-2469],[-848,9],[-1645,2098],[-3107,6530],[-70,1117],[814,-592],[2502,1702],[731,2356],[-2155,-1252],[-1191,530],[-1653,2023],[-620,2260],[998,1663],[-1505,-1512],[-194,1543]],[[689347,646059],[-1380,-275],[-997,2156],[-383,3443],[-1301,622],[-13,2164],[-750,2068],[-2080,-1304],[-2509,-284],[-326,-730],[-1409,885],[-1653,117],[-182,-843],[-2552,260],[-715,-710],[-1588,19]],[[671509,653647],[-584,340]],[[670925,653987],[-336,-554],[-2079,1067],[-2911,718],[-1583,83],[-689,813],[-1344,156],[-2721,1248],[-640,3435],[-339,3164],[-470,1093],[-1269,654],[-1961,-1320],[-2095,-2493],[-697,-283],[-1105,1112],[-1504,172],[-697,1289],[-2120,2252],[-599,1737],[-1237,1231],[-1012,122],[-1076,1697],[-1120,5518],[-557,497],[-71,1620],[-1335,2969],[-271,1643],[-1435,-1005],[-1391,1647],[-1410,-2041]],[[634851,682228],[-1577,121]],[[633274,682349],[457,-2431],[-1161,-922],[906,-364],[1086,-4814]],[[634562,673818],[920,-3459],[65,-1391],[1223,-1372],[466,-1847],[1614,-2085],[552,-2512],[-426,-1742],[1462,-6068],[685,-1762]],[[641123,651580],[-116,3883],[668,3180],[720,1018],[780,-1486],[-161,-2238],[324,-2232],[-483,-2842],[-445,-362]],[[642410,650501],[117,-1580],[718,-322]],[[643245,648599],[938,-1782],[958,59],[1103,944],[3459,-460],[1399,1192],[972,3153],[976,1370],[1179,2705],[1163,1752],[386,1592]],[[655778,659124],[971,1567],[-367,-4008]],[[656382,656683],[251,-3978]],[[656633,652705],[701,-3015],[1609,-3244],[3773,-1654],[2659,-6310],[800,-412],[-65,-1712],[-1190,-4272],[-1321,-2287],[-1171,-4182],[-1032,968],[-669,-1932],[-408,-3776],[268,-3494],[-1764,-678],[-1448,-1868],[-290,-2497],[-779,-1274],[-2198,-637],[-622,-1527],[112,-1209],[-643,-2030],[-2766,-198],[-1273,-1454],[-1457,-661]],[[647459,603350],[-2105,-2103],[-427,-1994],[121,-1786],[-1705,-1888],[-2991,-1769],[-1000,-1109],[-2270,-1263],[-838,-1074],[-1055,-2407],[-1884,-13],[-1618,-2289],[-1719,-1162],[-3143,-751],[-1718,-3098],[-1169,8],[-721,-877],[-1190,-312],[-1263,1318],[-676,2536],[141,2209],[-538,2199],[-189,3222],[-844,6515],[340,2236],[-112,2013]],[[618886,601711],[-279,2163],[-876,2284],[-248,1852],[-1511,2670],[-1446,4696],[-315,2393],[-992,3988],[-1884,3025],[-1298,1491],[-1444,4696],[148,1236],[-442,2149],[300,3028],[-246,2235],[-1996,6760],[-1025,1625],[-1046,630],[-1006,3130],[-89,2791],[-1751,4821],[-747,2903],[-1857,4962],[-1113,3569],[-1567,673],[454,2126],[475,5014]],[[597085,678621],[63,1193]],[[597148,679814],[-192,-460]],[[596956,679354],[-467,-1225],[-935,-7432],[-499,-1492],[-1277,1679],[-1424,3081],[-477,2994],[-985,2658],[-432,2679],[-572,-2033],[570,-1448],[185,-2335],[740,-2530],[1803,-3952],[7,-1722],[954,-3306],[183,-2372],[1684,-5675],[1747,-7204],[1638,-3184],[-675,-101],[-50,-2833],[486,-2940],[1477,-1881],[1783,-3744]],[[602420,635036],[154,-2431],[791,-2373],[-51,-6311],[153,-3192],[619,-4513],[1252,-1565],[777,-1816],[1133,-1448]],[[607248,611387],[839,-3424],[642,-4135],[434,-4787],[1352,-4717],[217,2046],[945,-2703],[2701,-2333],[1339,-3775],[1630,-2343],[428,-2222],[1103,-2063],[890,-922]],[[619768,580009],[814,-3073],[-382,-1306],[-1314,-1363],[-721,-1393],[1033,487],[929,-514]],[[620127,572847],[1686,-4239],[1482,-2098],[1546,39],[2427,2365],[2079,-533],[2333,2536],[1706,-205],[1820,1086],[734,-381]],[[635940,571417],[3254,1605],[1895,2692],[1285,-906],[-474,-2933],[157,-4022],[677,-1601],[-1262,-302],[-292,-5376],[-1098,-3454],[-908,-3824],[-697,-1405],[-252,-1795],[-1146,-3964],[-832,-4840],[-1111,-4024],[-1153,-3209],[-719,-2700],[-3046,-7176],[-2299,-4802],[-3141,-3941],[-1632,-2482],[-2403,-4558],[-4133,-9448],[-1242,-4279]],[[615368,494673],[-405,-1018],[-1381,-926],[91,-1009],[-773,-2048],[-1172,-882],[-297,-3331],[-1735,-7274],[-747,-1268]],[[608949,476917],[-1118,-7022],[152,-2688],[1662,-3242],[205,-861],[-716,-2926],[424,-2925],[-381,-2561],[409,-2957],[528,-1478],[396,-4278],[1888,-3258]],[[612398,442721],[412,-1168],[-581,-3972],[358,-3985],[-123,-2888],[260,-850],[-99,-4901],[280,-6374],[478,422],[47,-1919],[-603,-1920],[-164,-2121],[-1250,-2997],[-734,-2703],[-1673,-2115],[-439,-1068],[-2609,-1600],[-2502,-2944],[-2548,-6240],[-1877,-2196],[-1954,-3844],[-534,-55],[-141,-3859],[771,-1973],[792,-5004],[321,-4761],[307,1954],[227,-4967],[-569,-4947],[476,-156],[-288,-2054],[-784,-2193],[-1524,-1658],[-3500,-2605],[-1542,-2272],[-561,-2131],[718,-1564],[295,1093],[-191,-4536]],[[591350,345650],[-976,-8001],[-692,-2499],[-1410,-1869],[-1230,-2613],[-2907,-9432],[-3980,-7845],[-2765,-4500],[-2175,-2769],[-1800,-1412],[-1222,286],[-976,-1776],[-1765,222],[-488,-1157],[-3449,1089],[-882,-569],[-1984,421],[-856,-350],[-1269,-1798],[-2024,47],[-1473,-583],[-1415,-1911],[-1071,192],[-1491,2389],[-741,-83],[-63,1516],[-1269,-476],[314,1781],[-566,2762],[-1139,3520],[1110,1039],[167,3138],[-278,2251],[-1482,4286],[-1356,5446],[-664,4126],[-1396,4656]],[[545687,335174],[-2024,3861],[-1048,3432],[-1038,6330],[-341,3509],[-22,4103],[-932,4925],[-77,5455],[-196,1855],[340,1573],[-567,3037],[-968,2502],[-1452,5041],[-784,4337],[-1972,7452],[-1007,2286],[-889,3195],[-91,4457]],[[532619,402524],[211,3230],[-189,5168],[603,1172],[868,5904],[750,7107],[964,2430],[238,1493],[1205,1513],[1023,4192],[173,4493],[-351,2493],[-505,1261],[-917,4251],[-585,3881],[1001,2138],[54,1881],[-1434,6741],[-108,1642],[-839,2159],[-608,2949],[2127,1349]],[[536300,469971],[-1824,-720],[-550,1349]],[[533926,470600],[-101,2571],[-441,1898]],[[533384,475069],[-669,2598],[-1798,3848]],[[530917,481515],[-2174,5350],[-1634,2931],[-1279,3647],[-730,3520],[785,-1915],[568,200],[-1274,1777],[-676,3495],[876,800],[445,1316],[14,3790],[1375,-1447],[568,893],[-1264,598],[-615,1518],[814,145],[-75,2698]],[[526641,510831],[-569,636],[1169,4669],[-17,2234]],[[527224,518370],[410,4588],[-1090,4260],[510,326],[-711,1263],[-162,-852],[-1181,1003],[-228,2738],[-955,-164],[-51,1357]],[[523766,532889],[-895,902],[165,-2073],[-2802,-59],[-753,-890],[-2602,-632],[-1358,2112],[-568,2855],[7,1616],[-1458,3700],[-1193,1909],[-849,372],[-3944,-250]],[[507516,542451],[-3010,-903]],[[504506,541548],[-1210,-755]],[[503296,540793],[-658,-1653],[-1917,-314],[-1691,-1520],[-1246,-1624],[-2336,-1456],[-1009,-1294],[-1103,989],[-1987,944]],[[491349,534865],[76,235]],[[491425,535100],[187,14]],[[491612,535114],[-606,1212],[-305,-1213],[-2146,1061],[230,-471],[-2396,-544],[-344,387],[-2473,-1142],[-2803,-2207],[-1728,-1701]],[[479041,530496],[-1983,1414],[-2426,2753],[-3179,6061],[-1413,1377],[-177,918],[-1229,1322],[-600,1294]],[[468034,545635],[-627,1078],[-2091,1764],[-68,1655],[-1030,1131],[-210,1711],[-533,410],[-400,4945]],[[463075,558329],[65,719],[-1077,2776],[-91,1710],[-2047,1899],[-970,4048],[-743,50]],[[458212,569531],[-31,1196],[-940,446],[-116,4303],[-1068,-1067],[-387,1162],[-877,110],[-124,981],[-1091,1251]],[[453578,577913],[-143,4202]],[[453435,582115],[-172,1641],[746,-225],[3107,1067],[-1937,-207],[-848,-564],[-338,1387]],[[453993,585214],[-1143,4834],[-540,1407],[-1021,678],[1080,989],[843,2204],[856,3225]],[[454068,598551],[-2,2657],[526,3789],[744,3670],[134,2026],[-151,3752],[-357,2856],[-836,2125],[642,2519],[202,2611],[-609,2515],[-535,-108],[-705,2678],[-478,-1659]],[[452643,627982],[108,3383]],[[452751,631365],[217,3098]],[[452968,634463],[1591,4114],[411,2982],[1124,3861],[-259,562],[2390,4173],[508,1913],[170,3155],[1057,5033],[2329,2852],[888,4144]],[[463177,667252],[223,1310]],[[463400,668562],[630,1531],[2675,1275],[1544,1497],[970,1965],[1651,2081],[1760,4409],[516,1778],[40,2004],[-618,1602],[185,4187],[1281,3920],[283,2880],[1804,3642],[819,1109],[2053,1575],[1837,1948],[1522,4781],[1190,5982],[1797,693],[-167,-933],[1390,-2749],[1410,-709],[1768,702],[1353,-242],[650,996],[368,-1656],[1723,-140]],[[493834,712690],[851,-59],[1603,1600],[1163,1802],[1365,1144],[1317,231],[1297,2140],[2062,1528],[3711,480],[1053,1089],[2241,662],[2719,1],[1852,-1309],[2290,1557],[660,874],[1225,-985],[768,1024],[1961,-1398],[1851,479]],[[523823,723550],[3088,2388],[1412,-797],[600,-2808],[1782,2018],[202,-1175],[-1670,-3263],[181,-2584],[1149,-1501],[322,-2332],[-1626,-4120],[-1306,-1974],[262,-2142],[1566,-1988],[1005,287],[328,-1859],[839,-398]],[[531957,701302],[2153,-1916],[1316,-341],[1472,673],[2649,-1382],[767,-1009],[1843,-710],[507,-1371],[381,-2980],[582,-1365],[1159,-959],[1829,-295],[1577,-789],[2336,-1802],[2073,-2885],[986,-14],[1172,1187],[1215,3497],[-624,4378],[542,2377],[1388,2141],[2819,2116],[1532,-113],[2509,-1775],[544,-2399],[1420,-326],[922,-886],[1540,40],[947,-786],[349,-1352]],[[569862,692256],[644,-843],[1419,641],[3763,-1440],[1999,-1662],[1520,-278],[1548,-1304],[3675,3716],[1685,31],[140,763],[1312,-790],[1013,493],[-328,-1475],[919,-1183],[616,967],[777,-1110],[3609,665],[821,839]],[[594994,690286],[776,1554]],[[595770,691840],[558,1842],[1195,7038]],[[597523,700720],[1398,5619],[100,1280],[912,2257]],[[599933,709876],[-92,3523],[-496,2060],[356,2044]],[[599701,717503],[-227,2330],[1049,2068],[-388,1491],[-1421,-1858],[-2600,1111],[-2518,-3569],[-2500,-866],[-1158,875],[-989,2084],[-1859,1574],[-1968,383],[-446,-3290],[-2207,-910],[-1516,1425],[-292,1755],[-2040,702],[-1800,-814],[1629,2100],[-2481,-56],[517,855],[-878,1334],[-392,1769],[429,1724],[-2616,1769],[619,2087],[226,-1250],[1399,-17],[-930,1741],[694,1050],[-921,2402],[403,1604],[-1983,-566],[189,3096],[1547,2430],[1518,328],[530,-803],[4388,617],[-270,1223],[2464,637],[-1334,422],[-887,1174],[285,1265],[2143,-416],[1182,273],[1292,-664],[1236,135],[564,1259],[2357,2426],[2985,1706],[3804,-360],[711,631],[809,-1983],[2094,-273],[354,-1516],[918,-972],[745,598],[801,-1061],[3652,-1540],[2904,1078],[2330,-859],[1929,1482],[1529,1812]],[[615305,750685],[703,2681],[-762,4084],[-1812,2395],[-2384,2111]],[[611050,761956],[-3503,5143],[-1489,780],[-916,1654],[-1222,217],[-574,1401],[-1539,916],[807,967],[1961,517],[61,1641],[959,2333],[1327,253],[-2016,3233],[2041,163],[-174,885],[2375,1733],[-272,967],[-2726,-1051]],[[606150,783708],[-1863,-100],[-566,-935],[-2945,-1529],[-1257,-203],[-1519,-2044],[-138,955],[-1058,-1485],[481,-2897],[1487,-2311],[1701,843],[1124,-353],[-505,-1944],[-1453,-356],[-1105,552],[-1069,-1754],[-1030,27],[-2241,-2485],[-1276,983],[290,3224],[-1768,1484],[-1141,330],[3214,3218],[-1285,1355],[-2015,-546],[-1936,1428],[2217,1723],[-2905,291],[-2044,-667],[-1604,-4060],[-1715,-1092],[290,-2503]],[[582516,772857],[-412,-2468],[-1415,-508],[-1,996],[-1118,-3732],[-167,-3279]],[[579403,763866],[-333,-2091],[-921,37],[-569,-1241],[-112,-2585],[-1122,-1669],[1471,-2956]],[[577817,753361],[922,-2978],[1975,-1402],[-769,-1514],[-1690,631],[-1868,-637],[-671,-1694],[-1350,-1121],[-1581,-2504],[142,1418],[1495,1848],[-1907,-91],[-185,684]],[[572330,746001],[-2596,1587],[-865,-812],[-878,534],[-1096,-1325],[-888,141],[289,-1951],[-562,-1154],[-979,-43],[-1896,1653],[-103,-2717],[1934,-4432],[-1249,-179],[352,-1349],[-1025,-832],[1823,-1358],[1983,-2288],[246,-3350],[-1319,1783],[-1512,-783],[1258,-2596],[-910,-630],[-1211,1234],[748,-3118],[-33,-2888],[-564,1527],[-1122,-499],[-821,1937],[-522,-1728],[-860,2036],[-32,2726],[-1204,1855],[738,2029],[1170,779],[3043,-2191],[635,1290],[-2020,1555],[-2637,-694],[-998,375],[-926,3696],[-1330,1888],[-832,2265]],[[555559,739974],[-416,1979],[-1020,986],[-388,2442],[323,1843],[-57,2912],[380,2149],[-653,484]],[[553728,752769],[-2291,3340]],[[551437,756109],[-2361,2750]],[[549076,758859],[-229,244]],[[548847,759103],[-1894,2690],[-1415,895],[-1134,-140],[-2396,4366],[966,90],[-1599,2575],[-113,2217],[-1505,1523],[-964,-2975],[-934,1614],[-143,2422]],[[537716,774380],[394,419]],[[538110,774799],[-254,1086],[-3761,-1925],[-135,-1212],[827,-1620],[-764,-1455],[411,-2954],[819,-1357],[2425,-2509],[1239,-5224],[2377,-3774],[841,-702],[2209,32],[520,-1072],[-385,-1914],[3030,-2211],[2365,-2411],[1475,-3261],[-395,-1679],[-738,685],[-591,2033],[-2603,1054],[-1106,-3545],[188,-1308],[1436,-1530],[167,-2267],[-1710,-1678],[-38,-1811],[-1357,-2768],[-923,-16],[688,4582],[624,276],[-481,3522],[-920,3771],[-2060,1474],[-515,2544],[-1842,941],[-1025,2420],[-1791,48],[-1272,1338],[-2760,4846],[-947,804],[-1633,3039],[-1835,6419],[-2107,1774],[-1454,611],[-1901,-2982],[-1634,-900]],[[520814,764013],[-644,-421]],[[520170,763592],[-2132,-3121],[-1050,-574],[-1970,925],[-964,1280],[-1197,-341],[-1600,1220],[-2205,-2369],[-575,-1646],[443,-2868]],[[508920,756098],[102,-2884],[-3237,-3892],[-2916,-1335],[-3078,-7027],[-701,-2109],[340,-2709],[1129,-1798],[-1619,-1917],[-737,-1681],[-487,-3383],[-1404,-117],[-1306,-1945],[-1083,-2887],[-6054,-162],[-853,-1254],[-1382,-490],[-1261,-2357],[-1154,963],[-1254,4539],[-1089,1420],[-1449,-88]],[[479427,724985],[-1189,-1029],[-2121,685],[-1111,-528],[510,2361],[-186,6019],[-923,8],[215,1746],[-939,-71],[276,3599],[629,1210],[726,3773],[642,5036],[-618,4755],[280,646]],[[475618,753195],[240,1973],[-656,3107],[-856,1057],[1004,2118],[1736,621],[-23,833],[1553,1093],[1211,-1006],[4434,-72],[3174,-988],[2552,615],[1552,-876],[474,491],[1495,-749],[1507,470]],[[495015,761882],[860,927],[664,5901],[458,5762],[873,-1292],[-736,2528],[-319,3379],[-1778,1205],[-1140,3840],[615,875],[-1466,8],[210,941],[-4092,2172],[-1143,-86],[-1018,1283],[970,772],[-1087,2192],[4136,1783],[1499,-1801],[684,661],[2971,25],[-525,906],[-49,2351],[-759,2852],[1660,-21],[334,-1732],[2708,-540],[1613,898],[-640,1509],[2941,1748],[965,1505],[-38,2885],[926,1491],[1701,631]],[[507013,807440],[2292,1662]],[[509305,809102],[2434,52]],[[511739,809154],[-2159,914],[2038,411],[-655,1187],[1488,2953],[544,2967],[3844,3539],[2094,202],[1059,-942]],[[519992,820385],[243,2365],[2012,54],[1342,-1044],[352,2136],[998,304],[-73,3208],[-750,1921]],[[524116,829329],[-57,1149]],[[524059,830478],[-126,2562],[-1344,1075],[88,5966],[1409,-658],[280,1359],[-1356,754],[930,1534],[2599,718],[1134,2065],[1586,915],[-26,-2916],[-670,-3689],[1564,-587],[29,-1339],[-1493,-489],[-377,-2060],[-1645,-2204],[-381,-2688],[699,-660]],[[526959,830136],[112,-716]],[[527071,829420],[1105,-1889],[1633,-1020],[783,373],[-265,-2274],[1338,-301],[1977,1326],[1289,1771],[1259,-333],[1165,-1601],[767,73],[393,-1776],[1068,-720]],[[539583,823049],[660,-355]],[[540243,822694],[-380,1107]],[[539863,823801],[-496,92]],[[539367,823893],[109,450]],[[539476,824343],[5485,2015],[1038,1561],[1950,1041],[2949,643],[962,-2413],[851,-485],[1745,652]],[[554456,827357],[1028,2738],[1516,437],[1054,1728]],[[558054,832260],[-112,-611]],[[557942,831649],[-735,-1191],[1650,-280],[95,1022]],[[558952,831200],[37,968]],[[558989,832168],[-528,4734]],[[558461,836902],[70,4464],[1826,4428],[2294,908],[2035,-3759],[1789,-482],[1311,1874],[-224,3233]],[[567562,847568],[574,2866],[-2116,39],[-932,3317],[174,1629],[2461,1641],[2954,287],[182,699],[4070,-1117],[2883,200]],[[577812,857129],[4,1425],[2592,616],[556,1013],[2709,-748],[-1115,1906],[-1811,-23],[-1183,1090],[-362,1789],[-1987,-836]],[[577215,863361],[-1544,15],[-4404,-1218],[-3649,-1723],[-2442,-333],[-1657,1361],[-1123,-1106],[339,2081],[-3191,1279],[-210,2198],[682,3698],[-972,2359],[-423,3752],[1520,2466],[-294,978],[2152,629],[590,1999],[1990,1471],[2860,3668],[777,1693],[2028,351],[166,3667],[-3312,1931]],[[567098,894577],[-2925,-414],[-2045,636],[-312,-1452],[-1912,-1123],[42,-1465],[-1229,-2086],[1059,-2047],[-2102,-3527],[-3913,-2313],[-2077,-1772],[-398,-1673],[-1577,-387],[394,-1363],[-1296,-886],[-1222,-5185],[334,-5184],[1958,-658],[2309,-3023],[509,-1909],[-2795,-2357]],[[549900,856389],[-432,1148],[-1213,-340],[-2217,687],[-1193,-972],[2137,-11],[1086,-1029],[1875,337]],[[636756,779239],[-2981,-3555],[-1749,-1226],[-1238,-4225],[-913,-950],[1684,-3929],[410,-2866],[-127,-2813],[3082,-7052]],[[634924,752623],[1483,-3216],[333,-1632],[1526,-2620],[596,-43],[1043,-1761],[-1242,219],[-1021,-725],[-977,-6644],[-517,364],[-452,-1888],[50,-2251]],[[635746,732426],[589,-4549],[1081,-1013],[1835,-530],[1118,-2331],[1626,-1606],[1788,-759],[1189,43],[3289,1463],[1655,-299],[-155,3112]],[[649761,725957],[-252,3462],[172,5546],[-502,2047],[-1522,329],[219,2035],[1021,-365],[-322,2147],[-1636,19],[-457,2880],[583,3788],[560,-1262],[1306,-39],[412,-905],[1705,163],[924,1173],[-328,1791],[-1381,1931],[-690,3387],[-1895,16],[-540,-697],[-300,-4539],[-1022,3379]],[[645816,752243],[-89,1897],[374,3908],[-1766,535],[-958,1824],[-890,93],[17,1826],[-1308,4208],[-1388,787],[-93,1517],[1563,280],[1898,-579],[-1349,1662],[-49,1000],[1043,2237],[3098,241],[1859,-395],[-1185,1427],[1004,3666],[97,2829],[-706,1690],[-1202,215],[-1105,-895],[-2520,1603],[-2108,-1367],[-1165,-1452],[-1812,-682],[-320,-1079]],[[579890,406773],[-220,141]],[[579670,406914],[-1105,-546],[-671,-1286],[-690,-10],[-2147,-6749]],[[575057,398323],[576,1214]],[[575633,399537],[498,1027]],[[576131,400564],[826,2210],[951,844],[287,1338],[1698,181],[628,1169],[-631,467]],[[596829,424051],[8,920]],[[596837,424971],[-448,7467],[713,2757]],[[597102,435195],[-55,1606]],[[597047,436801],[-915,2270],[165,1707],[-397,4516],[-565,1769],[-903,1399],[-117,-964]],[[594315,447498],[86,-2168],[765,-2509],[-181,-705],[342,-6592],[-719,-2188],[41,-1803],[711,-3505],[-25,-2578],[835,-2199],[-59,-2417],[712,855],[1187,-2077],[-633,3686],[-548,753]],[[583319,439012],[-188,1258],[-1007,-1653],[65,-1400],[914,550],[216,1245]],[[590316,457091],[-1354,2401],[-631,-116],[447,-1971],[1538,-314]],[[581104,494978],[-351,228],[-450,-2385],[-164,-2606]],[[580139,490215],[167,89]],[[580306,490304],[1242,2470],[-300,1659]],[[581248,494433],[-144,545]],[[594373,505981],[-82,25]],[[594291,506006],[-1283,3],[-822,1245],[-139,-1043],[-2578,-1442],[-719,-834],[122,-1120],[-754,-2779],[125,-863]],[[588243,499173],[42,-439]],[[588285,498734],[229,-485],[-558,-4978],[429,-5130],[1171,3050],[1584,-1374],[713,620],[968,-717],[1092,2009],[-1480,404],[689,756],[-629,442],[819,952],[209,1512],[615,-40],[118,1925]],[[594254,497680],[365,913]],[[594619,498593],[45,2690],[383,818],[1656,983],[-266,1090],[-1202,-1532],[-841,2220],[-21,1119]],[[550098,499048],[149,1643],[-772,44],[623,-1687]],[[582340,501712],[626,1212]],[[582966,502924],[-236,1073]],[[582730,503997],[-387,11]],[[582343,504008],[-409,-359],[-361,-2723],[767,786]],[[580912,453401],[-726,882],[-1388,-3483],[-94,-2068],[1006,317],[1202,4352]],[[586662,453459],[-41,293]],[[586621,453752],[-1610,6282],[-212,3670],[-1086,2711],[-477,-209],[-675,1517],[627,2047],[-544,2923],[168,1754],[-569,1196],[94,2477]],[[582337,478120],[31,410]],[[582368,478530],[-801,3341],[-183,2968]],[[581384,484839],[-285,99]],[[581099,484938],[-369,-5798],[532,1280],[-362,-2874],[-21,-3186],[709,-2918],[-506,-1768],[893,-4769],[1766,-3432],[410,-3397],[794,-1827]],[[584945,456249],[-19,-482]],[[584926,455767],[-303,-1397],[928,-402],[684,-1335],[427,826]],[[593018,514450],[-202,523],[-1153,-1089],[-1262,-151],[1988,-1771],[-373,1661],[1002,827]],[[586953,517904],[-123,-430]],[[586830,517474],[-2165,-4070],[-5,-1348]],[[584660,512056],[322,-1170],[1043,2878],[1196,2183],[-268,1957]],[[605708,543686],[-517,-515],[-73,-2474],[733,2227],[-143,762]],[[500755,544260],[92,2515],[-132,4775],[-564,-668],[-1498,2472],[-1767,4265],[904,-3637],[1675,-2135],[-283,-1706],[850,-738],[478,-1638],[-200,-2189],[-850,-921],[-1222,72],[2049,-2460],[468,1993]],[[601901,519752],[-90,1900],[-625,906],[-572,2734],[-131,6867],[-621,-468],[-287,-4607],[330,-2413],[1272,-3754],[308,-1698],[416,533]],[[513014,566127],[-597,1548],[-7,-3130],[530,-1266],[74,2848]],[[790472,578337],[-362,1765],[-571,235],[-705,2460],[-782,165],[298,-1542],[1254,-2269],[868,-814]],[[540322,581615],[423,1277],[-906,-197],[-187,-1535],[670,455]],[[603570,574593],[283,-961],[568,2872],[-365,1103],[-1012,-223],[-261,-2127],[787,-664]],[[785773,614477],[-978,844],[245,-1141],[733,297]],[[589539,639047],[271,-551],[1877,3887],[-268,2056],[-1140,-4893],[-926,179],[-1993,-3425],[-939,-3194],[577,837],[905,2959],[1140,2091],[496,54]],[[752585,676072],[-1025,1705],[79,-2453],[946,748]],[[824182,677333],[-1536,690],[124,2955],[-646,-2607],[368,-1217],[1202,-1253],[488,1432]],[[835004,688893],[-392,-39],[-672,2672],[-642,-830],[-27,-1781],[1157,-714],[576,692]],[[750960,685556],[1857,1866],[-196,502],[-1819,-456],[158,-1912]],[[632245,686535],[-2077,1346],[-107,-1133],[2184,-213]],[[738577,687953],[-1352,630],[-67,-767],[1247,-724],[172,861]],[[748104,693080],[-970,734],[-1117,-439],[1587,-1436],[500,1141]],[[831685,699001],[-162,1684],[-748,-1323],[910,-361]],[[749194,703001],[333,-1261],[388,1327],[-721,-66]],[[621884,698374],[-912,2321],[-189,-2590],[862,-635],[239,904]],[[741047,689360],[-98,1041],[-960,-2790],[1058,1749]],[[731194,691121],[-361,1115],[-731,-517],[1092,-598]],[[827614,691459],[-1739,1048],[424,-1368],[1315,320]],[[770743,711929],[-1115,128],[456,-1016],[659,888]],[[771908,711114],[-346,1481],[-599,-1257],[945,-224]],[[779868,722801],[-1328,2011],[-885,400],[-850,-1816],[857,-1378],[2071,-529],[135,1312]],[[626850,728317],[-640,381],[-219,2714],[-698,0],[719,-6148],[475,-543],[895,1727],[-532,1869]],[[619412,731894],[916,848],[-73,1920],[-2128,-401],[-480,-1454],[1470,-136],[295,-777]],[[592937,734903],[-423,1618],[-409,-2100],[1042,-1478],[390,1123],[-600,837]],[[750778,745730],[-534,277],[670,-3123],[-136,2846]],[[624971,744746],[897,-2169],[941,273],[-774,1684],[-1064,212]],[[688863,749129],[401,-1223],[890,346],[-1291,877]],[[829457,701243],[710,1093],[-1035,1086],[-870,-1953],[24,-1928],[1171,1702]],[[620169,704535],[573,1320],[-1115,1822],[542,-3142]],[[868915,771622],[-4,576]],[[868911,772198],[-637,875],[-1411,-56]],[[866863,773017],[-184,-432]],[[866679,772585],[179,-2794],[823,-1261],[1234,3092]],[[728146,775844],[-613,3476],[-1063,599],[-286,-2349],[1962,-1726]],[[719418,781676],[-2373,-910],[-4556,241],[-1862,1081],[-1109,-477],[-2226,87],[-1475,-1921],[-313,-1431],[-1495,-3136],[1878,-3895],[-53,3183],[525,674],[-32,2094],[1371,615],[324,1592],[1433,1380],[576,-606],[1742,282],[2404,-597],[904,269],[1633,-929],[2424,160],[922,2078],[-642,166]],[[598330,772830],[-1552,3481],[-238,1599],[-2940,519],[1851,-1185],[1771,-2191],[248,-1615],[860,-608]],[[519093,779803],[-793,572],[-699,-995],[1492,423]],[[598060,786080],[-477,2063],[-370,-1496],[-2947,-274],[-691,-4032],[1396,3312],[1118,595],[1023,-674],[948,506]],[[527029,786288],[-240,234]],[[526789,786522],[-1282,617]],[[525507,787139],[1032,-944]],[[526539,786195],[490,93]],[[825635,792558],[1620,3199],[-490,1285],[-1798,-2969],[668,-1515]],[[757740,790146],[-2251,-998],[622,-1428],[1629,2426]],[[734765,788675],[-2596,2000],[-262,2671],[340,1138],[1998,1271],[-1664,2431],[446,1097],[-1459,-559],[2041,-2300],[-1745,-2249],[-101,-3485],[-1101,-120],[3342,-3218],[761,1323]],[[760553,795780],[-1500,966],[-1096,-395],[1480,-1392],[1116,821]],[[592130,795130],[-1395,3067],[-92,-920],[-2031,776],[2414,-2351],[1104,-572]],[[662809,774782],[-63,-283]],[[662746,774499],[-880,-3290],[-81,-2711],[618,-374],[836,1696],[-121,4023]],[[663118,773843],[104,375]],[[663222,774218],[640,1702],[1079,-1224],[186,-2401]],[[665127,772295],[-179,-732]],[[664948,771563],[-498,-1754],[1691,-3336],[620,1333],[58,2571]],[[666819,770377],[23,2136]],[[666842,772513],[-175,2227],[-643,9],[218,1988],[1582,-394],[2240,2499],[162,813],[-1538,1592],[-1140,68],[-663,-1693],[1804,3],[-214,-1962],[-2400,103],[-1211,-2325],[-46,1648],[-1706,-751],[-303,-1556]],[[742708,752661],[-166,982],[-1185,542],[-376,-1675],[1727,151]],[[703585,752117],[-1682,832],[-17,-1401],[1699,569]],[[660075,754430],[-215,659]],[[659860,755089],[-770,252],[-497,-1984]],[[658593,753357],[-112,-1155]],[[658481,752202],[812,-1186],[1311,750],[-529,2664]],[[553592,755111],[-214,-628]],[[553378,754483],[495,-417]],[[553873,754066],[-86,1194]],[[553787,755260],[-195,-149]],[[717506,757030],[-149,861],[-2533,-379],[-3085,-1239],[102,-788],[1829,-813],[1647,-51],[2189,2409]],[[516297,816124],[-1241,1845],[-1057,-3142],[1265,-546],[1033,1843]],[[703851,819125],[51,1726],[-1161,44],[1110,-1770]],[[634409,825632],[1183,-1358],[145,-1724],[943,-1659],[1879,699],[-2208,287],[365,2372],[1415,1722],[-1964,-1244],[-1308,1336],[874,1313],[401,3009],[1077,272],[1377,1644],[3786,1378],[-3237,-375],[-2032,-734],[-1385,-1939],[-318,-1994],[-914,-462],[-79,-2543]],[[554900,827208],[1780,1468],[-1158,167],[-1782,-2619],[1160,984]],[[787591,822480],[-1194,3030],[438,1154],[-296,2720],[582,2473],[-947,5896],[-1558,234],[-2102,-1663],[761,-2664],[18,2978],[1444,772],[1153,-552],[976,-4508],[-665,-3079],[514,-1751],[-596,-2483],[754,-2311],[277,-3377],[441,3131]],[[713185,829568],[-35,-687],[2794,-326],[857,1577],[-1329,746],[-2287,-1310]],[[607446,848975],[857,2297],[-657,304],[-3077,3613],[-399,-1849],[1374,100],[216,-1895],[-2564,1819],[3079,-4609],[1171,220]],[[587585,849699],[-646,1460],[-1172,-1555],[1818,95]],[[576897,854139],[-443,162]],[[576454,854301],[-1548,-1193],[1826,-4688]],[[576732,848420],[152,25]],[[576884,848445],[395,2763],[-382,2931]],[[541480,852979],[-1184,-1323],[-1071,-3768],[344,-681],[1911,5772]],[[545566,856089],[-2918,-151],[1284,-796],[1634,947]],[[538995,854750],[20,1368],[-2508,170],[280,-2440],[-1181,897],[-539,-4145],[1156,1315],[2108,700],[664,2135]],[[574388,886199],[1329,-1605],[1852,1547],[-3181,58]],[[808666,875704],[-89,-3371],[864,42],[70,3529],[-604,2084],[1769,920],[-988,-1704],[1908,-641],[736,1839],[-1092,1421],[-2668,-1308],[-1701,923],[-963,1693],[843,-3241],[1968,-830],[-53,-1356]],[[601163,868433],[-2027,3839],[141,2843],[-3354,2343],[246,-1205],[2030,-1123],[560,-1699],[-1394,-144],[-1142,1644],[-29,-4945],[2298,-1770],[1032,-2898],[1572,2012],[67,1103]],[[575103,870033],[-456,1418],[-1619,-283],[2075,-1135]],[[565308,868591],[687,2172],[1572,1448],[-398,753],[-1313,-1732],[-548,-2641]],[[574860,877079],[-457,-1440],[-2353,-868],[458,2157],[-1308,-1179],[806,-1433],[-2040,-5057],[865,-1794],[573,2204],[-180,2069],[654,1513],[3076,2356],[-94,1472]],[[581993,872761],[1135,1841],[-1847,695],[192,-2281],[-3509,461],[-859,2011],[1692,22],[-591,2050],[-2474,1261],[823,-1749],[-36,-2598],[978,-1509],[2521,-1952],[-1514,-494],[-577,-1386],[-660,1288],[-1517,-2391],[2228,-540],[-349,-980],[2557,1064],[-381,2511],[1023,-550],[1885,1843],[-720,1383]],[[597257,880953],[-1849,2047],[1683,-3970],[166,1923]],[[592892,880587],[-374,-949],[2109,-325],[-1735,1274]],[[583315,879692],[-2504,1663],[836,-2316],[1169,-221],[1433,-1873],[-934,2747]],[[590325,892960],[-1483,2117],[-1322,-558],[2805,-1559]],[[550002,895031],[-92,1328],[-1727,-154],[1819,-1174]],[[580190,897575],[-2219,-1640],[1544,169],[675,1471]],[[755379,910475],[-7289,-911],[1729,-560],[6655,1098],[-1095,373]],[[744889,916455],[-2083,2995],[881,-2910],[1202,-85]],[[780618,942300],[1406,549],[-1870,3023],[3704,-1323],[-412,1551],[8865,1674],[-2125,1828],[-648,-1651],[-2725,-820],[-4020,-206],[-4747,1302],[1105,-1681],[-2744,-1047],[3870,-856],[341,-2343]],[[579019,915436],[-3570,-2388],[840,-960],[2478,660],[252,2688]],[[748524,915745],[1750,953],[4056,-718],[-3761,1228],[-3777,-1505],[1732,42]],[[589764,898704],[-271,1975],[-1256,137],[1527,-2112]],[[584338,890689],[964,-760],[2410,692],[-3374,68]],[[603422,861159],[2045,-467],[-643,1444],[-1402,-977]],[[582879,867448],[1589,-1766],[1945,-4630],[961,-1418],[679,1946],[2225,-351],[1233,2762],[-1159,2814],[-2642,1894],[-2011,2000],[-2820,-3251]],[[779963,809216],[-1131,409],[-518,-5760],[697,440],[952,4911]],[[756434,802727],[791,-1756],[2112,1637],[-1302,2231],[-1826,-1453],[225,-659]],[[692465,805016],[-1413,-1035],[-246,-1466],[1570,-68],[-579,1015],[668,1554]],[[805177,833755],[-802,1378],[-1061,-1398],[-62,-2025],[-3079,-7966],[-1466,-2430],[-1286,-1172],[-1723,-3464],[-1088,-858],[-2485,-3589],[-3818,-1032],[1665,-1374],[1211,-59],[2699,1251],[1080,1513],[354,2173],[4538,2709],[1981,3088],[750,253],[-264,2743],[1133,362],[866,1690],[-183,1343],[1040,6864]],[[292109,640353],[-1063,1606],[183,1013],[880,-2619]],[[752159,635584],[-729,-143],[459,1554],[-494,3009],[472,-131],[544,-1945],[-252,-2344]],[[284284,648382],[233,-3023],[-549,79],[-635,2768],[951,176]],[[856273,662620],[-854,-1163],[-828,-2143],[211,2010],[1460,2654],[11,-1358]],[[285484,658185],[54,3465],[-910,2455],[1298,-2211],[-442,-3709]],[[281964,663072],[1525,90],[-2020,-1408],[495,1318]],[[656077,664210],[-649,-1303],[-1687,-315],[2408,2105],[-72,-487]],[[836135,638730],[-470,-4119],[-718,2555],[-710,1103],[-704,3598],[241,3313],[1308,4559],[1141,3284],[1536,1437],[932,-1787],[-805,-4998],[-450,-4183],[-505,-2710],[-796,-2052]],[[284045,651095],[-4,-1445],[-833,-1043],[-1084,2015],[766,2339],[512,370],[643,-2236]],[[450460,673524],[-463,1570],[712,168],[-249,-1738]],[[188325,676558],[-210,-1398],[-654,463],[122,1897],[742,-962]],[[185678,676836],[-1180,2138],[-19,947],[1070,-1606],[129,-1479]],[[838507,691292],[-1462,897],[355,668],[1107,-1565]],[[634098,680225],[-539,1034],[287,1066],[252,-2100]],[[457220,671475],[32,-1616],[-892,-536],[76,2191],[784,-39]],[[859589,671840],[-799,241],[1458,1587],[-659,-1828]],[[454626,672853],[-899,-2209],[-687,1970],[2184,1117],[-598,-878]],[[460564,671605],[674,3389],[352,-926],[-279,-1965],[-747,-498]],[[577340,717578],[-351,1436],[1432,1552],[-400,-2182],[-681,-806]],[[594456,712459],[-2952,-2899],[-1368,910],[-367,1326],[1099,1290]],[[590868,713086],[636,1300],[1436,-323],[2790,1526],[-1447,-1786],[173,-1344]],[[874812,707856],[-735,35],[935,1481],[-200,-1516]],[[566256,715245],[1279,-1017],[1135,362],[2095,-703],[748,-969],[1175,429],[212,-1009],[-4012,-653],[-253,916],[-2292,930],[-934,1004],[847,710]],[[543268,731152],[-1325,-4513],[541,-2638],[-505,-1929],[-1697,657],[-997,1807],[-659,-18],[-4083,4261],[830,2152],[467,-878],[715,921],[1449,-1123],[2290,265],[944,747],[2030,289]],[[884288,728792],[-272,1022],[715,1904],[198,-1484],[-641,-1442]],[[557256,732117],[414,-1853],[-980,1577],[566,276]],[[572483,731138],[-689,2109],[873,-197],[-184,-1912]],[[300269,747979],[-3587,-2408]],[[296682,745571],[-2327,-92]],[[294355,745479],[1273,1664],[4641,836]],[[526334,758316],[195,-4003],[-1012,-4413],[-1052,1205],[-672,4558],[409,1138],[2132,1515]],[[508736,740452],[844,-171],[-1045,-2725],[-1949,1847],[2188,2117],[-38,-1068]],[[555771,738334],[-1191,1739],[527,556],[664,-2295]],[[565042,735526],[1899,-1704],[491,-2672],[-1436,1073],[-1407,2370],[-1061,411],[1514,522]],[[573361,737722],[483,-1765],[-1176,-33],[-879,1032],[1572,766]],[[570660,741596],[-224,-1038],[-831,1135],[1055,-97]],[[511926,740758],[-1253,797],[1065,332],[188,-1129]],[[526755,746921],[481,-2266],[-523,-6785],[-363,-1273],[-1194,591],[-486,-1933],[-647,81],[-640,1653],[359,3754],[-211,2686],[-782,2127],[96,1550],[965,-374],[1824,2411],[1121,-2222]],[[864373,703794],[1134,295],[355,-889],[-481,-1352],[998,-116],[222,-2433],[-679,-1487],[-1097,-7039],[-1811,-2308],[289,1504],[-373,2662],[-184,-3198],[-1078,671],[337,1834],[-355,2899],[1238,3131],[-717,2804],[-708,73],[133,-1502],[-967,-746],[-685,3027],[222,762],[1957,1597],[331,1186],[1304,221],[615,-1596]],[[452247,699446],[1382,-656],[-1327,-214],[-55,870]],[[850907,701549],[29,1403],[1566,324],[-78,-1031],[-1517,-696]],[[873214,707667],[778,-176],[282,-2406],[-1005,-1257],[-543,-2139],[-1527,1562],[-961,-894],[-858,-3069],[-931,-471],[-408,913],[-229,3044],[-909,-535],[1549,2073],[392,1792],[971,-386],[1245,532],[344,1305],[1027,717],[783,-605]],[[892303,749827],[628,190],[20,-4701],[929,-1896],[502,-2646],[-214,-4344],[-674,-808],[-530,-3381],[-997,-393],[-501,-2300],[301,-2858],[-190,-2756],[-946,-2958],[-16,-2628],[701,-1980],[-1157,-1272],[-114,-1441],[-1378,-2177],[-261,2353],[722,1444],[-699,697],[-552,-3057],[-1074,805],[-454,-2600],[-690,-1303],[-97,2107],[-626,661],[-1078,-2904],[-1794,403],[-1338,-483],[592,1124],[-1084,198],[-91,1503],[-886,-2259],[891,-2099],[-1455,-872],[-1149,-3644],[-1287,-50],[-771,2042],[-209,2313],[791,1256],[-1791,1569],[-1477,-400],[-667,-1010],[-2294,-1332],[-2512,-447],[-255,-2300],[-1127,1264],[-2282,-452],[237,2469],[971,122],[1395,1856],[2964,4650],[1259,-311],[2327,477],[2667,1231],[424,-1313],[981,-132],[1153,1567],[-247,1321],[1922,4452],[404,3792],[1331,829],[-1107,-2074],[257,-1984],[905,-396],[477,1074],[2238,1581],[1570,3706],[1432,1769],[1766,7515],[51,2004],[-679,761],[567,2590],[-254,1680],[914,1246],[372,2494],[671,-204],[143,-1790],[1221,-65],[226,2141],[-1109,-621],[379,2173],[812,-788]],[[877937,714367],[-468,-2295],[903,1565],[-435,730]],[[328330,795571],[-1162,-87]],[[327168,795484],[-3740,1896]],[[323428,797380],[-886,1532]],[[322542,798912],[-1542,1007],[857,675]],[[321857,800594],[3536,-1399],[2892,-2500]],[[328285,796695],[45,-1124]],[[915797,775121],[-60,1171],[1897,2214],[568,-30],[-2405,-3355]],[[323107,780571],[1533,-828]],[[324640,779743],[2683,385]],[[327323,780128],[389,-389],[-2374,-2489]],[[325338,777250],[-485,1590]],[[324853,778840],[-622,-690]],[[324231,778150],[-1339,1448],[-978,163]],[[321914,779761],[-770,1278]],[[321144,781039],[1096,2492]],[[322240,783531],[-262,-1695]],[[321978,781836],[1129,-1265]],[[331391,775898],[539,2552]],[[331930,778450],[1778,-263]],[[333708,778187],[103,-1152]],[[333811,777035],[-1550,-1839]],[[332261,775196],[-2494,-479]],[[329767,774717],[-588,2178],[1737,5066]],[[330916,781961],[1283,1226]],[[332199,783187],[213,-1397],[-681,-3528],[-1340,-2777],[1000,413]],[[906131,768341],[1001,-340],[-1266,-1152],[-1459,-2374],[1724,3866]],[[899511,766086],[2706,-1047],[1512,2332],[-671,-3374],[666,-2736],[1367,494],[-911,-1254],[-2429,-1347],[-1837,-389],[-1500,-2739],[-536,-2481],[-2024,1527],[-1823,1903],[-1236,-191],[-1169,-1213],[-1404,1287],[-459,-1334],[2322,-3135],[-1364,62],[-1420,-2324],[-426,908],[314,1993],[-799,2812],[196,1550],[1653,2374],[-261,1500],[2511,-614],[281,2626],[686,2232],[382,4129],[-553,2604],[985,2095],[2129,-4091],[1623,-2502],[1489,-1657]],[[295648,774097],[-1094,-164]],[[294554,773933],[1345,1560],[-251,-1396]],[[912776,773199],[-1906,-1939],[-710,-78],[-1251,-2510],[-861,-884],[969,2677],[1775,2189],[1984,545]],[[9463,811999],[432,-667]],[[9895,811332],[-1456,-891]],[[8439,810441],[1024,1558]],[[6513,810872],[1645,1335],[-236,-1097],[-1409,-238]],[[13068,812920],[2748,1149]],[[15816,814069],[-104,-820],[-2644,-329]],[[33432,820758],[-3061,-3029]],[[30371,817729],[1969,3695]],[[32340,821424],[1032,595]],[[33372,822019],[60,-1261]],[[134017,819872],[828,-2926]],[[134845,816946],[-375,-732]],[[134470,816214],[1025,-2515],[-2620,3729],[58,1281],[-1119,819]],[[131814,819528],[2203,344]],[[142910,818356],[118,-2495]],[[143028,815861],[-470,-1356],[-187,2806]],[[142371,817311],[-426,-530],[-772,2038]],[[141173,818819],[401,1553],[1336,-2016]],[[34659,820350],[-795,281],[1868,1201]],[[35732,821832],[440,2586]],[[36172,824418],[1857,-1573],[-1281,-1312],[-2089,-1183]],[[139308,819707],[-1857,2230]],[[137451,821937],[1590,-639]],[[139041,821298],[267,-1591]],[[131512,825393],[1448,-552]],[[132960,824841],[1295,634]],[[134255,825475],[-953,-5192]],[[133302,820283],[-2316,1437]],[[130986,821720],[-577,1603]],[[130409,823323],[11,2256]],[[130420,825579],[1092,-186]],[[531559,829920],[1050,-499],[-783,-1059],[-1172,856],[905,702]],[[883167,831111],[738,-350],[-1514,-2253],[-551,1304],[318,1916],[1009,-617]],[[45900,830448],[327,-1453]],[[46227,828995],[-4071,-1875]],[[42156,827120],[-178,1117],[994,1619]],[[42972,829856],[1839,938]],[[44811,830794],[1089,-346]],[[962916,829609],[-14,-859],[-2483,3557],[1457,103],[1040,-2801]],[[541910,830692],[-1121,476],[225,1152],[896,-1628]],[[534913,835213],[-983,-1888],[-980,-4110],[-579,2453],[-1021,105],[-854,3064],[1378,1314],[958,-1456],[130,1602],[1978,569],[-27,-1653]],[[129708,833783],[-959,-1626]],[[128749,832157],[44,1600],[915,26]],[[529569,834175],[390,-2823],[-2213,177],[-356,2087],[2179,559]],[[136169,833460],[-581,-1676]],[[135588,831784],[-721,1199]],[[134867,832983],[-874,-1440],[-232,1485]],[[133761,833028],[614,2461]],[[134375,835489],[988,732]],[[135363,836221],[806,-2761]],[[482975,836075],[-490,-1917],[-389,1335],[879,582]],[[563676,853234],[-1059,-811],[-1350,1503],[1475,815],[934,-1507]],[[128983,838496],[1009,-115]],[[129992,838381],[2860,-4972]],[[132852,833409],[-1162,-96]],[[131690,833313],[1708,-1515],[-438,-2939]],[[132960,828859],[-1800,1990]],[[131160,830849],[-892,1846]],[[130268,832695],[195,1361],[-1797,1157]],[[128666,835213],[317,3283]],[[280734,838063],[-2959,-1979]],[[277775,836084],[2024,3959]],[[279799,840043],[935,-1980]],[[132963,836150],[-1464,799]],[[131499,836949],[779,2491]],[[132278,839440],[868,-1507],[-183,-1783]],[[483950,838526],[-1108,-330],[205,2116],[903,-1786]],[[127916,837243],[-665,-301]],[[127251,836942],[-512,4513]],[[126739,841455],[641,555],[1123,-1671],[-587,-3096]],[[76620,850469],[855,-1179],[-1868,-861]],[[75607,848429],[-1666,423],[1500,1950]],[[75441,850802],[1179,-333]],[[552990,847363],[-730,-768],[261,-1825],[-2115,-2831],[-27,3770],[1113,1623],[1498,31]],[[75283,847292],[1304,11]],[[76587,847303],[590,-1474]],[[77177,845829],[-2940,-2078]],[[74237,843751],[-1341,-2179],[-1352,1686]],[[71544,843258],[-47,-1370]],[[71497,841888],[-1237,2510]],[[70260,844398],[475,1327]],[[70735,845725],[1404,422]],[[72139,846147],[349,1121]],[[72488,847268],[1055,-526],[1011,1426]],[[74554,848168],[729,-876]],[[545912,838208],[-323,1649],[1703,4598],[257,-149],[-1637,-6098]],[[125084,844493],[969,-3752]],[[126053,840741],[-93,-2907]],[[125960,837834],[-614,2625],[-1265,896],[363,1217]],[[124444,842572],[-838,1284],[-863,-1390]],[[122743,842466],[69,1825]],[[122812,844291],[1226,1278],[1046,-1076]],[[129538,842431],[1145,-729]],[[130683,841702],[-719,-2463]],[[129964,839239],[-1084,-3]],[[128880,839236],[-1046,3232]],[[127834,842468],[1704,-37]],[[482931,845403],[1218,-1399],[-676,-1326],[-2254,2354],[1428,1237],[284,-866]],[[125887,849293],[1223,-106],[-74,-1536]],[[127036,847651],[948,-3245]],[[127984,844406],[-1849,-1451],[291,2312],[-1170,4625],[631,-599]],[[123296,848287],[1697,351]],[[124993,848638],[197,-3377]],[[125190,845261],[-1573,1073],[-539,-1435],[-2435,3271],[685,1462],[1486,294],[482,-1639]],[[482781,850488],[-630,-2027],[-1476,-1606],[-153,2835],[2151,1624],[108,-826]],[[954541,851909],[350,2440],[2254,1221],[120,-1988],[-2724,-1673]],[[562826,852016],[1876,-816],[-1563,-1498],[-1507,-452],[-904,2031],[2098,735]],[[891694,943123],[-2524,1095],[2077,531],[447,-1626]],[[814964,945499],[-1680,-1810],[-3553,1529],[1614,1161],[3619,-880]],[[907764,951248],[4547,253],[119,-836],[4524,-315],[1507,-1627],[-2915,-1019],[-4179,314],[-5400,2207],[1797,1023]],[[210778,949266],[-2132,660]],[[208646,949926],[1503,1671]],[[210149,951597],[1956,-1581]],[[212105,950016],[-1327,-750]],[[240159,949216],[-12,-1995]],[[240147,947221],[-3196,-291]],[[236951,946930],[-5173,2064],[2469,3189]],[[234247,952183],[3455,383]],[[237702,952566],[2457,-3350]],[[449998,951464],[1151,-2456],[-3013,53],[-514,1881],[2376,522]],[[183799,965371],[-3325,1261]],[[180474,966632],[1941,652]],[[182415,967284],[1384,-1913]],[[250463,962485],[-3651,709],[-5,1308]],[[246807,964502],[2715,-79]],[[249522,964423],[941,-1938]],[[560022,970354],[4190,-3580],[4959,-1392],[-5833,-2848],[-192,1561],[-4675,-583],[1676,2859],[-3585,3503],[3460,480]],[[193893,964006],[-4872,-1067]],[[189021,962939],[-3367,1103],[-63,2262]],[[185591,966304],[5502,1043],[4304,-54],[-3358,-1451],[1854,-1836]],[[209605,962901],[-1869,-922],[-2364,3218],[4233,-2296]],[[234765,965592],[5282,-126]],[[240047,965466],[176,-1756],[-6854,57]],[[233369,963767],[1396,1825]],[[200410,955317],[-468,-1498]],[[199942,953819],[3136,-133]],[[203078,953686],[1376,1646]],[[204454,955332],[2543,-1863]],[[206997,953469],[-1060,-3283]],[[205937,950186],[-3586,-1567],[-4660,816]],[[197691,949435],[-5861,-2524],[-4385,-1315]],[[187445,945596],[-2762,79]],[[184683,945675],[-1718,1990]],[[182965,947665],[3602,1240],[3235,261]],[[189802,949166],[1770,1228],[-7438,-937]],[[184134,949457],[-847,2167]],[[183287,951624],[-1209,-2052]],[[182078,949572],[-3547,-710]],[[178531,948862],[-5198,1799]],[[173333,950661],[1238,1192]],[[174571,951853],[2992,119]],[[177563,951972],[2654,1261],[-5287,-618]],[[174930,952615],[1909,1655]],[[176839,954270],[-755,1141],[2858,2156]],[[178942,957567],[3852,83]],[[182794,957650],[1029,-1449]],[[183823,956201],[3128,-31],[4569,-3869]],[[191520,952301],[5461,-249],[-1970,2112]],[[195011,954164],[1016,1458]],[[196027,955622],[-2333,1824],[2588,2032]],[[196282,959478],[2421,-133],[-127,-1769]],[[198576,957576],[1834,-2259]],[[889023,953962],[2131,-1174],[1859,3000],[2709,-1385],[3404,-235],[4359,-1648],[-1033,-1876],[-2398,-1329],[-3584,1736],[758,1909],[-2545,5],[1474,-3125],[1420,-965],[-1820,-888],[-1349,1012],[-4776,-855],[-1839,585],[-1407,-1713],[-2797,835],[-2429,1933],[141,3707],[4586,2650],[3136,-2179]],[[877634,951478],[-1379,-120],[685,2700],[694,-2580]],[[238068,960381],[3611,-1730]],[[241679,958651],[4696,358],[5273,-2913]],[[251648,956096],[-5202,-173]],[[246446,955923],[5762,-2357],[-1225,-1167],[2026,-659]],[[253009,951740],[4609,971],[3302,-685]],[[260920,952026],[5935,1877]],[[266855,953903],[4940,71]],[[271795,953974],[5088,-1196]],[[276883,952778],[1910,-2546]],[[278793,950232],[-2009,-875],[2656,-794]],[[279440,948563],[-2434,-1991]],[[277006,946572],[-4253,-622]],[[272753,945950],[-9035,772]],[[263718,946722],[-425,-523],[-8913,-145]],[[254380,946054],[231,1722]],[[254611,947776],[-4179,-1399],[-5881,1449]],[[244551,947826],[-1293,3277]],[[243258,951103],[995,1846],[-2842,4124],[-6062,-532]],[[235349,956541],[-4365,2738]],[[230984,959279],[243,1454]],[[231227,960733],[3111,545]],[[234338,961278],[3730,-897]],[[168348,952708],[4562,2934],[350,-868],[-2743,-2669],[-2169,603]],[[228608,957739],[1014,-6202]],[[229622,951537],[-940,-1733]],[[228682,949804],[-2484,-698]],[[226198,949106],[-4627,-9]],[[221571,949097],[-1380,2007]],[[220191,951104],[3167,1830]],[[223358,952934],[-8195,-840]],[[215163,952094],[1662,2193]],[[216825,954287],[235,3289]],[[217060,957576],[5106,-2959]],[[222166,954617],[-2419,3175]],[[219747,957792],[6056,1294]],[[225803,959086],[2805,-1347]],[[216034,955063],[-3020,-1484],[-2878,2477]],[[210136,956056],[4908,588]],[[215044,956644],[990,-1581]],[[211047,958429],[2699,-788]],[[213746,957641],[-3387,-733]],[[210359,956908],[688,1521]],[[448381,955226],[-1397,2299],[981,1254],[416,-3553]],[[179024,963052],[-2040,-1550]],[[176984,961502],[181,-2906],[-2593,-1855]],[[174572,956741],[-2406,880]],[[172166,957621],[666,2001]],[[172832,959622],[-2809,-1607]],[[170023,958015],[-1046,-2290],[-989,1266],[-1080,-2852]],[[166908,954139],[-3074,957]],[[163834,955096],[-3836,-451]],[[159998,954645],[97,2707],[2089,239],[7010,5116]],[[169194,962707],[5031,50],[2544,1359]],[[176769,964116],[2255,-1064]],[[546629,978121],[3249,-1200],[3148,-3242],[2981,-67],[3406,-2402],[-4500,-696],[-3693,-3541],[-4830,-8566],[-6149,3672],[-1155,2163],[8228,1383],[-501,675],[-6247,-859],[-2565,1550],[8601,1910],[16,1855],[-3792,-1128],[-263,1824],[-2377,-626],[475,-1545],[-4133,-1051],[-3823,2839],[1391,1114],[-1930,2245],[-1033,-911],[-950,3951],[6037,-660],[2550,-2047],[1767,2719],[4725,-4843],[-1329,4150],[2696,1334]],[[306975,996546],[8048,-431]],[[315023,996115],[-2237,-1635]],[[312786,994480],[6922,1379],[5055,-1988]],[[324763,993871],[4467,-581]],[[329230,993290],[-1943,-2511]],[[327287,990779],[-6660,-1834],[-5699,-695]],[[314928,988250],[-4700,-2105]],[[310228,986145],[7173,1381]],[[317401,987526],[699,-1242]],[[318100,986284],[-8741,-3591]],[[309359,982693],[-7659,-5431],[-5790,-32],[17,-1548]],[[295927,975682],[-4981,-439]],[[290946,975243],[-4554,541]],[[286392,975784],[6573,-2724],[-11248,133]],[[281717,973193],[1942,-786]],[[283659,972407],[4519,382]],[[288178,972789],[5063,-1675]],[[293241,971114],[-1237,-1062]],[[292004,970052],[-4153,-140],[3278,-1146]],[[291129,968766],[-1868,-1883],[-5963,-378]],[[283298,966505],[-177,-2530]],[[283121,963975],[-2203,-1105]],[[280918,962870],[-6244,-373]],[[274674,962497],[4934,-659]],[[279608,961838],[334,-1317]],[[279942,960521],[2589,247]],[[282531,960768],[12,-2408],[-6683,-2339]],[[275860,956021],[-1334,1992]],[[274526,958013],[-3776,1247],[824,-1525]],[[271574,957735],[-4590,-75]],[[266984,957660],[-3488,-880]],[[263496,956780],[-2707,772]],[[260789,957552],[-6334,-177]],[[254455,957375],[-3261,515]],[[251194,957890],[195,1984],[3060,1641]],[[254449,961515],[4295,418],[-3453,3228]],[[255291,965161],[2992,1025]],[[258283,966186],[3971,-2555]],[[262254,963631],[2361,-592]],[[264615,963039],[2664,1016]],[[267279,964055],[-4194,157]],[[263085,964212],[-717,2184]],[[262368,966396],[1453,2279]],[[263821,968675],[-3315,-1370]],[[260506,967305],[827,1550]],[[261333,968855],[-4533,-984],[2067,3540]],[[258867,971411],[5011,818]],[[263878,972229],[4812,-841]],[[268690,971388],[2313,790]],[[271003,972178],[-3158,889],[-4206,3309],[-3697,1380]],[[259942,977756],[316,2809]],[[260258,980565],[7176,-535],[5189,-3001]],[[272623,977029],[2349,-174],[-5420,3465]],[[269552,980320],[8084,1485]],[[277636,981805],[8891,2071]],[[286527,983876],[-4724,256],[733,1459],[-7556,-3038]],[[274980,982553],[-8525,-584],[-9038,672]],[[257417,982641],[-4421,805]],[[252996,983446],[1412,1150]],[[254408,984596],[-4262,1025],[4079,2322]],[[254225,987943],[-8122,80]],[[246103,988023],[2535,1771]],[[248638,989794],[7920,1232]],[[256558,991026],[7206,-606]],[[263764,990420],[-4267,1210],[4678,1554]],[[264175,993184],[15200,-3524]],[[279375,989660],[-8407,3393]],[[270968,993053],[4004,2085],[6281,-591]],[[281253,994547],[-3906,1373]],[[277347,995920],[20398,1008],[9230,-382]],[[38512,862457],[1128,-411],[383,-2376]],[[40023,859670],[-1431,-816],[-2868,1381]],[[35724,860235],[-825,1173]],[[34899,861408],[1666,62]],[[36565,861470],[1947,987]],[[89622,859078],[1542,3229]],[[91164,862307],[539,-616]],[[91703,861691],[-2081,-2613]],[[319909,868276],[-1665,1681]],[[318244,869957],[1784,75],[-119,-1756]],[[291239,908966],[74,-4127]],[[291313,904839],[-1814,-1504],[-3402,-98]],[[286097,903237],[-836,2602],[1571,3111]],[[286832,908950],[2427,625],[1980,-609]],[[295495,906299],[-2644,266],[-369,1412],[3531,-575]],[[296013,907402],[-518,-1103]],[[639625,914604],[-1775,-1932],[-2665,-750],[-1078,1820],[979,2346],[1650,444],[2889,-1928]],[[543778,910905],[1580,1867],[528,-1442],[-1685,-1444],[-4597,-1176],[3208,2518],[196,2533],[1138,1390],[-368,-4246]],[[542241,913167],[42,-1936],[-1948,99],[1906,1837]],[[279970,912589],[-542,459]],[[279428,913048],[2248,2652]],[[281676,915700],[1021,-396]],[[282697,915304],[-2727,-2715]],[[259456,906015],[-1011,2159]],[[258445,908174],[1496,493]],[[259941,908667],[-485,-2652]],[[291997,909646],[-1442,1047],[1157,724]],[[291712,911417],[285,-1771]],[[304619,875284],[-1192,285]],[[303427,875569],[-1024,1666]],[[302403,877235],[1923,-856]],[[304326,876379],[293,-1095]],[[23714,881749],[2868,349]],[[26582,882098],[2786,-2077],[1975,-223]],[[31343,879798],[-377,-826]],[[30966,878972],[-2572,-459]],[[28394,878513],[-2080,1691]],[[26314,880204],[-3512,269]],[[22802,880473],[912,1276]],[[284615,879658],[-1122,-1024]],[[283493,878634],[-1569,1996]],[[281924,880630],[2232,-120],[459,-852]],[[285098,881443],[641,554],[1267,-1706],[-1908,1152]],[[264112,891353],[853,1104],[7118,-4758]],[[272083,887699],[1039,-2557],[-630,-1075]],[[272492,884067],[2983,348]],[[275475,884415],[1463,-1942]],[[276938,882473],[-2067,-1781]],[[274871,880692],[-3699,1453]],[[271172,882145],[-248,1304]],[[270924,883449],[-2853,1020]],[[268071,884469],[-650,-1693]],[[267421,882776],[-2513,-2986]],[[264908,879790],[-2396,-1008]],[[262512,878782],[-860,3361],[-2894,-777]],[[258758,881366],[-950,574]],[[257808,881940],[2603,2753],[-340,2541]],[[260071,887234],[1146,6745]],[[261217,893979],[1131,1269]],[[262348,895248],[1764,-3895]],[[456824,897085],[1909,906],[-723,-1653],[769,-1905],[3246,-1371],[250,-2408],[-2684,-4130],[-4204,-1983],[-1611,-1456],[-3266,-903],[-2327,-1815],[-4289,883],[-2913,2249],[-3778,-581],[-379,1079],[1907,308],[1139,1934],[-2279,2353],[-4331,405],[5928,1098],[-1615,1054],[1748,1308],[-2939,788],[-2771,-1024],[-1542,550],[953,1536],[2296,-57],[-1255,1892],[1995,-517],[1593,521],[-1873,1701],[1927,432],[2831,-2396],[-70,-3268],[840,-1229],[1083,2319],[792,-516],[272,2739],[2485,-1546],[220,1797],[1681,552],[1885,-2007],[-550,1940],[2074,-1144],[1103,1413],[1125,-422],[577,1867],[1543,402],[1228,-1695]],[[481580,873383],[-254,-1407],[-1249,1749],[1503,-342]],[[279041,874472],[614,-2284]],[[279655,872188],[-957,-2261],[-1657,1029]],[[277041,870956],[677,3109]],[[277718,874065],[1323,407]],[[272221,877686],[-315,-1789]],[[271906,875897],[-2506,-2620]],[[269400,873277],[-1897,-294]],[[267503,872983],[-173,848]],[[267330,873831],[-420,723]],[[266910,874554],[36,302]],[[266946,874856],[1453,2538]],[[268399,877394],[3822,292]],[[0,890205],[0,1449]],[[0,891654],[0,21752],[3127,-1359],[505,-1232],[9297,-5142],[1377,-1952],[-210,-4298],[1476,-1654],[1079,2575],[-1228,1921],[2133,247],[1253,-1043],[3980,-218],[4310,-4517],[1295,-155],[-2118,-1646],[-349,-1445],[-2180,1024],[1101,-1448],[-2368,-319],[-2434,1096],[1587,-1516],[-3,-2233],[-2184,-1017],[-28,-3431],[-2755,898],[-880,1118],[-2993,976],[-1273,1235],[-666,2726],[-2674,845],[-3483,-763],[-2214,5101],[-1880,-1943],[1199,-2969],[-1799,-2663]],[[264792,893213],[-1282,1457]],[[263510,894670],[549,1112]],[[264059,895782],[733,-2569]],[[267428,894527],[-1090,-148]],[[266338,894379],[-803,2127],[1893,-1979]],[[259474,925417],[4151,836]],[[263625,926253],[624,-889]],[[264249,925364],[584,3462]],[[264833,928826],[-3477,2372]],[[261356,931198],[1405,1352],[2929,-960],[-2749,2184]],[[262941,933774],[-843,2092],[1739,3324],[3435,482]],[[267272,939672],[3118,1852],[3482,-563],[3142,-5266],[-2651,-2571]],[[274363,933124],[1030,-2371],[2268,2860]],[[277661,933613],[3730,-253],[2703,-1016]],[[284094,932344],[-1734,2489],[4047,714]],[[286407,935547],[4442,-1421]],[[290849,934126],[1086,-2253]],[[291935,931873],[1695,-297]],[[293630,931576],[-308,-2239]],[[293322,929337],[4172,32],[4572,-1873]],[[302066,927496],[-664,-1520],[1952,-12]],[[303354,925964],[381,-1376]],[[303735,924588],[-1724,-2195]],[[302011,922393],[3684,2042]],[[305695,924435],[4421,-1908]],[[310116,922527],[-1557,-1872]],[[308559,920655],[485,-1573],[1732,2211]],[[310776,921293],[2102,-1660]],[[312878,919633],[395,-1800]],[[313273,917833],[-2479,139]],[[310794,917972],[-1108,-1048]],[[309686,916924],[4839,-1425]],[[314525,915499],[-89,-1090]],[[314436,914409],[-3154,565]],[[311282,914974],[518,-1241]],[[311800,913733],[-795,-2890]],[[311005,910843],[3678,-624]],[[314683,910219],[-325,-1362]],[[314358,908857],[1718,384],[-559,-2228],[3991,824]],[[319508,907837],[2280,-2491]],[[321788,905346],[889,-2126]],[[322677,903220],[2211,-172],[-1837,-2445]],[[323051,900603],[4814,1165],[1858,-2195]],[[329723,899573],[-1564,-1989]],[[328159,897584],[-1918,557]],[[326241,898141],[1560,-2201]],[[327801,895940],[-1663,-5]],[[326138,895935],[-191,-2337]],[[325947,893598],[-1885,-138],[-747,-4080]],[[323315,889380],[-803,1074]],[[322512,890454],[-1832,43]],[[320680,890497],[-2351,3836]],[[318329,894333],[1103,1858]],[[319432,896191],[-2282,-478]],[[317150,895713],[-2671,3310]],[[314479,899023],[-1444,83],[-11,-1576],[-1548,1105]],[[311476,898635],[1904,-2700]],[[313380,895935],[-2983,-568],[3164,-2952],[-578,-496]],[[312983,891919],[1831,-2268],[2023,-521]],[[316837,889130],[2400,-2661]],[[319237,886469],[-265,-2421]],[[318972,884048],[1365,0]],[[320337,884048],[456,-4527],[-1882,2964]],[[318911,882485],[396,-3137]],[[319307,879348],[1016,-1969]],[[320323,877379],[-1331,179]],[[318992,877558],[313,-1697]],[[319305,875861],[-3261,2732],[-529,-474]],[[315515,878119],[-2126,1645]],[[313389,879764],[-1982,2540]],[[311407,882304],[474,-1842]],[[311881,880462],[-2142,1793],[-1159,-131]],[[308580,882124],[2138,-3146],[1293,-466]],[[312011,878512],[4710,-5241]],[[316721,873271],[-768,-2018]],[[315953,871253],[-3287,1676]],[[312666,872929],[-2607,497],[-1955,1008]],[[308104,874434],[-1285,2010],[-1920,112]],[[304899,876556],[-2826,1654],[-1998,2293],[1436,489],[-2131,1165]],[[299380,882157],[-3390,4234]],[[295990,886391],[-4091,2183]],[[291899,888574],[767,-1392],[-6156,-1892],[-2597,767]],[[283913,886057],[-1065,1485]],[[282848,887542],[219,1905]],[[283067,889447],[2041,1524]],[[285108,890971],[95,1520]],[[285203,892491],[4162,-1339],[333,524]],[[289698,891676],[5994,1005]],[[295692,892681],[-543,1668],[-1910,2206],[3424,3318],[2726,3289]],[[299389,903162],[-3020,6597]],[[296369,909759],[-937,-434]],[[295432,909325],[-894,1684],[-1267,6],[-1375,2388],[-4128,-1721]],[[287768,911682],[-427,1878]],[[287341,913560],[1676,127]],[[289017,913687],[463,1704]],[[289480,915391],[-1725,727]],[[287755,916118],[-292,1438]],[[287463,917556],[-2996,958]],[[284467,918514],[-697,2378]],[[283770,920892],[-1223,-106]],[[282547,920786],[-2176,2219],[-781,-1370],[1272,-2340],[-2018,-490]],[[278844,918805],[-5399,1283]],[[273445,920088],[-1608,-1600]],[[271837,918488],[-2646,964]],[[269191,919452],[-2133,-244]],[[267058,919208],[-6842,1081]],[[260216,920289],[-839,1516]],[[259377,921805],[-3546,-884]],[[255831,920921],[-2632,1606]],[[253199,922527],[-1688,3192]],[[251511,925719],[4475,-695]],[[255986,925024],[1957,398]],[[257943,925422],[-2033,1167],[-3353,470]],[[252557,927059],[-2129,1211]],[[250428,928270],[-498,2703]],[[249930,930973],[453,2745]],[[250383,933718],[1662,3893],[4288,3874]],[[256333,941485],[2641,658]],[[258974,942143],[4608,-153]],[[263582,941990],[377,-672],[-3088,-2574],[-1508,-2308]],[[259363,936436],[1647,-6515]],[[261010,929921],[2814,-2475]],[[263824,927446],[-4350,-2029]],[[301559,899263],[867,-1573],[-276,-2234],[1790,1987],[2847,-459],[185,2188],[-2702,1022],[-1976,1627],[-735,-2558]],[[303757,887946],[-344,2276],[-2683,2000],[626,-2975],[-1076,-1051],[1111,-705],[1617,1086],[749,-631]],[[496366,863369],[710,-551],[-621,-1955],[-1077,995],[988,1511]],[[310717,863514],[897,-667]],[[311614,862847],[-1518,-1158],[621,1825]],[[996837,924325],[82,2397],[3080,1817],[0,-3227],[-3162,-987]],[[0,925312],[0,1133]],[[0,926445],[0,1276]],[[0,927721],[0,818]],[[0,928539],[4572,-51],[2283,-1576],[-804,-1158],[-4681,-855],[-1370,413]],[[970001,916943],[-3922,1519],[1581,1060],[2825,-789],[-484,-1790]],[[565112,924262],[-3005,-1783],[-1012,843],[4017,940]],[[429354,924887],[84,-1591],[-2421,-1176],[-919,587],[-3593,-589],[525,2626],[2036,-204],[3214,1072],[1074,-725]],[[948519,912918],[-918,1240],[595,1534],[323,-2774]],[[232499,915545],[1985,-3018]],[[234484,912527],[-2266,-2158]],[[232218,910369],[-2974,431]],[[229244,910800],[-2357,1773],[-3112,444]],[[223775,913017],[-41,1265],[2777,1204]],[[226511,915486],[522,2488],[1326,635]],[[228359,918609],[4140,-3064]],[[286124,914356],[-898,1615]],[[285226,915971],[1805,-297],[-907,-1318]],[[548619,917037],[1392,-541],[-149,-1818],[-2193,-1020],[-442,1990],[1392,1389]],[[277839,916524],[-2223,991]],[[275616,917515],[3289,791],[-1066,-1782]],[[358295,916778],[-935,1790],[1865,-37],[-930,-1753]],[[353524,919101],[1905,-814],[-187,-1885],[-4071,-1377],[-685,1680],[-3040,1027],[1658,1353],[-1370,1466],[1233,757],[2768,-568],[1789,-1639]],[[553486,919822],[936,-570],[-2243,-2318],[-1209,1120],[2398,2841],[118,-1073]],[[667917,919042],[-28,-1237],[-4132,989],[-1333,2215],[1479,1176],[4014,-3143]],[[475129,924399],[1694,1784],[948,-586],[-2642,-1198]],[[647614,926786],[-329,-1618],[-2148,1873],[2477,-255]],[[720837,935555],[-2348,1009],[1411,1197],[937,-2206]],[[715645,933003],[-2018,38],[1869,1974],[2184,-880],[-2035,-1132]],[[347175,935965],[-1408,-1320],[-1920,892],[3328,428]],[[223930,942411],[4686,1360],[-1247,-1303],[-3439,-57]],[[182061,934716],[2661,676]],[[184722,935392],[811,1698]],[[185533,937090],[5384,-1584]],[[190917,935506],[-1737,-2119]],[[189180,933387],[2097,55],[2596,1753]],[[193873,935195],[-1264,2056],[1812,-146],[3639,-2869],[1355,-4433]],[[199415,929803],[2513,851],[-1083,1508]],[[200845,932162],[-1507,5667]],[[199338,937829],[581,1439]],[[199919,939268],[2995,-431],[3162,-1572]],[[206076,937265],[4064,-9341]],[[210140,927924],[-611,-1954]],[[209529,925970],[1711,-2023]],[[211240,923947],[2511,-637],[4131,-3081],[1444,-144]],[[219326,920085],[298,-2344],[-3608,753]],[[216016,918494],[-1075,-1723]],[[214941,916771],[-2050,794]],[[212891,917565],[747,-2805]],[[213638,914760],[1787,1566],[1309,-410],[329,-2270],[-2883,-1187]],[[214180,912459],[-6141,574]],[[208039,913033],[240,953]],[[208279,913986],[-3115,478]],[[205164,914464],[-1439,1645],[-2169,-2592]],[[201556,913517],[-4184,-1436],[-6569,-1290]],[[190803,910791],[-5047,-284]],[[185756,910507],[-1359,2040]],[[184397,912547],[-214,2113]],[[184183,914660],[-4069,413]],[[180114,915073],[-3763,947]],[[176351,916020],[-1640,2248],[-87,1754]],[[174624,920022],[7064,1258],[5428,-517]],[[187116,920763],[2793,495]],[[189909,921258],[-5902,2263]],[[184007,923521],[-6204,-619]],[[177803,922902],[-4434,256]],[[173369,923158],[-1880,1534],[1250,1600],[5340,1323]],[[178079,927615],[846,975]],[[178925,928590],[-5934,-923]],[[172991,927667],[-54,1592],[-3127,163]],[[169810,929422],[-213,1770]],[[169597,931192],[2032,1642]],[[171629,932834],[-448,1607]],[[171181,934441],[2286,1760]],[[173467,936201],[8093,3209]],[[181560,939410],[1318,-609]],[[182878,938801],[152,-2422]],[[183030,936379],[-969,-1663]],[[167398,943794],[1680,-502]],[[169078,943292],[1633,1284]],[[170711,944576],[2859,-77]],[[173570,944499],[5567,-3631]],[[179137,940868],[177,-1066]],[[179314,939802],[-9763,-4471]],[[169551,935331],[-1239,-1918],[-2145,-876],[-1221,-4188]],[[164946,928349],[-3139,-361]],[[161807,927988],[-3299,-2114],[-2974,3493]],[[155534,929367],[-4875,2725]],[[150659,932092],[2154,2668]],[[152813,934760],[419,2893]],[[153232,937653],[2887,4099]],[[156119,941752],[-2498,3437]],[[153621,945189],[9391,1077]],[[163012,946266],[4869,-1760]],[[167881,944506],[-483,-712]],[[222216,942806],[2345,-1271]],[[224561,941535],[4378,925]],[[228939,942460],[1612,-1309],[-753,-1657]],[[229798,939494],[-3234,-2290]],[[226564,937204],[2670,-48]],[[229234,937156],[880,-2070]],[[230114,935086],[1713,331]],[[231827,935417],[-705,-2280]],[[231122,933137],[507,-2844]],[[231629,930293],[-2691,-1209]],[[228938,929084],[-2435,850]],[[226503,929934],[723,-1969]],[[227226,927965],[-2690,-436],[-3965,4650]],[[220571,932179],[-3138,964],[-2749,2773]],[[214684,935916],[2198,1623]],[[216882,937539],[1588,-1840]],[[218470,935699],[2405,158]],[[220875,935857],[1078,1127]],[[221953,936984],[-951,1726]],[[221002,938710],[-2810,1045]],[[218192,939755],[1488,2218]],[[219680,941973],[2536,833]],[[416796,999793],[11551,-1800],[-17216,-1041],[13863,-107],[5218,547],[1814,-1672],[8193,-1670],[-6503,-1827],[-15882,-746],[-643,-1219],[12951,271],[2377,-1778],[3304,1841],[4904,337],[532,-2213],[-5350,-4552],[3170,731],[5321,3046],[7111,-987],[5278,2582],[9341,-1093],[1845,-1333],[-8263,-3915],[-6270,-1125],[2287,-863],[-2586,-1359],[-7113,351],[-1971,-2692],[2375,-711],[580,-3145],[-5225,-3538],[-2199,-4529],[2458,718],[3811,-1143],[-3306,-592],[1247,-1484],[5257,-908],[-475,-2589],[-6755,644],[-2598,-1858],[1279,-1832],[3648,-268],[1651,-2733],[232,-3126],[-2942,500],[-3608,-2031],[2378,485],[879,-1926],[1739,1463],[2112,-2937],[-401,-1158],[-4890,-1025],[-3346,1038],[-3,-1320],[5467,-1275],[-396,-2105],[-4654,-1321],[-2230,452],[-3249,2478],[-1760,-1500],[-5450,-2312],[6017,1789],[1912,-146],[5174,-2843],[-714,-4733],[-4933,2246],[-1558,3193],[-5632,-1907],[5240,906],[290,-2555],[7519,-4104],[-1509,-1447],[2086,-132],[637,-5640],[-3244,-526],[-3059,698],[-2140,3960],[-3676,2064],[-3393,-233],[3748,-549],[43,-1519],[-2710,-1382],[-4404,337],[649,-1826],[-1310,-1317],[5647,-474],[-2924,-1613],[5642,1355],[6546,-1414],[2469,67],[-2028,-1901],[-6039,-3225],[267,-565],[-3469,-2743],[-8078,-2390],[-1720,76],[-2062,-1148],[-2246,63],[-2522,1829],[453,-2643],[-2757,-2160],[-2624,-5335],[-3019,-2818],[-1886,1131],[658,-1785],[-2081,-1831],[-1898,240],[-1920,-1649],[-686,691],[1972,3639],[-1281,-370],[-2458,-3774],[-4318,-605],[1705,-1076],[-1877,-1730],[-2308,309],[2506,-3679],[-1665,-1529],[642,-2942],[-1384,-1252],[-163,-1422],[-2641,-3435],[-2454,155],[2191,-899],[-468,-2463],[587,-1751],[-593,-1039],[-1093,-5417],[-1607,-1911],[480,-2273],[-2762,-1358],[-934,1081],[-2572,1116],[-3,1434],[-1851,1012],[548,3349],[-2848,-2160],[-3696,234],[-1991,2497],[-1273,3631],[1532,1122],[-2208,-480],[195,1387],[-2127,1425],[-197,2066],[-2996,4860],[-664,3334],[1323,2105],[3083,849],[-1667,556],[-1887,-1033],[-1449,-2396],[-474,1168],[-708,6194],[-2363,785],[257,2271],[-794,421],[3711,2719],[2153,2273],[-4558,-3851],[-1924,-512],[-56,1536],[1657,2447],[-2350,1829],[238,1675],[3145,1964],[4125,-671],[591,1008],[-3823,180],[-3862,-1706],[1621,3903],[4318,-907],[1083,1603],[-3253,-633],[-2791,468],[955,1857],[2046,535],[1450,-906],[1476,1135],[1470,5839],[1190,1712],[-5452,264],[-2135,1439],[-2753,710],[-1435,1645],[1014,716],[3789,-413],[3548,-1843],[411,4026],[-4531,361],[717,1905],[-1918,459],[-104,1606],[-1349,-2317],[-3815,-190],[-920,1184],[978,2866],[-795,2032],[2254,1115],[146,1368],[-2652,1424],[1098,885],[-2241,1752],[485,1999],[-2158,1918],[1252,1823],[-6522,5086],[243,1799],[-7941,2908],[-5733,946],[-4092,-989],[-5953,-123],[1056,-1033],[-4094,531],[-3710,1967],[3502,1650],[-6161,769],[-1171,2180],[5235,118],[1074,872],[6118,-369],[-839,2375],[-7389,-1268],[-7538,2783],[-2092,1526],[1174,1835],[9450,2092],[4198,1538],[4207,92],[1522,1232],[2431,4719],[-3516,-668],[-3697,843],[404,1461],[8623,3812],[2314,-1012],[431,1970],[4076,-501],[557,3324],[5483,1849],[12599,2042],[3178,-1718],[2240,1542],[5225,-2527],[3759,136],[-4024,3211],[5913,-324],[9917,-3416],[2107,119],[816,3076],[-3649,2116],[11439,316],[-9982,634],[-1074,781],[6681,1464],[4748,-970],[2620,1371],[5774,-1975],[1251,2883],[21874,470]],[[653666,939029],[3082,-635],[-960,-2440],[-2022,-1922],[-162,-3137],[2071,-3494],[2366,-2480],[2029,-1174],[-1332,-828],[-7069,539],[-3382,1145],[2145,1494],[-2200,2464],[-4309,-297],[-1038,1691],[399,1744],[1860,347],[3105,4663],[-478,1367],[4461,1423],[1434,-470]],[[202996,940045],[854,1278]],[[203850,941323],[3059,416],[2400,-897]],[[209309,940842],[183,-1543]],[[209492,939299],[-2102,-2601],[-4394,3347]],[[894957,942510],[3218,-1938],[410,-1911],[-5262,383],[-3378,1020],[1956,2267],[3056,179]],[[241192,944080],[2634,-1117]],[[243826,942963],[3152,218],[2037,-834]],[[249015,942347],[-4898,-6603],[-5815,18]],[[238302,935762],[1822,-1989]],[[240124,933773],[-1340,-2325]],[[238784,931448],[-3209,-8]],[[235575,931440],[-985,4468]],[[234590,935908],[-237,5414]],[[234353,941322],[2598,-189]],[[236951,941133],[-951,2135]],[[236000,943268],[5192,812]],[[279063,941080],[3474,67]],[[282537,941147],[3000,-985]],[[285537,940162],[2547,-2480]],[[288084,937682],[-308,-1542],[-3987,451]],[[283789,936591],[-4624,-835]],[[279165,935756],[-1897,2777]],[[277268,938533],[-1780,924]],[[275488,939457],[171,2234],[3404,-611]],[[696316,937765],[-2094,-63],[637,2135],[2197,413],[1905,-2017],[-2645,-468]],[[688236,956383],[-4119,-1504],[-13684,-3964],[-2374,-2429],[-3890,-2353],[-1573,-51],[-259,-2192],[-4105,-4516],[-1482,-411],[-3954,928],[-1964,-611],[-1491,2461],[2444,1146],[2609,3958],[2546,1951],[1636,2529],[1442,-252],[3540,3042],[5725,1283],[721,1248],[4916,-268],[8032,2230],[4644,2338],[2641,-439],[1151,-2138],[-3152,-1986]],[[933113,802729],[-1883,-1229],[-69,1204],[1283,609],[1157,2200],[-488,-2784]],[[146674,804734],[4765,-1918],[2331,-5262],[1797,-1212]],[[155567,796342],[1386,-3803],[-272,-1472]],[[156681,791067],[-3041,1562],[-1198,969]],[[152442,793598],[753,1585]],[[153195,795183],[-1696,-517]],[[151499,794666],[-511,1450]],[[150988,796116],[-1665,1522],[-289,1292]],[[149034,798930],[-2506,2827]],[[146528,801757],[-1706,-61]],[[144822,801696],[-116,1881]],[[144706,803577],[1165,-240],[57,1057]],[[145928,804394],[-1647,-501]],[[144281,803893],[-798,1456]],[[143483,805349],[1189,689]],[[144672,806038],[2002,-1304]],[[345948,810042],[-1590,-1232],[443,-2495],[-2285,-5023]],[[342516,801292],[-356,-2642]],[[342160,798650],[1598,2823]],[[343758,801473],[238,-888]],[[343996,800585],[1754,338]],[[345750,800923],[-1417,-1733]],[[344333,799190],[-131,-1497],[2445,178]],[[346647,797871],[-104,-1672]],[[346543,796199],[2153,1955],[16,-1114],[1405,593],[940,-712]],[[351057,796921],[128,-1069]],[[351185,795852],[-1091,-2574],[744,-160]],[[350838,793118],[-1027,-1546]],[[349811,791572],[2807,1423]],[[352618,792995],[-218,-1523],[-1316,-1152]],[[351084,790320],[-700,-2418]],[[350384,787902],[716,-812]],[[351100,787090],[892,1988],[1158,682]],[[353150,789760],[-860,-2725]],[[352290,787035],[147,-1173],[945,1863],[357,-1304]],[[353739,786421],[-1155,-5143],[-1519,-7]],[[351065,781271],[-55,2711]],[[351010,783982],[-1493,-1524]],[[349517,782458],[846,3001]],[[350363,785459],[-896,2801]],[[349467,788260],[-1030,-2871]],[[348437,785389],[-817,58]],[[347620,785447],[-1275,-2839]],[[346345,782608],[-1470,-189],[18,1172],[964,527]],[[345857,784118],[1727,2431],[-1380,533],[-190,-946],[-1187,171]],[[344827,786307],[12,1712]],[[344839,788019],[-1010,-875]],[[343829,787144],[-2031,-574]],[[341798,786570],[-3835,606]],[[337963,787176],[-2177,-629]],[[335786,786547],[-431,2517]],[[335355,789064],[2616,3120]],[[337971,792184],[-1073,450],[869,2880]],[[337767,795514],[1177,861]],[[338944,796375],[-99,1854],[1994,6850],[1521,3414]],[[342360,808493],[2013,1738],[1575,-189]],[[341569,796584],[-2717,-3564],[704,54],[2013,3510]],[[896558,826971],[596,-1498],[-161,-2055],[849,-2951],[278,-4044],[-467,-3138],[834,-3629],[1001,-7042],[1267,-5754],[969,-2942],[-1376,2332],[-1092,614],[-1743,-671],[-1474,-6675],[-49,-1980],[1246,-3053],[590,-2534],[744,-254],[264,-2318],[-414,-1968],[-414,3143],[-1957,840],[-1391,-4644],[-686,3163],[579,4083],[-207,2651],[603,2523],[-875,4365],[766,4852],[-197,5604],[377,4192],[-1346,3044],[-171,3179],[396,1674],[56,4643],[1952,641],[500,2656],[-1032,2280],[1185,671]],[[980032,818789],[1734,-952],[-1392,-593],[-1224,1101],[882,444]],[[487744,825735],[-1037,-665],[769,1798],[268,-1133]],[[275745,817216],[-3632,1793]],[[272113,819009],[533,807]],[[272646,819816],[1977,116]],[[274623,819932],[1122,-2716]],[[488342,820617],[-491,-1109],[-540,1495],[1031,-386]],[[538081,826905],[-958,-811],[-522,1768],[684,919],[796,-1876]],[[482727,825162],[530,-6881],[-827,-4031],[-3340,-876],[-381,-705],[-3192,-2340],[-2838,-601],[710,1220],[-1503,-526],[1450,1622],[-1348,-612],[-819,579],[1685,2259],[-401,1894],[1116,1253],[599,1874],[-2271,2671],[724,3240],[-556,963],[4197,-98],[1145,2367],[-1752,239],[1359,2756],[2065,281],[868,-603]],[[479947,831107],[3027,743],[1519,-3282],[-68,-2316],[-1698,-1090]],[[491362,851389],[-286,-1151],[-2160,-2146],[-401,-2258],[2032,772],[3691,-35],[823,-1236],[-2263,-5523],[-1728,-1051],[1561,-389],[-1972,-1722],[2120,-2],[1256,-736],[1366,-1971],[1010,-4719],[3354,-3886],[-337,-570],[1560,-5105],[-861,-1507],[2805,316],[1670,-1216],[249,-1687],[-1311,-3695],[-1451,-685],[-183,-2033],[2024,-139],[-48,-1073],[-1215,-1517],[-2098,-966],[-2751,15],[-1754,779],[-1720,-1740],[-2676,672],[-1126,-500],[-1080,-2388],[-1053,958],[-1543,-594],[-1381,-1595],[-325,1332],[2109,3140],[1097,2442],[2921,99],[1953,3174],[-2388,-2076],[-3632,2057],[-838,-660],[-1000,1505],[2442,1879],[1119,2040],[-336,2214],[-1616,-648],[1589,2446],[2625,1041],[669,2003],[160,2634],[-829,-293],[-1120,2013],[375,2939],[-1455,-1083],[-3271,454],[1275,3814],[-598,1172],[132,2086],[-1505,-1666],[-1061,-2414],[440,4103],[1170,4164],[-1289,-1339],[-1334,1102],[1585,3049],[-128,3843],[1667,2259],[-162,1526],[5593,679],[-157,-707]],[[589039,490923],[-572,-747],[-84,1557],[656,-810]],[[592152,491925],[-899,584],[683,564],[216,-1148]],[[258356,772744],[-801,-2405]],[[257555,770339],[-323,707]],[[257232,771046],[1124,1698]],[[268828,776662],[1113,441]],[[269941,777103],[234,-736],[2217,752]],[[272392,777119],[160,-2629]],[[272552,774490],[-3724,2172]],[[256275,785735],[-1336,-1321]],[[254939,784414],[-604,-1184]],[[254335,783230],[-490,967]],[[253845,784197],[615,1271]],[[254460,785468],[1815,267]],[[516113,815548],[-838,-971],[-918,455],[1358,1238],[398,-722]],[[189596,872516],[-2362,-2950],[-1044,1470],[3406,1480]],[[581880,871836],[-1354,-975],[-795,1658],[1719,282],[430,-965]],[[309615,809192],[-402,-1402],[-1162,1505],[715,1124],[849,-1227]],[[708031,725294],[-1442,-438],[470,-803]],[[707059,724053],[-1499,-1179],[-646,387],[-3185,-349],[-2784,-2329],[-1209,-2336],[673,-1235],[536,-3855],[-1819,-3866],[239,-2848],[-1766,-588],[-1170,600],[-352,-913],[1156,-3132],[-1011,-1519],[-1163,-548],[-722,-3475],[105,-2943],[-1140,-1792],[-754,999],[-1212,0],[-1619,-1756],[443,-963],[-1252,-747],[-1008,520],[-1464,-2331],[-612,-6378],[-3004,-1636],[-1595,30],[-1174,-1023],[-1475,629],[-3031,-532],[-4536,2668]],[[669009,681613],[724,1598]],[[669733,683211],[1889,4168],[-344,3262],[-2332,668],[24,4468],[-747,5264],[990,2176],[-1128,792],[-70,2701],[1122,1331],[-454,1178],[625,803],[864,5722]],[[670172,715744],[779,-959],[1226,-83],[900,-1617],[2080,1629],[144,2209],[2094,1148],[1802,1945],[848,4688],[2051,706],[584,1884],[2103,-1308]],[[684783,725986],[1519,-81],[1917,-963]],[[688219,724942],[857,-1318],[2480,2224],[846,-1284],[631,2634],[2109,659],[-102,1541],[1845,3152],[1047,-885],[63,-2302],[760,87],[-331,-4773],[457,-2338],[568,-228],[3915,4231],[1415,61],[80,-1108],[1417,1088],[1110,-124],[645,-965]],[[566573,440308],[223,-3162],[-210,-1365],[56,-4659],[-303,-2233],[224,-1122],[-5511,-74],[2,-17504],[475,-3801],[429,-547],[2988,-5635]],[[564946,400206],[-5455,-2133],[-1865,-113],[-979,784],[-3657,413],[-996,678],[-893,1800],[-7308,58],[-5077,5],[-1484,2257],[-840,238],[-1537,-1453],[-1483,262],[-753,-478]],[[536300,469971],[3696,-165],[5324,160],[1118,-2226],[-24,-1364],[765,-4655],[1532,-4849],[3103,828],[1910,-181],[519,4871],[369,636],[2606,604],[25,-2030],[3176,-164],[252,-684],[-171,-2633],[321,-2818],[-184,-4902],[75,-2523],[948,-2644],[303,-3855],[-358,-1191],[280,-1789],[784,819],[1655,-111],[676,583],[1205,-221],[368,841]],[[533384,475069],[1017,2282],[1379,1031],[533,-1124]],[[536313,477258],[-1726,-2587],[145,-3699],[-806,-372]],[[555733,756786],[1170,-1918],[225,-2072]],[[557128,752796],[-215,-3561],[700,-2177],[620,-328]],[[558233,746730],[186,-1133],[-1038,-3206],[-962,-818],[-289,-1931],[-571,332]],[[553728,752769],[-3,1422]],[[553725,754191],[148,-125]],[[553787,755260],[-14,-11]],[[553773,755249],[822,2019],[1138,-482]],[[656633,652705],[-901,-1424],[-745,766],[-96,-3705],[623,-1063],[-1215,-426],[-109,-1581],[-858,-4087],[-39,-1959]],[[653293,639226],[-226,-489],[-7081,1844],[-2674,6790],[-67,1228]],[[655778,659124],[179,-2205],[425,-236]],[[309296,179739],[65,13040]],[[325969,372995],[1214,-2244],[794,-2648],[3023,-4732],[2632,-1395],[1443,-2135],[2799,-2994],[1510,-1049],[718,-1999],[-1777,-5378],[32,-1472],[-1251,-3354],[102,-701],[1213,243],[4809,-1661],[758,1376],[1249,-553],[416,1569],[2054,2949],[410,2035],[172,4341]],[[348289,353193],[1281,314],[732,-864],[611,-3295],[-464,-5309],[-1358,-1791],[-1524,-1041],[-627,-1585],[-1733,-1999],[-1389,-3158],[-1981,-5081],[-1862,-3513]],[[339975,325871],[-732,-2389],[172,-1585],[-992,-6008],[-93,-3549]],[[309879,194532],[-49,393],[-4164,1672],[-5440,110],[-1359,2659],[188,5089],[-2258,-334],[-967,3631],[-209,3213],[319,1594],[906,79],[427,1919],[1020,1089],[17,1621],[876,1719],[-625,2090],[478,2273],[1225,1724],[-98,2195],[680,1497],[-501,2476],[678,1225],[-318,2221],[1090,2064],[-1676,2601],[1933,168],[135,1907],[-1687,344],[388,2687],[-624,2900],[343,1619],[-1014,1048],[61,4098],[1010,1166],[-418,2671],[-58,5681],[658,2112],[-342,939],[775,3402],[316,3654],[1317,1465],[-213,4131],[-387,1652],[137,3839],[-205,1604],[379,1895],[1808,2737],[-182,4358],[501,3515],[661,2560],[554,453],[-116,2920],[207,2652],[-556,73],[-416,4738],[-1154,5345],[182,2495],[995,4195],[570,486],[79,3490],[-275,2637],[552,1308],[475,4086],[1341,2896],[911,4568],[1390,745],[-654,3019],[463,2161],[-516,3958],[600,2332],[-493,1506],[866,2641],[2483,2122],[965,6117],[-517,1064]],[[313347,369511],[1342,3587],[963,607],[403,1844],[1247,-1760],[1982,-19],[1256,-746],[778,-3548],[970,4473],[438,398],[2709,48],[534,-1400]],[[629140,735218],[-1045,-171]],[[628095,735047],[-1011,4059],[-1605,45],[-392,1153],[-731,-365]],[[624356,739939],[-1331,1995],[-1609,748],[-391,1871],[426,1405],[-786,2296]],[[620665,748254],[4338,1089]],[[625003,749343],[1628,-2630],[-587,-1238],[1635,-2395],[-1069,-1518],[1728,-2269],[490,-1257],[312,-2818]],[[547091,792639],[-243,-1257],[783,-2256]],[[547631,789126],[-224,-1768],[-1433,236],[-271,-4387],[-1001,-852]],[[544702,782355],[-376,-1098],[-2658,-307],[-1381,-1238],[-2232,611]],[[538055,780323],[-3644,1082],[-608,2248],[-2569,-631],[-609,-1058],[-1590,402]],[[529035,782366],[-2424,1140]],[[526611,783506],[-146,1264]],[[526465,784770],[74,1425]],[[527029,786288],[-98,96]],[[526931,786384],[1715,-1361],[327,1349],[1529,-847],[3412,1897],[1324,-290],[912,-1134],[-555,4045],[2391,2146],[388,1445]],[[538374,793634],[651,-975],[1784,-19],[780,2279],[3014,-1357],[1168,269],[1320,-1192]],[[628095,735047],[-1763,761],[-1840,3816]],[[624492,739624],[-136,315]],[[635746,732426],[-767,-144],[-1582,2417],[608,947],[-294,1975],[517,514],[-907,1688],[-619,-210],[-3562,-4395]],[[625003,749343],[777,940],[1422,-1334],[1847,-913],[596,1283],[-1362,2193],[688,1386]],[[628971,752898],[888,-464],[1421,-2948],[1667,-606],[1977,3743]],[[584871,490497],[-360,-1430],[252,-1635],[737,-400],[28,-1715],[-1015,-1862],[-771,-2941],[-1390,-2187]],[[582352,478327],[16,203]],[[581384,484839],[-244,85]],[[581140,484924],[38,1702],[-583,1975]],[[580595,488601],[135,697],[909,-1220],[1328,546],[172,2233],[1732,-360]],[[515815,805530],[552,-132]],[[516367,805398],[282,-12]],[[516649,805386],[1030,-2573],[-689,-1157]],[[516990,801656],[-1035,-1192],[126,-2260]],[[516081,798204],[-784,-162],[-1776,1643],[-21,2060],[-1901,-1041],[-3,1696],[-1410,464],[-1556,2693],[-742,-400],[-875,2283]],[[509305,809102],[1534,-1008],[900,1060]],[[511739,809154],[770,523],[2704,-1124],[973,-945],[-371,-2078]],[[509987,574011],[-300,-1782],[636,-1871],[328,-2798],[-718,-2009],[-52,-2138],[-645,-764],[-778,-4115],[-751,-209],[-246,-6960],[246,-6885],[-191,-2029]],[[504506,541548],[432,461],[-556,2327],[131,1836],[-69,12162],[-488,1392],[-262,4218],[-1528,2148],[335,3754]],[[502501,569846],[1462,2689],[1538,-170],[1135,2836]],[[506636,575201],[-65,1924],[1423,864],[1993,-3978]],[[500603,593059],[-148,-2454],[1262,-4703],[1619,-2049],[-591,43],[-3,-1913],[1605,-2408],[1413,465],[423,-1468],[-374,-1115],[827,-2256]],[[502501,569846],[-1157,-7],[-1535,732]],[[499809,570571],[-641,304],[-1117,-1054],[-5912,56],[-236,-2406],[356,-1128],[57,-4407],[195,-1047]],[[492511,560889],[-336,-329],[-1131,2782],[-1816,-3],[-1262,-1476],[-1772,1684],[-361,1846],[-1177,1093]],[[484656,566486],[92,3651],[530,969],[32,3685],[1362,1210],[1026,1810],[-145,1982],[705,720],[-284,1927],[772,1561],[1320,-1116],[762,513],[287,2323],[781,40],[120,1607],[1158,1915],[955,-626],[389,1707],[571,175],[1995,1976],[803,1352],[1457,69],[1259,-877]],[[757152,634925],[157,-3980],[-836,791],[-419,-869],[401,-2970]],[[747364,635607],[-862,7959],[-482,1409],[461,3297],[-1633,1510],[-339,842],[351,1699],[454,-195],[397,1817],[1205,35],[-336,1754],[-880,497],[-1021,1860],[1204,3729],[457,-1339],[2409,-1697],[192,1247],[566,-1625],[-24,-3768],[1737,-875],[4473,69],[1163,-1335],[-603,-290],[-521,-3085],[-519,-1061],[-1416,-603],[-574,-2565],[430,-3295],[845,-739],[884,3110],[-23,1075],[879,-15],[321,-4470],[361,-1443],[232,-4191]],[[577817,753361],[-1332,-286],[-666,940],[-1888,-679],[-818,-1471]],[[573113,751865],[-708,-257],[248,-1412],[-2511,-1133],[-2036,1830],[-2453,-982],[-1998,-299]],[[563655,749612],[249,2255],[-469,1639],[-1369,1898]],[[562066,755404],[499,753],[-158,2377],[1417,2048],[-1173,1579],[-372,3276],[790,1365]],[[563069,766802],[899,-947],[-305,-1443],[849,234],[6313,-1203],[1996,1993],[2420,949],[2215,-1068],[460,-976],[1487,-475]],[[552797,770541],[949,71],[-547,-3427],[1200,-1535],[-941,-464],[675,-1549],[-816,-1009]],[[553317,762628],[-466,-1427],[-979,-366],[-640,-1554],[-21,-2421]],[[551211,756860],[-2135,1999]],[[548847,759103],[-865,3006],[-1556,1974],[-1387,2584],[-233,1533],[-1094,1730],[143,2448],[1404,-1008],[659,1230],[3561,-820],[2361,-4],[957,-1235]],[[578188,837332],[1797,-1187],[1612,-22],[297,-1505],[2088,951],[1870,-1630],[197,-3078],[-497,-1583],[895,-799],[785,-2681],[1079,-829],[-105,-1454],[1933,-697],[706,-2112],[-1562,-1453],[-2012,622],[-442,-1063],[768,-1294],[117,-2880],[517,-1252]],[[588231,813386],[-2174,-324],[-1244,-2665],[32,-1963],[-1066,1260],[-2262,-563],[-585,1389],[-951,-633],[-1693,578],[-2538,33],[-356,822],[-3380,955],[-4343,-272],[-2101,-2070]],[[565570,809933],[131,3095],[-1266,1283],[1800,2413],[118,2152],[-1118,5405]],[[565235,824281],[3565,206],[2164,2116],[867,3481],[2545,2096],[-883,411],[377,1926]],[[573870,834517],[1275,965],[1457,-188],[1586,2038]],[[253072,598860],[-954,23],[211,11377]],[[252329,610260],[78,924],[908,-31],[788,2846],[631,157]],[[338445,385253],[-57,2053],[-2529,3151],[-2546,-67],[-4860,-2061],[-445,-2429],[-998,-3004],[-1,-2984],[-1040,-6917]],[[313347,369511],[-1901,-7],[-303,4537],[-550,2598],[-29,1886],[-936,2231],[94,1845],[-681,910],[130,4369],[654,1708],[-1404,2753],[-349,5437],[-609,636],[-549,2589]],[[306914,401003],[-317,1812],[518,663],[1114,3294]],[[308229,406772],[86,-108]],[[307332,412821],[-12,12]],[[307320,412833],[534,1615],[-562,1622],[388,2167],[985,2360],[-538,3056],[252,1105],[13,3652],[815,2240],[-2481,9184]],[[306726,439834],[2028,-352],[336,-660],[915,614],[907,1871],[972,119],[387,1049],[1308,1404],[1060,1739],[1295,885],[2410,673],[230,-3202],[-352,-1975],[323,-2598],[5,-2456],[915,-3174],[1331,-1634],[258,-1119],[2319,-469],[664,-955],[775,65],[839,-1944],[1637,-809],[1073,-2321],[1980,213],[1585,-1778],[89,-2340],[488,-2570],[71,-2786],[-861,-56],[947,-2259],[185,-4679],[4549,-350],[535,261],[-348,-2168],[208,-3460],[1565,-1647],[718,-4544],[-629,-4749],[-920,-3931],[752,-1393],[-830,-1096]],[[343103,516223],[1286,-592],[290,1169],[-594,1540],[537,1287],[571,-655],[2088,1135],[1007,-1605]],[[348288,518502],[1350,-1219],[1007,1385],[2230,-1015],[734,1068],[1972,7928],[939,2129]],[[351748,304813],[-452,1152]],[[351296,305965],[18,-30]],[[352392,311289],[-83,-134]],[[352309,311155],[-1203,1592],[-444,2051],[-1275,1195],[-1020,2192],[-1852,1538],[-841,2071],[-1243,-1204],[-476,2670],[-1824,3088],[-1060,-1043],[-1096,566]],[[348289,353193],[15,849]],[[348304,354042],[381,1259],[573,6839]],[[349258,362140],[-996,1501],[-992,-960],[-1066,-98],[-477,2430],[-221,5388],[-643,2156],[-946,156],[-570,1117],[-1506,-1058],[-2830,960],[349,6583],[-915,4938]],[[306726,439834],[-1061,129],[-1199,-762],[-695,286],[15,9077],[-1669,-2890],[-2622,-223],[-548,2924],[-2307,585],[654,2478],[-1597,3834],[-629,2426],[153,912],[-783,1342],[783,1462],[-105,2390],[1438,2025],[292,1301],[-278,1457],[710,2747],[258,3034],[523,329],[2372,3334],[2420,912],[483,1049],[1097,138],[460,-895],[759,385]],[[305650,479620],[1571,18018],[-742,4221],[-1120,2035],[47,4251],[1616,897],[827,-561],[31,1355],[-551,1185],[-1363,-27],[10,3846],[4644,65],[-48,1584],[567,-1389],[1362,2105],[410,-131],[727,-2787],[-10,-2401],[605,77]],[[314233,511963],[1595,-2791],[1723,1371],[578,-1731],[1027,2469],[1034,861],[1713,2168],[220,1690],[1782,1884],[13,1122],[-1832,671],[31,1638],[-503,2388],[-6,2267],[-1658,3821],[1562,-545],[650,-1251],[2019,-41],[906,-1945],[567,468],[145,2044],[838,822],[715,-345],[1664,1122],[761,1357],[770,109],[1107,2721],[-382,1229]],[[331272,535536],[1666,218],[421,-924],[-439,-3256],[1237,-901],[423,-2652],[-843,-2050],[65,-1412],[-453,-3906],[664,-2463],[-3,-2213],[1459,-3108],[1024,-1021],[974,479],[475,1795],[2073,691],[1321,1836],[784,-787],[983,361]],[[819833,533745],[518,-3074],[-610,57],[-223,3017]],[[819518,533745],[-778,-1076],[260,-1925],[-644,-2187],[-1513,3369]],[[816843,531926],[317,-10]],[[754532,669180],[-103,-1199],[1100,-637],[230,-3171],[-1116,-669],[-2589,-179],[-1094,703],[-1617,-1119],[-2517,1540],[94,2101]],[[746920,666550],[1793,4688],[1234,1207],[1020,-398],[12,-970],[2014,-627],[411,574],[1055,-708],[73,-1136]],[[570163,399300],[-97,-721],[1492,-4348],[1131,-5268],[1417,-2100],[1509,-1499],[164,-1972],[1164,-308],[-84,-3161],[1045,-3014],[2755,-1412],[193,-1507],[716,-760]],[[581568,373230],[-652,-114],[-806,-1586],[-1749,-1260],[-888,-2253],[-2510,-3737],[-422,-3177],[-1064,-2025],[-1499,-976],[-912,-5088],[-390,-641],[-1932,-610],[-2373,1283],[-1744,1980],[-1075,-1133],[-663,-3633],[-1526,-3016],[-1235,-1623],[-2518,31],[-314,2400],[523,1652],[-61,1477],[-1244,5248],[-1013,1499]],[[555501,357928],[-9,16450],[2760,0],[9,21810],[2881,712],[3024,1120],[552,-106],[783,-2521],[2162,2813],[477,-441],[1051,1370],[972,165]],[[563500,569410],[1256,-3150],[928,-3348],[-66,-2857],[-429,-1338],[192,-1771],[1694,-890]],[[567075,556056],[401,-2217],[1560,-911],[1095,-2447],[-159,-1216],[1941,-2692],[1314,-2545],[-148,-1067],[571,-2287],[1581,-1732],[889,-3956]],[[576120,534986],[-801,526],[-814,-803],[-3603,1479],[-766,-1703],[-1343,-560],[-1239,380],[-2507,-1961],[-837,437],[-1000,-535],[-927,-3032],[-2042,868],[-415,-217],[-2721,1291],[-921,2174],[-1166,1538],[-849,227],[-1201,-1399],[-1392,-3755],[119,-4616]],[[551695,525325],[-378,856],[-871,-729],[-2008,1094],[-2124,-885],[-492,-1933],[-77,-2234],[-792,-3328]],[[544953,518166],[-333,3783],[-801,1295],[-1795,4145],[-295,3150],[-871,1819],[-406,3640],[150,3467],[-516,1028],[856,1428],[1407,5829],[651,1541]],[[543000,549291],[1013,-287],[1483,1234],[463,1078],[665,-1864],[2402,2563],[2617,458],[1436,3527],[-612,1384],[714,748],[1453,29],[1871,629],[1198,1650],[1363,3371],[1283,2322],[-54,1234],[935,1469],[1252,1028],[1018,-454]],[[313542,772321],[-966,631]],[[312576,772952],[63,1967]],[[312639,774919],[-31,200]],[[311702,775814],[-38,-22]],[[311664,775792],[-16,7865],[-1191,1559],[-1648,-845]],[[308809,784371],[-1151,1538]],[[307658,785909],[-2124,-4467]],[[305534,781442],[-802,-4756],[-1671,-3814]],[[303061,772872],[-1193,164]],[[301868,773036],[-528,-1674]],[[301340,771362],[-8865,-22]],[[267330,873831],[-420,723]],[[292475,771340],[-1307,-619]],[[291168,770721],[-1718,-2421]],[[289450,768300],[-20,24]],[[279963,760904],[422,241]],[[280385,761145],[-13,-1049]],[[270430,756850],[633,2722]],[[251257,789167],[-84,-67]],[[251173,789100],[-3508,1179]],[[247665,790279],[-1884,-843]],[[245781,789436],[-4105,3279]],[[241676,792715],[-1975,-511]],[[239701,792204],[-2537,1286]],[[237164,793490],[-255,716]],[[236909,794206],[-395,2614],[-834,385],[-19,-2240],[-6577,10],[-10660,-1]],[[218424,794974],[-4738,0],[-7106,0],[-10660,0],[-8290,0],[-5922,0],[-5922,0],[-8292,0]],[[167494,794974],[-8574,0]],[[138819,835824],[-202,1310]],[[138617,837134],[-4105,2900]],[[134512,840034],[-691,-52]],[[133821,839982],[-576,2586],[-4969,9944],[-3121,3456]],[[125155,855968],[-298,1719]],[[124857,857687],[-1180,1271],[-2349,-1115]],[[121328,857843],[-714,-2681],[-2388,-1476]],[[118226,853686],[-430,1914]],[[117796,855600],[-4423,5079],[295,1541],[-2484,-951]],[[111184,861269],[-2857,694]],[[108327,861963],[0,55397]],[[526465,784770],[146,-1264]],[[529035,782366],[-61,-1865],[-955,295],[-410,-1411],[-1912,-444],[-826,-2706],[-1620,3644],[-1618,-3101],[-2130,24]],[[519503,776802],[-732,2903],[-913,87],[-951,-1680],[-73,1667],[2612,5298],[146,989],[1562,611]],[[521154,786677],[3515,378]],[[524669,787055],[685,84]],[[525354,787139],[153,0]],[[304393,396029],[1367,827],[206,2976],[948,1171]],[[868915,771709],[-4,489]],[[866863,773017],[-131,-309]],[[866732,772708],[-479,546],[-1125,-2031],[-1011,-439],[480,-4967],[17,-3784],[-536,-3144],[-1788,-1038],[284,-1135]],[[862574,756716],[-796,2111],[-950,631],[-496,-3100],[-1128,-364],[-1084,-2223],[-2440,-301],[683,-2516],[-499,-1028],[-2588,842],[-767,1479],[-841,-830],[-307,-1676],[-1392,-2686],[-3055,-2636],[-1465,-2700]],[[817405,638260],[-696,-172]],[[815489,636816],[-446,-704]],[[799923,632140],[-474,813],[-1252,-215],[-958,1686],[-952,506],[-354,2468],[678,2272],[-662,767],[-1942,85],[-1576,2503],[-1141,-1238],[-192,-1334],[-1177,-1227],[-883,286],[-386,-1268],[-819,1444],[-414,-1094],[-475,990],[-819,-1845],[-1356,1706],[-1082,-2143]],[[783687,637302],[-1267,492],[-408,-1236],[589,-2531],[-88,-4007],[-1335,436],[-237,2037]],[[780941,632493],[-51,1058],[-1637,-1706],[-879,29],[-657,1413],[-169,1934],[-2013,580],[534,4142],[-123,1605],[-1325,565],[-88,2566],[-420,1288],[425,1474],[-2270,-149],[-1256,-915],[350,1302],[-442,2138],[800,4503],[981,2031],[1258,1375],[295,4483],[-224,5860],[-1139,537],[-395,2839],[-1558,2179],[-599,-1731]],[[770339,671893],[-2000,1433],[-891,-283],[831,2083],[-404,1700],[-759,-835],[494,1778],[-846,1406],[-1443,-1426],[-266,-901],[-1807,720],[-407,809],[-2002,-3017],[-1933,-1258],[-242,-1117],[-1160,-1512],[-104,-1174],[-1907,-1295],[-961,176]],[[746920,666550],[-396,1219],[277,2055],[-632,1322],[-1420,-1311]],[[744749,669835],[-2690,-191],[-1631,1463],[-406,-928],[-915,918],[-347,-920],[-765,2068],[-1544,229],[101,1636],[-1235,20],[-1349,1873],[-354,1826],[-1438,-215],[-1189,2542],[-837,419],[-1932,2558],[-321,1254],[-1739,64],[-450,-1448],[-680,422]],[[725028,683425],[-912,1483],[-1363,910],[-741,1898]],[[722012,687716],[-22,32]],[[721990,687748],[-1520,1101]],[[720470,688849],[-85,152]],[[720385,689001],[-645,1760],[-875,-646],[-201,3519]],[[718664,693634],[-916,3746],[865,457],[606,-1415],[834,846]],[[720053,697268],[-8,373]],[[720045,697641],[-226,3602]],[[719819,701243],[-63,322]],[[719756,701565],[-863,1620],[-136,3483],[605,833],[-833,1717],[-1080,805],[-749,3537],[-560,1383]],[[716140,714943],[-31,68]],[[716109,715011],[-981,-120],[-1888,1102]],[[713240,715993],[-598,1335],[-1121,-344],[-675,1538],[193,1741],[-372,1584],[-1156,524],[-215,1038],[-2237,644]],[[708031,725294],[631,913],[-623,1278],[-415,5383],[-1298,887],[-1322,-313],[-17,2341],[-455,2647]],[[704532,738430],[786,934],[214,2587],[2361,1788],[66,880],[1994,662],[261,-1774],[2230,851],[954,3157],[3611,553],[664,1753],[2586,2436],[2562,1479],[-18,934]],[[722803,754670],[-124,2817],[1040,1232],[-414,1005],[1099,702],[-1196,5543],[279,3844],[-1273,303],[172,1240],[2205,727],[2330,1304],[826,-1111],[1360,-226],[288,1889],[-711,459],[1953,9870],[2740,-1276],[1807,11],[332,-841],[1941,1380],[477,1133],[-363,3916],[621,2780],[2222,852],[566,2845],[1582,456]],[[742562,795524],[1366,453]],[[743928,795977],[-198,-1664],[657,-1934],[1493,-1010],[1474,-2263],[1426,8],[2089,-1942],[509,-2316],[1038,-1959],[455,-2521],[-89,-2922],[-945,-3025],[599,-1951],[1963,-707],[3343,-242],[2414,-798],[2932,-3260],[1773,-431],[1561,-6349],[1315,-2879],[2278,411],[6283,-1313],[1434,647],[4806,-1253],[719,-1481],[3056,-1244],[1773,-1508],[2185,744],[0,-1293],[1345,-374],[597,844],[4370,3263],[3892,939],[3533,51],[2659,1883],[1686,3363],[2571,2192],[-1475,3886],[609,2724],[768,1404],[1427,-35],[515,-833],[2750,-1019],[1232,1167],[1352,2500],[3233,555],[1555,2001],[894,2926],[2140,427],[2709,2104],[1488,256],[2396,-915],[532,1493],[-519,1731],[-1748,2986],[-1621,1955],[-2027,23],[-1161,-1989],[-1639,1288],[-1471,-68],[-924,-1014],[-946,1529],[1100,4410],[2026,6721]],[[824119,799896],[3306,-1839],[1606,1961],[2246,1315],[-268,2012],[2508,7077],[1708,2206],[-70,3518],[-1635,391],[75,915],[1693,2279],[4538,1855],[3898,153],[2976,-2233],[730,413],[1594,-956],[1844,-3806],[1368,-5297],[332,-2403],[1061,-2324],[-2,-1506],[789,-1449],[-243,-1989],[1381,-1805],[1956,186],[1156,-1410],[1050,159],[1939,-2946],[992,-180],[697,-3080],[-256,-1266],[808,-2584],[2173,-65],[2158,521],[402,1058],[2116,889],[2291,1637],[751,-306],[523,-3592],[-1623,-2448],[96,-1032],[-932,-3727],[-15,-1488],[-1876,-4461],[-201,-2157],[-844,-383]],[[492511,560889],[-141,-2202],[406,-1832],[263,-3506],[-789,-1640],[-545,-4307],[-694,-2356],[98,-2719],[662,-4178],[468,-254],[-61,-2649],[-566,-132]],[[479041,530496],[-66,4321],[385,1445],[-67,3062],[-952,792],[-254,1539],[-1986,1617],[752,1741],[100,1614],[-527,2870]],[[476426,549497],[707,-10],[598,3484],[-665,645],[53,1196],[1544,-268],[-750,2230],[480,1742],[-327,1985],[-670,473],[2,3118],[405,832]],[[477803,564924],[915,1570],[1926,-1488],[763,1026],[109,1819],[685,-498],[406,898],[55,-2635],[575,-500],[530,1153],[889,217]],[[544953,518166],[-344,-3518],[-883,1414],[-2331,576],[-1162,845],[-3307,40]],[[536926,517523],[-203,562],[-5200,257],[-55,-784]],[[531468,517558],[-3747,1],[-497,811]],[[523766,532889],[681,2620],[720,4809],[1666,3097],[333,1352],[1010,1400],[941,-623],[344,1018],[1184,-2164],[336,-1540],[1106,1537],[79,1135],[782,1348],[-261,923],[690,1881],[604,4103],[473,1856],[1119,1724],[342,3198],[683,671],[262,2942],[738,3370],[991,3170],[1854,2087],[187,3651],[-300,1123],[-893,507],[-371,4116]],[[539066,582200],[1256,-585]],[[540322,581615],[681,-1920],[889,-4800],[-143,-4336],[684,-4480],[1052,-2071],[-2275,-392],[-1646,226],[-739,-1708],[986,-2891],[2178,-3828],[908,-4180],[103,-1944]],[[576120,534986],[1069,-2752],[1122,-1744],[654,-155],[832,1072],[1179,-692],[883,1325],[576,-148],[1439,-3584],[871,-867],[917,-2043]],[[585662,525398],[-235,-2660],[258,-1154],[-328,-2320],[1484,-1752]],[[586841,517512],[-11,-38]],[[584660,512056],[-1486,-2485],[-23,-1897],[-698,-3669]],[[582453,504005],[-110,3]],[[582340,501712],[2,5]],[[582342,501717],[-184,-5222]],[[582158,496495],[-988,-1767]],[[581170,494728],[-66,250]],[[580139,490215],[21,11]],[[580160,490226],[435,-1625]],[[581140,484924],[-41,14]],[[584945,456249],[-9,-232]],[[584936,456017],[-4664,-1572],[55,-1274],[-1437,-3106],[637,-3593],[25,-4964],[-783,-4821],[349,-1950],[1616,-3180],[1009,-488],[367,1355],[654,279],[0,-7331],[-670,852],[-1499,-710],[-1824,5254],[-2290,1699],[-936,3496],[-686,-1740],[-981,-434],[-1584,486],[-1880,1582],[-83,2288],[-2734,-798],[-42,1776],[-982,1185]],[[536313,477258],[950,-1200],[751,881],[88,1388],[622,-179],[1160,1097],[145,-3151],[826,-299],[2478,5041],[757,573],[762,2785],[196,2570],[-6,5051],[904,2000],[1205,4149],[845,831],[1317,2669],[-80,1609],[454,3031],[41,5237],[432,2469],[40,2835],[1163,5398],[332,3282]],[[530917,481515],[1039,2346],[958,-1045],[236,2240],[-1101,2855],[188,2927],[1275,-414],[1061,489],[-40,2376],[1004,-17],[551,-2260],[1314,-486],[747,1522],[425,-1938],[557,-8],[824,3418],[199,2825],[-125,2613],[194,2096],[-1723,2459],[68,2335],[563,2047],[964,1630],[-704,3310],[-916,287],[-1603,-1053],[-309,2412],[363,3042]],[[301889,574992],[-1773,-1158],[-807,-2784],[-548,-487],[-1176,-3691],[-381,-4160],[-972,-3331],[828,194],[728,-892],[363,-2852],[692,-1455],[-74,-5493],[654,-501],[875,-2251],[2443,24],[995,524],[1555,-858],[1822,-4758],[2687,128],[2511,505],[357,-1281],[-1071,-4473],[-84,-4524],[538,-3807],[973,-2657],[-1454,-3099],[600,-588],[1133,-2390],[930,-6914]],[[305650,479620],[-1038,2498],[-1099,196],[1837,6109],[-61,546],[-2274,2604],[-1340,-684],[-988,1074],[-644,-1030],[-1142,-606],[-1366,121],[-742,772],[-118,2654],[-832,813],[-466,2631],[-1617,1649],[-477,2310],[-1066,2255],[-1341,553]],[[290876,504085],[-1653,1527],[-1198,1762],[-510,-1262],[-1182,196],[-1396,926],[-125,1254],[-2346,2427],[-1521,2424]],[[283608,547547],[469,2853],[404,-994],[1085,2544],[-784,3116],[289,947]],[[270656,561454],[-1045,-756],[-1,-2305],[553,-642],[-488,-1252],[154,-1699],[-450,-815],[400,-1454]],[[261820,570254],[343,725],[1978,-1417],[578,633],[980,-428],[500,-1182],[992,-220],[470,1031]],[[594456,712459],[-1330,-157],[-394,735],[-1864,49]],[[541137,806029],[512,920],[1002,-1200],[2959,-1412],[-582,-888],[1302,-1932],[863,826],[-304,1127],[2763,-2695],[1910,-550],[749,-2184]],[[552311,798041],[-1865,-1501],[-1117,-2187],[-1584,-162],[-654,-1552]],[[538374,793634],[-3286,4113],[-987,3444],[489,1821],[5323,3251],[1224,-234]],[[527054,829528],[17,-108]],[[539583,823049],[24,-14]],[[539607,823035],[433,-2642],[-766,-2078],[1335,-2395],[370,-2647],[-419,-1477],[1152,-3435],[-575,-2332]],[[526931,786384],[-142,138]],[[521154,786677],[-87,2795],[705,3387],[823,2000],[-1899,1057],[-1987,51],[-1086,1730]],[[517623,797697],[397,2049],[-1030,1910]],[[516649,805386],[-276,1385],[830,2990],[-680,1620],[2204,880],[815,2780],[450,5344]],[[524116,829329],[-32,661]],[[524084,829990],[2970,-462]],[[620127,572847],[-898,-2965]],[[619229,569882],[-1014,483],[-2109,-595],[-89,3606],[1700,5198]],[[617717,578574],[810,-533],[1241,1968]],[[526959,830136],[95,-608]],[[524084,829990],[-25,488]],[[300643,611589],[18,1790],[-662,1520],[714,800],[239,2357],[-339,3480]],[[523823,723550],[-1021,-2619],[388,-754],[-286,-2947],[412,-3949],[-412,-2784],[-2033,-3872],[-38,-1469],[642,-3341],[1333,-2025],[340,-2270],[1974,-2792],[1318,-10918]],[[526440,683810],[-579,-677],[917,-2836],[562,-3966],[-75,-2410],[279,-4589],[-468,-2695],[408,-2861],[-97,-1753],[-1023,-1293],[-119,-1579],[1534,-4355],[76,-1665],[633,-2726],[1195,-235],[2363,-1543],[1198,-4579]],[[533244,644048],[-5754,-7235],[-6708,-8434],[-4570,-8259],[-4469,-1992]],[[511743,618128],[-2297,-915],[-819,958],[417,1545],[-146,2244],[-2215,1625],[-519,1089],[-1483,774],[-207,1050],[-1236,1551],[-57,1687],[-7683,10714],[-8895,12352]],[[486603,652802],[-6023,7672],[-4701,5897]],[[475879,666371],[0,2195]],[[475879,668566],[64,6293],[2709,3738],[1639,1633],[1277,-334],[374,1424],[2922,876],[1335,3012],[1793,1383],[1939,2174],[-580,782],[19,2750],[2248,1021],[240,1234],[1340,518],[3259,-243],[582,2247],[-1234,2425],[-470,2613],[-323,8491],[-1178,2087]],[[290876,504085],[-949,-96],[1008,-2562],[38,-2349],[-440,163],[-452,-3596],[-1442,-3565],[-1637,-2546],[-3282,-2482],[-1346,-2462],[-207,-2249],[-721,-3252],[-19,-1403],[-1084,-2536],[-707,372],[-854,2801],[-1392,942],[-678,-993],[-351,2335],[919,1136],[-404,2903]],[[594994,690286],[131,-677]],[[595125,689609],[1831,-10255]],[[602420,635036],[-6380,-3],[-8723,-3],[-5194,-4],[-6367,2],[-6367,2]],[[569389,635030],[0,42574],[-705,6331],[687,3116],[-336,3308],[827,1897]],[[617717,578574],[-1184,2464],[-520,1787],[-1117,1871],[-1648,3819],[-1522,1699],[-1916,625],[-927,-339],[-344,881],[-1583,-1207],[-1723,2535],[-869,-4166],[-872,1805],[-647,-1077],[-1389,-90]],[[601456,589181],[-271,5185],[264,700],[1089,6197],[-73,1946],[337,2573],[1117,16],[1032,2348],[1308,751],[989,2490]],[[495015,761882],[1066,-991],[-194,-1001],[3281,-1456],[717,-807],[1868,3],[182,921],[2032,-1477]],[[504739,756526],[-772,548]],[[504739,756526],[906,-888],[1720,-77],[1555,537]],[[479427,724985],[-272,2406],[1337,2720],[-890,2445],[959,3549],[-485,467],[-1009,3118],[1386,311],[629,3727],[-328,3946],[1989,3098],[-917,832],[-211,1599],[-1469,229],[-712,-873],[-2281,368],[32,1409],[-1567,-1141]],[[577812,857129],[-955,-2975]],[[576857,854154],[-403,147]],[[576732,848420],[54,9]],[[576786,848429],[-809,-2889]],[[575977,845540],[-2276,17],[-1504,1820],[-2445,1334],[-2190,-1143]],[[619229,569882],[-731,-2239],[506,-2478],[944,-1914],[836,-2966],[1501,-2331],[8209,-5859],[2778,0]],[[633272,552095],[-4320,-8885],[-4118,-9392],[-2643,228],[-2398,-1813],[-928,-2088],[-2132,-913],[-389,-949]],[[616344,528283],[-1842,-203],[-1266,1953],[-2564,-2498],[-966,-2342],[-3912,1141],[-3279,4519],[-2288,226],[-886,2123],[-50,3175],[-1324,879]],[[597967,537256],[-518,1071],[-1031,5849],[-1796,3350],[-1106,2638],[-1222,531],[-593,1130],[616,2636],[1997,279],[392,822],[-45,5209]],[[594661,560771],[593,3930],[-44,2389],[822,2086],[999,-91],[503,5639],[1343,4270],[1421,1120],[291,3227],[495,2103],[372,3737]],[[580460,913634],[-1375,-3161],[595,-1770],[1830,-758],[1765,-2211],[-2478,-4251],[2267,-5213],[534,-2425],[-767,-669],[-598,-3557],[1302,-1205],[98,-2364],[1099,-2046],[-1388,-1619],[3269,-3194],[981,-1912],[-690,-1883],[-4432,-6052],[-5257,-5983]],[[567098,894577],[-1122,2286],[658,3670],[-1445,3788],[474,2989],[-2379,2586],[-2181,768],[-3820,3059]],[[557283,913723],[2777,1385],[2192,-3263],[2536,-420],[1473,929],[3020,-1259],[2242,2351],[731,3925],[1427,1554],[3790,869],[3477,-2312],[533,-1175],[-1021,-2673]],[[348288,518502],[595,797],[574,2112],[-22,1898],[591,2674],[-1001,2752],[-274,2553],[-7,3131],[822,2047]],[[516081,798204],[1542,-507]],[[519503,776802],[-598,-1279],[964,-1831],[-1459,-1676],[1119,-2377],[-521,-1220],[345,-1367],[1860,-682],[-399,-2357]],[[520814,764013],[-644,-421]],[[503967,757074],[772,-548]],[[526641,510831],[4846,-191],[-19,6918]],[[482727,825162],[-1199,-177],[-1111,2071],[-849,-1701],[-2120,1737],[2499,4015]],[[620665,748254],[-1810,2705],[-893,-734],[-2657,460]],[[611050,761956],[478,888],[1383,-212],[2590,-1865],[2329,30],[3788,-2827],[485,-1069],[2538,1124],[2379,-1667],[-247,-1599],[2198,-1861]],[[499809,570571],[30,-2873],[1218,-2007],[-410,-4908],[822,-623],[-112,-3003],[-323,-546],[877,-2696],[-290,-939],[142,-4693],[-305,-2978],[588,-2360],[1250,-2152]],[[491349,534865],[76,235]],[[468362,578206],[-313,-1219],[547,-1085],[1034,1124],[710,-1811],[1118,1855],[1262,-1008],[1284,1262],[-104,1239],[980,-369],[624,-2843],[-10,-1476],[1151,-1700],[-713,-2077],[907,-267],[295,-3275],[669,-1632]],[[476426,549497],[-616,595],[-504,-2347],[-633,-278],[-962,1185],[263,1325],[-414,4186],[-695,1117],[-1431,-293]],[[471434,554987],[-1191,-888],[588,2087],[-528,3713],[-1431,3931],[-1959,90],[-1640,-775],[-706,-2895],[-756,-1599],[-736,-322]],[[458212,569531],[1002,3368],[1159,897],[1480,451],[-15,1621],[-586,998],[669,797],[-58,2140]],[[461863,579803],[1795,-239],[197,-924],[2002,-886],[2505,452]],[[453993,585214],[2924,-6],[1115,1338],[2174,-1917],[967,326],[361,-1234],[-1109,-589],[-2512,1900],[-375,-951],[-1467,-420],[-56,-999],[-2263,-14],[-317,-533]],[[453578,577913],[3158,803],[1052,1123],[4075,-36]],[[558233,746730],[1699,113],[983,1413],[1567,66],[1173,1290]],[[573113,751865],[844,-1865],[-817,-966],[1,-1684],[-811,-1349]],[[254921,597903],[-2078,-3474],[-683,-1639],[139,-1535],[-529,-1131]],[[251770,590124],[-2061,-3444],[26,-582]],[[243791,590891],[444,3133],[-311,1461],[1252,4439],[3582,15],[83,1886],[-815,1878],[-1942,3246],[1158,-21],[10,3342],[5077,-10]],[[341125,537589],[-378,-3130],[-1056,-173],[-954,-4853],[616,-2938],[787,-1914],[683,144],[261,-2929],[1404,-5014],[615,-559]],[[331272,535536],[-1763,4177],[689,1820],[-47,2846],[1188,437],[897,1049],[193,1117],[-855,457],[-239,1704],[571,1863],[1337,1424],[558,1495],[-517,1442]],[[817405,638260],[69,-246]],[[269007,593543],[-716,89],[-1256,-1266],[-1862,-954],[-897,1045],[-836,-1686],[-130,-1264],[-1607,-2769],[-704,1219],[-811,-1660],[-1115,-39],[64,-2666],[-968,-1908],[-773,-72]],[[256071,584100],[275,2450],[-1210,1034],[-921,-788],[-1772,3057],[-673,271]],[[551211,756860],[226,-751]],[[552514,776837],[644,-4357],[-361,-1939]],[[537716,774380],[2200,-210],[456,970],[869,-1054],[1368,-2],[-173,1574],[966,601],[31,2172],[2445,1773]],[[545878,780204],[2472,-3252],[1114,-952],[1531,-221],[1519,1058]],[[561477,791492],[1770,-1752],[299,-963]],[[563546,788777],[-1628,-1299],[-3475,-8801],[-2216,-792]],[[556227,777885],[-1975,276],[-1738,-1324]],[[545878,780204],[-1176,2151]],[[547631,789126],[1707,-1397],[2673,100],[188,1263],[3074,777],[1209,973],[434,1370],[2671,151],[876,-1269],[1014,398]],[[847411,448364],[-195,-316]],[[844380,449187],[165,186]],[[844545,449373],[683,-512],[450,1407]],[[845678,450268],[364,208]],[[846915,451584],[90,154]],[[847005,451738],[631,-1069],[-477,-428],[252,-1877]],[[890964,489271],[628,-15]],[[891592,489256],[0,-1148]],[[891592,488108],[4,-20988],[-314,-2334],[315,-979],[3,-13114]],[[891600,450693],[-144,200]],[[826595,529426],[-23,-50]],[[804524,516729],[69,-2445],[2367,-4460],[1200,920],[2311,-106],[857,853],[298,1752],[806,711],[1298,47],[126,-651],[1760,-1311],[779,1175],[1787,195],[791,3039],[-123,1602],[1091,1616],[-258,1883],[1023,1145],[310,2437],[7,2921],[910,2429],[3346,-69],[1316,-986]],[[720385,689001],[85,-152]],[[721990,687748],[22,-32]],[[725028,683425],[-1281,-1568],[-817,-2823],[-512,-3514],[3051,-2934],[1899,-2772],[375,277],[2070,-2339],[1547,-877],[1497,41],[728,672],[1442,-1141],[209,-1527],[1688,-1777],[765,585],[469,-1185],[1747,-387],[931,-826],[874,713],[754,-1156],[2132,414],[296,1746],[-492,2424],[349,4364]],[[770339,671893],[36,-1660],[-1211,-1741],[385,-3210],[-1035,1405],[-1677,-724],[-434,-1009],[-2157,-2663],[10,-3294],[-1606,-4726],[426,-1154],[-1152,-4306],[-459,-2639],[-2279,862],[299,-2014],[-137,-3255],[-559,-596],[-238,-1859],[232,-2121],[-549,-2112],[-765,754],[-317,-906]],[[689347,646059],[1553,636],[11,1783],[2308,44],[436,-595],[2308,1455],[470,-1068],[911,960],[10,1704],[-1099,4356],[-10,1446],[-1066,234],[-457,1206],[157,3326],[-1907,1973],[272,2193],[1601,3995],[720,1043],[927,-1754],[3147,1384],[1310,4676],[1560,1641],[1328,5365],[1188,942],[250,2026],[1223,2714],[815,836],[-320,895],[-22,3124],[638,1397],[1429,1135],[221,823],[-1877,1420],[15,1414],[-857,66],[-142,1321],[-859,1484],[433,1569],[-414,1666],[682,1196],[-824,170],[-389,1816],[420,1944],[942,663],[3914,-1554],[921,988],[1538,391],[1261,2216]],[[714023,712724],[2086,2287]],[[716109,715011],[31,-68]],[[719756,701565],[63,-322]],[[720045,697641],[8,-373]],[[649761,725957],[2308,938],[918,2374],[1397,1168],[1806,-156],[589,1043],[2091,-196],[395,-1341],[3056,-2082],[1055,266],[1350,-1024],[724,-1965],[1390,-1280],[774,-1927],[2162,29],[396,-6060]],[[669733,683211],[-724,-1598]],[[669009,681613],[1319,-2879],[846,-3442],[742,-1452],[2424,-2041],[1,-5639],[1122,13],[384,-758],[-381,-2719],[-2024,-619],[-556,-1209],[-1026,-679],[-559,-2805],[-224,-3357]],[[671077,654027],[-152,-40]],[[634851,682228],[-455,1586],[-1022,1395],[-12,3106],[-920,74],[0,2359],[418,2334],[-1274,3728],[-694,254],[-2067,2741],[-735,168],[92,1611],[-740,2253],[-1340,2139],[113,2632],[668,2271],[1266,1950],[-452,2349],[545,1756],[-1233,96],[-1005,1058],[-918,3026],[-739,3652]],[[624347,724766],[-566,3567],[-972,969],[609,2658],[-1132,6047],[1017,265],[549,2052],[640,-700]],[[633274,682349],[-850,668],[-1551,-795],[-580,-2511],[-1040,-2615]],[[629253,677096],[-486,-193],[-4555,770],[-5163,7712],[-2176,3466],[-4737,5087],[-3399,1099]],[[608737,695037],[283,1342],[-527,842],[-789,5208]],[[607704,702429],[5322,5687],[826,574],[577,2014],[60,3076],[383,2087],[-301,2565],[475,2614],[1033,489],[1584,3030]],[[617663,724565],[1155,1560],[2949,-879],[491,533],[746,-1987],[1343,974]],[[599409,698654],[-656,-2011]],[[598753,696643],[-995,823],[-659,-2213],[688,-2435],[-919,-2092],[1605,489]],[[598473,691215],[-77,-912]],[[598396,690303],[-61,-562]],[[598335,689741],[107,-581],[-830,-4216],[-464,-5130]],[[595125,689609],[645,2231]],[[597523,700720],[841,-48],[1272,2110]],[[599636,702782],[123,-2857],[-350,-1271]],[[538055,780323],[-894,-1532],[707,-500],[-175,-2032],[417,-1460]],[[608737,695037],[-509,-768],[-5566,-2982],[2838,-5874],[-963,-1106],[-456,-1886],[-1984,-764],[-775,-2197],[-1280,-1805],[-2957,966]],[[598473,691215],[301,1695]],[[598774,692910],[-21,3733]],[[599409,698654],[1624,-2062],[1240,-413],[5431,6250]],[[714023,712724],[-783,3269]],[[665127,772295],[-32,-131]],[[665095,772164],[-1906,1936]],[[663189,774100],[33,118]],[[722803,754670],[-801,1322],[-1197,263],[-1010,1885],[-1673,527],[-7596,404],[-428,-701],[-1633,532],[-2329,1990],[-1814,-1407],[-176,-3518],[-2055,1356],[-2601,1092],[-1556,-525],[-860,-2873]],[[697074,755017],[-1475,-1007],[-890,-1529],[-2863,-2687],[-1335,-2907],[46,-1282],[-1536,885],[-311,2294],[-3406,-103],[-586,4833],[-1359,59],[252,5841],[-825,-674],[-853,2568],[-1641,2395],[-1284,-969],[-3434,455],[-3380,-805],[-2304,4008],[-424,1334],[-2646,2687]],[[666820,770413],[22,2100]],[[662809,774782],[-59,-262]],[[662750,774520],[-391,14],[-6872,-3247],[5,-21758]],[[655492,749529],[-1200,-353],[-822,1158],[-960,2731],[-2175,2465],[-2419,-766],[-2100,-2521]],[[636756,779239],[-1728,1359],[-14,1181],[984,52],[-2360,5751],[-2271,-26],[-800,3220],[-954,757],[116,2330],[866,1735],[-590,1592],[528,2877],[929,2493],[1053,619],[2024,-3256],[1136,1095],[-606,3552],[1940,1416],[485,1372],[2080,1221],[1520,2605],[1529,-1504],[2740,1220],[667,-1182],[2131,4],[1954,-2175],[1690,-2696],[214,2002],[2264,-2348],[710,2],[1927,2473],[1445,270],[1196,-1044],[1102,1201],[1445,-166],[1457,-2187],[2580,-666],[396,1287],[2742,-615],[1243,981],[543,2184],[-617,1257],[-2495,1239],[-1109,1928],[1680,1033],[859,1445],[-492,2073],[681,1350],[2574,-171],[112,973],[-2265,1062],[933,1399],[-1543,583],[984,2533],[1653,-609],[3181,941],[3853,1653],[1935,-118],[887,1534],[2071,261],[4085,1215],[3567,3064],[3347,-1346],[1544,845],[1243,-4181],[-257,-2293],[676,-320],[2591,675],[375,-1824],[606,1007],[2935,-1213],[-776,-699],[-77,-2116],[1353,980],[1368,-783],[279,946],[3347,2717],[3279,1993],[-726,-2961],[3135,-3338],[2142,-4388],[2758,-6785],[1437,-4257],[1216,1017],[68,1404],[1193,582],[538,-1853],[1096,-1356],[2856,-73],[2398,1581],[1633,-1302],[1050,-3172],[1851,-1053],[840,-2737],[2470,-594],[1203,654],[1968,-3103]],[[616344,528283],[-1506,-4598],[-1048,-2293],[39,-21831],[1509,-4159],[30,-729]],[[608949,476917],[-3957,6031],[-524,1269],[98,2458],[-9967,11867]],[[594599,498542],[20,51]],[[594373,505981],[-1,1]],[[594372,505982],[1410,4908],[850,1118],[493,2445],[-165,4955],[-1272,4051],[-153,3128],[-633,720],[-525,2413]],[[594377,529720],[3590,7536]],[[704532,738430],[-2755,-373],[-1139,-1057],[-1178,403],[-932,1944],[-2048,-1128],[-348,896],[-3639,-235],[26,2629],[1833,1384],[1346,-906],[1407,1123]],[[697105,743110],[963,285],[1077,-797],[1219,1696],[629,-219],[2098,2277],[-1368,579],[-1267,1718],[-794,126],[-593,2051],[-713,-2400],[-1739,749],[-1671,1830],[1836,2655],[1074,849],[-782,508]],[[785927,574073],[-548,2269],[52,1994],[-711,1444],[-499,5154],[631,271],[1005,3264],[807,1161],[4388,564],[819,-1187],[304,704]],[[792175,589711],[464,-1402],[503,276],[1036,-1373],[612,738],[-406,1742],[1453,1393],[884,-1561],[1943,2313]],[[798664,591837],[-522,-3427],[761,-4081],[-362,-2414],[223,-2905],[-450,-1656],[-651,98],[-635,-1182],[-1435,-765],[-4,-1485],[-1129,357],[-429,-729],[13,-2018],[866,-1671],[-11,-1288],[-1136,1156],[-2035,-611],[-477,-2088],[-1179,-730]],[[851760,728554],[1487,3097],[2416,23],[932,1866]],[[559895,755010],[-1396,-451],[-1371,-1763]],[[555733,756786],[778,1663]],[[556511,758449],[1164,2552],[1743,-3005],[1006,-484],[-529,-2502]],[[634562,673818],[-2142,-58],[-662,2704],[-2505,632]],[[783687,637302],[1263,-2814],[314,-1435],[706,114],[-273,-2461],[704,-2217],[1472,-1153],[1160,1446],[1475,-1745],[-598,-1216],[697,-396],[859,-2112],[-1060,-2414],[-1430,383],[-377,-1986],[2278,-3179],[1196,-903],[-169,-1190],[1035,-1753],[647,-2467],[2253,-4643],[538,-2933],[1945,-2465],[-640,-1425],[1354,-3242],[-480,-1631],[108,-1628]],[[792175,589711],[812,1089],[104,4922],[303,2009],[-600,1703],[-997,1024],[-824,2887],[182,3867],[-1371,3054],[-1037,2981],[-1836,530],[-658,-2251],[-1207,-1156],[-1432,2235],[-2767,-4331],[-546,618],[568,2664],[-174,2213],[655,3377],[-207,3384],[-1629,-287],[-632,1518],[404,1970],[-626,1761],[-543,-410]],[[778117,625082],[353,2451],[876,561],[533,2889],[1062,1510]],[[599933,709876],[1269,-93],[422,-2324],[-1785,-3280],[-203,-1397]],[[468034,545635],[666,1931],[1723,3121],[213,1847],[503,512],[295,1941]],[[569389,635030],[-2,-11809],[-2776,-39],[0,-2958]],[[566611,620224],[-5322,5606],[-6655,7008],[-3992,4205],[-6242,6574],[-2792,-2660]],[[541608,640957],[-2079,-2238],[-2082,3328],[-4203,2001]],[[526440,683810],[1046,935],[892,2346],[-281,4032],[1976,3654],[1885,1973],[-1,4552]],[[579824,326379],[-958,-270],[-1038,-2931],[-737,251],[-1012,1683],[-936,3862],[675,857],[1225,3432],[2472,2123],[1877,-3010],[248,-1066],[-813,-3847],[-1003,-1084]],[[565235,824281],[-87,1206],[-1909,1264]],[[563239,826751],[181,2854],[-734,1307],[-1374,27],[-2324,1188]],[[558988,832127],[1,41]],[[558461,836902],[2884,1994],[4801,-459],[855,-385],[2174,793],[463,-1171],[1433,-416],[2799,-2741]],[[575977,845540],[1236,-1251],[-437,-2793],[1288,-1777],[124,-2387]],[[475879,668566],[-373,0],[63,-3174],[-1718,-191],[-894,-1348],[-1434,0],[-1865,886],[-1305,-753],[152,-1481],[-1056,-3135],[-868,-434],[-1112,-7111],[-1750,-2545],[-694,-2488],[-1278,-1128],[-695,-2251],[-556,-6520],[-1138,-2662],[-583,-2430],[-2528,237],[-3478,-415]],[[452769,631623],[199,2840]],[[463177,667252],[223,1310]],[[578367,773986],[-313,3094],[402,2835],[-479,3122],[-2042,3919],[-989,3053],[-1005,621]],[[573941,790630],[2584,1290],[1693,-1419],[2595,-1556],[186,-3080],[1082,-1236],[63,-1676],[849,-800],[-111,-2835],[-1921,1046],[-608,-609],[57,-2217],[-2043,-3552]],[[187598,692927],[3952,-2631]],[[191550,690296],[7854,31]],[[199404,690327],[7,2665]],[[199411,692992],[4885,-54],[549,-1336]],[[204845,691602],[2722,-4369]],[[207567,687233],[825,-955],[1319,-5737]],[[209711,680541],[1093,-1727]],[[210804,678814],[2369,-2281]],[[213173,676533],[1089,1522]],[[214262,678055],[364,2286]],[[214626,680341],[1292,1347],[1947,-368],[1471,-2067]],[[219336,679253],[1056,-2321]],[[220392,676932],[1008,-4389]],[[221400,672543],[2196,-4617]],[[223596,667926],[136,-2913]],[[223732,665013],[790,-2918]],[[224522,662095],[178,-694],[2848,-2266],[2011,-1149]],[[229559,657986],[590,539]],[[174644,697459],[6675,1079]],[[181319,698538],[-308,-1227]],[[181011,697311],[6587,-4384]],[[559895,755010],[2171,394]],[[511743,618128],[19,-12717],[-314,-3783],[-679,-3570],[-1035,-2363],[-6123,-498],[-945,-1691],[-2063,-447]],[[468362,578206],[-2,3185],[-680,2535],[-546,-320],[-619,1879],[98,3398],[-581,1493],[-145,2076]],[[465887,592452],[488,-377],[644,1480],[313,2550],[847,1184],[1409,-2810],[699,1609],[2098,-290],[2123,725],[10179,1],[424,4660],[-747,1693],[-1105,20579],[-1576,29341],[4920,5]],[[778117,625082],[-645,639],[-1198,-364],[119,-1039],[-1336,-864],[-289,-1593],[-1882,-487],[-623,348],[-550,-1715],[-308,-3129],[133,-1843],[-747,-750],[409,-1208],[446,-3608],[1795,-4180],[109,-1443],[944,-3266],[-627,-771],[-75,-3834],[-1040,-1182],[153,-2307],[900,-2694],[1010,-1837],[564,-1974],[-81,-3633],[825,-3291],[584,-4543],[-1180,-4004],[-1202,-2633],[-152,-2787]],[[553317,762628],[992,-1902],[2202,-2277]],[[553773,755249],[-181,-138]],[[553378,754483],[347,-292]],[[743928,795977],[1051,1713],[2266,126],[1793,1450],[-28,1099],[2809,1892],[4721,3802],[2079,-1541],[3189,-282],[1010,-3156],[1380,-523],[2057,458],[3149,-770],[619,-901],[2486,2057],[489,2697],[-1262,2679],[338,2151],[2628,4555],[2857,-2143],[1521,-174],[2533,-1620],[2029,-588],[492,-4553],[1097,-1172],[2636,-1472],[4864,1984],[2318,-1001],[1370,48],[1450,-1915],[1985,-383],[238,-1960],[1612,-1606],[1731,71],[2675,-974],[1744,-25],[1414,1124],[2064,405],[2710,1136],[302,1073],[3146,2827],[1240,-241],[1475,-1687],[1232,-405],[1158,772],[1524,-1108]],[[591350,345650],[-2148,58]],[[589202,345708],[-146,4865],[-311,359]],[[588745,350932],[104,8869],[-517,3368],[-706,2428],[-716,6400]],[[586910,371997],[3009,6323],[296,3684],[542,1166],[928,3806],[-637,2873],[-169,2291],[768,3807],[-125,9758],[-1958,1562],[-843,118],[-700,1272],[-1254,1129],[-2218,168],[-116,2086]],[[584433,412040],[-456,3868],[1226,1014],[7024,4773]],[[592227,421695],[1207,-3286],[1934,945],[479,-1123],[99,-4142],[-813,-3497],[409,-1846],[2486,-5319],[-342,3180],[531,2368],[1103,605],[382,6911],[-127,1308],[-1666,4587],[-1075,2219]],[[596834,424605],[3,366]],[[597102,435195],[7,928]],[[597109,436123],[1865,-23],[429,765],[1128,-1290],[909,-270],[983,859],[1390,-825],[2232,2558],[876,-797],[842,1092],[1463,630],[1853,1788],[1319,2111]],[[465887,592452],[-1606,2569],[-1936,5341],[-869,24],[-1199,2560],[-1918,573],[-2160,-1137],[-1308,274],[-823,-4105]],[[452643,627982],[233,3099],[10967,28],[-217,6885],[-200,1523],[374,1464],[1143,1606],[1658,1163],[20,14976],[9261,0],[-3,7645]],[[596829,424051],[5,554]],[[592227,421695],[-583,-52],[-889,2440],[821,2283],[150,3522],[1045,834],[-404,2235],[-72,3422],[426,2234],[-329,1566],[1105,1795],[-362,2108],[-604,1165],[-321,2439],[-766,1297]],[[591444,448983],[1391,-1188],[1480,-297]],[[778108,542882],[160,1362],[1714,-1455],[721,-1088],[-199,-2794],[366,-795],[1229,1605],[883,-488],[630,2470]],[[826676,529600],[-81,-174]],[[816815,531927],[28,-1]],[[564946,400206],[2484,945],[1826,-369],[907,-1482]],[[555501,357928],[0,-21769],[-859,-312],[-1416,-2576],[-897,412],[-2044,-15],[-1819,1028],[-173,2044],[-915,1908],[-835,-2494],[-856,-980]],[[541608,640957],[537,-6364],[26,-2362],[1182,-3371],[-56,-1309],[1005,-2549],[-594,-2364],[-724,-17748],[-3074,-6862],[-2554,-8113],[439,-4006]],[[537795,585909],[-785,-200],[-1858,-2039],[-533,-1380],[-2920,1540],[-1258,106],[-2151,-601],[-1580,-2722],[-2403,578],[-1821,2269],[-851,277],[-2033,-2001],[-702,637],[-1161,2938],[-2484,1595],[-695,-685],[-1162,15],[-1878,-1789],[-554,-4045],[-837,-1452],[-142,-4939]],[[537795,585909],[1271,-3709]],[[516367,805398],[-552,132]],[[585749,918146],[-25,-1452],[-1759,565],[-3505,-3625]],[[557283,913723],[-1404,-95],[563,-1581],[-971,-2356],[-4420,1221],[-1283,-3540],[-1645,823],[-3325,-4017],[767,-2197],[-2724,-3348],[169,-1089],[-2613,-1047],[-176,-4904],[-2304,-4266],[1187,-696],[-325,-2666],[-1836,360],[-1770,-796],[-1840,-3844],[606,-1724],[-288,-2421],[525,-1815],[-412,-3346],[2015,-2183],[-549,-1811],[-1080,-260],[818,-3270],[-285,-2038],[-2237,-3048],[-105,-3947],[-707,654]],[[647459,603350],[-990,3862],[-2087,10047]],[[644382,617259],[8332,5923],[1844,11884],[-1265,4160]],[[671509,653647],[-432,380]],[[307320,412833],[-113,108]],[[307956,408551],[363,-118]],[[308196,406812],[33,-40]],[[892036,450086],[-436,607]],[[891592,488108],[0,1148]],[[891592,489256],[365,-10]],[[554456,827357],[1677,-227],[7106,-379]],[[565570,809933],[1390,-3987],[-370,-2577],[-725,-194],[-2951,-4966],[446,-3071],[-753,308]],[[562607,795446],[-2497,2010],[-2844,-120],[-2387,-1110],[-875,2330],[-812,-1171],[-881,656]],[[539607,823035],[636,-341]],[[539863,823801],[-381,71]],[[539476,824343],[6,-471]],[[862574,756716],[445,-1380]],[[642410,650501],[-838,-197],[-449,1276]],[[578367,773986],[1523,-1281],[1786,1099],[840,-947]],[[563069,766802],[-576,2775],[-1124,-973],[-2042,1895],[372,1543],[-1993,2145],[2,1573],[-1481,2125]],[[563546,788777],[905,815],[2709,-1058],[1114,148],[874,-1263],[1585,1143],[1941,484],[1267,1584]],[[558952,831200],[36,927]],[[558054,832260],[-112,-611]],[[0,890205],[0,1449]],[[0,925312],[0,1133]],[[0,927721],[0,818]],[[999999,913406],[0,-23201]],[[866732,772708],[-53,-123]],[[868915,771622],[0,87]],[[606150,783708],[203,2771],[1703,1754],[2321,-62],[617,2513],[-870,1909],[956,1541],[-840,928],[1065,1139],[-109,2350],[-695,-147],[-1683,1682],[-712,-185],[-1833,1349],[-588,-784],[-1733,2911],[-2232,-1198],[-3355,1957],[-277,2988],[-688,945],[-2306,240],[-314,2579],[769,599],[-1841,3344],[-3409,-214],[-625,-1153],[-1443,-78]],[[576786,848429],[98,16]],[[576897,854139],[-40,15]],[[683568,913720],[921,-525]],[[584749,498394],[841,-2938],[45,-4593],[-764,-366]],[[580160,490226],[146,78]],[[581248,494433],[-78,295]],[[582158,496495],[981,-486],[1194,2342],[416,43]],[[452751,631365],[18,258]],[[644382,617259],[-7737,-2221],[-2834,-2751],[-1646,-4198],[-383,-1993],[-1295,-939],[-815,1867],[-1033,-221],[-2510,524],[-718,638],[-2756,-171],[-664,-438],[-1386,1135],[-631,-929],[-72,-3969],[-1016,-1882]],[[594661,560771],[-520,4],[189,2270],[-186,2095],[-2000,3858],[-275,4392],[351,3708],[-1326,34],[41,-1264],[-1846,-18],[731,-1722],[191,-3900],[-1309,-2341],[-772,-2615],[-1195,-2500],[-1348,-335],[-2046,3168],[-1104,-1258],[-368,-1756],[-1315,-939],[-431,-1683],[-2210,15],[-453,1606],[-2254,84],[-1453,-522],[-1833,4011],[-259,1290],[-2031,-751],[-783,-3075],[-703,-5260],[-1069,-1311]],[[563500,569410],[173,2519],[-1017,1924],[-567,5870],[-1464,771],[1119,3194],[-335,2374],[1118,2352],[-357,2507],[804,1019],[726,2604],[5,2198],[475,1004],[2440,460],[-9,22018]],[[594377,529720],[-1352,-2756],[-1367,741],[-1837,-1031],[-499,-1055],[-995,1627],[-883,-724],[-910,623],[-872,-1747]],[[635940,571417],[-2,-10703],[-2666,-8619]],[[562607,795446],[-1130,-3954]],[[549900,856389],[43,-180]],[[589202,345708],[-328,130],[-101,-2893],[-1358,61],[-1128,1086],[-748,2062],[25,2078],[1122,3377],[578,574],[1481,-1251]],[[599701,717503],[725,-490],[616,1999],[726,372],[-276,1323],[337,2045],[2160,-943],[2098,1530],[1597,-1235],[1639,-69],[1833,856],[1915,1610],[2249,-51],[2092,1110],[251,-995]],[[688219,724942],[154,1865],[1332,3234],[108,1214],[-792,2556],[155,1735],[-1186,275],[-908,1384],[1026,2247],[2067,-502],[1288,3553],[967,366],[-191,2183],[577,1366],[830,-831],[2024,2171],[860,-1681],[-1023,-1695],[751,-1495],[847,223]],[[655492,749529],[2891,-348],[-150,3513],[346,518]],[[658579,753212],[-98,-1010]],[[660075,754430],[-26,79]],[[660049,754509],[1142,1924],[1269,-1012],[-929,1844],[1216,891],[2394,-2837],[1131,-26],[290,-2019],[638,-711],[-285,-2577],[1015,-1053],[2427,-157],[467,479],[1128,-1080],[2077,-7318],[4200,-5361],[4028,-4236],[679,178],[2145,-1994],[-298,-3458]],[[844545,449373],[448,506]],[[844993,449879],[685,389]],[[847805,449005],[-394,-641]],[[847005,451738],[291,494]],[[588280,498780],[5,-46]],[[594254,497680],[345,862]],[[597109,436123],[-62,678]],[[591444,448983],[-1352,1488],[-1363,606],[-1133,2019],[-969,612]],[[586627,453708],[-6,44]],[[582337,478120],[15,207]],[[584749,498394],[930,386],[2601,0]],[[594372,505982],[-81,24]],[[588243,499173],[37,-393]],[[582342,501717],[624,1207]],[[582730,503997],[-277,8]],[[586953,517904],[-112,-392]],[[352309,311155],[-25,-42]],[[351267,306014],[29,-49]],[[251173,789100],[-75,-59]],[[280385,761145],[23,13]],[[289481,768262],[-31,38]],[[311664,775792],[-173,-93]],[[312640,774914],[-1,5]],[[138294,830715],[-1003,-1519]],[[663118,773843],[71,257]],[[665095,772164],[-147,-601]],[[666819,770377],[1,36]],[[660049,754509],[-189,580]],[[658593,753357],[-14,-145]],[[662750,774520],[-4,-21]],[[581568,373230],[829,282],[2214,-1082],[1267,227],[1032,-660]],[[586662,453459],[-35,249]],[[584433,412040],[-2524,-318],[-1700,-2010],[-319,-2939]],[[579890,406773],[-220,141]],[[575057,398323],[-669,-492],[-1240,665],[-1306,-134],[-1679,938]],[[584936,456017],[-10,-250]],[[575633,399537],[498,1027]],[[194446,794974],[-11287,0]],[[183159,794974],[-1817,3409],[182,2469],[-384,2214],[-1158,1154],[-2198,3412],[-1879,3917],[-663,-456],[-1133,2619],[-1142,1346],[-1360,18],[-273,1708],[-1344,2838],[-2555,1459],[-771,2631],[2,36479]],[[166666,860191],[12153,0],[5208,0],[10417,0]],[[194444,860191],[2,-65217]],[[146674,804734],[4763,-1918],[2332,-5262],[1798,-1212]],[[155567,796342],[1385,-3802],[-271,-1473]],[[156681,791067],[-4239,2531]],[[152442,793598],[753,1585]],[[153195,795183],[-1696,-517]],[[151499,794666],[-511,1450]],[[150988,796116],[-1665,1521],[-289,1293]],[[149034,798930],[-2506,2827]],[[146528,801757],[-1706,-61]],[[144822,801696],[-116,1881]],[[144706,803577],[1164,-240],[58,1057]],[[145928,804394],[-1647,-501]],[[144281,803893],[-798,1456]],[[143483,805349],[1189,689]],[[144672,806038],[2002,-1304]],[[134017,819872],[828,-2926]],[[134845,816946],[-375,-732]],[[134470,816214],[1025,-2515],[-2622,3729],[60,1281],[-1119,819]],[[131814,819528],[2203,344]],[[142910,818356],[118,-2495]],[[143028,815861],[-470,-1357],[-187,2807]],[[142371,817311],[-427,-530],[-771,2038]],[[141173,818819],[401,1551],[1336,-2014]],[[139308,819707],[-1857,2230]],[[137451,821937],[1590,-639]],[[139041,821298],[267,-1591]],[[131512,825393],[1448,-552]],[[132960,824841],[1295,634]],[[134255,825475],[-953,-5192]],[[133302,820283],[-2316,1437]],[[130986,821720],[-577,1603]],[[130409,823323],[11,2256]],[[130420,825579],[1092,-186]],[[138819,835824],[-202,1310]],[[138617,837134],[-4105,2900]],[[134512,840034],[-691,-52]],[[133821,839982],[-576,2586],[-4971,9944],[-3119,3456]],[[125155,855968],[-298,1719]],[[124857,857687],[-1180,1273],[-2349,-1117]],[[121328,857843],[-713,-2681],[-2389,-1476]],[[118226,853686],[-430,1914]],[[117796,855600],[-4064,4594]],[[113732,860194],[7956,-3],[7932,0],[5287,0],[7932,0],[5287,0],[7932,0]],[[156058,860191],[10608,0]],[[183159,794974],[-5516,0]],[[177643,794974],[-2753,0]],[[174890,794974],[-7396,0]],[[167494,794974],[-8574,0]],[[158920,794974],[-724,0]],[[158196,794974],[-795,2762]],[[157401,797736],[-1545,209]],[[155856,797945],[-35,2883]],[[155821,800828],[-659,-1117]],[[155162,799711],[-1780,1348]],[[153382,801059],[-768,2925]],[[152614,803984],[-3864,212],[1534,798]],[[150284,804994],[-1721,237]],[[148563,805231],[-319,1130]],[[148244,806361],[-1182,-282]],[[147062,806079],[-1807,1681]],[[145255,807760],[176,1939]],[[145431,809699],[-623,1758]],[[144808,811457],[214,3046]],[[145022,814503],[-863,-2968]],[[144159,811535],[-708,2195]],[[143451,813730],[699,4431]],[[143429,817681],[-797,2476],[-1191,732]],[[141441,820889],[-93,1622],[1590,-1307]],[[142938,821204],[-1159,2494]],[[141779,823698],[-903,-2657]],[[140876,821041],[-776,-838],[-2144,2799]],[[137956,823002],[813,2426],[-1076,1704],[2415,6170]],[[140108,833302],[-1178,-614]],[[138930,832688],[-111,3136]],[[252921,841529],[-12978,-18385],[-4259,-5483],[-5,-20456]],[[235679,797205],[-18,-2238],[-5733,8]],[[229928,794975],[-4397,-1],[-7107,0]],[[218424,794974],[-987,22745],[-773,17686],[-2,24786]],[[216662,860191],[11248,0],[8834,3]],[[236744,860194],[-45,-4347]],[[236699,855847],[319,-2357]],[[237018,853490],[1073,-913]],[[238091,852577],[-125,-2480]],[[237966,850097],[767,2741]],[[238733,852838],[2161,-22]],[[240894,852816],[2329,-8777]],[[243223,844039],[-794,-2022]],[[242429,842017],[4484,1823]],[[246913,843840],[1442,-99]],[[248355,843741],[2226,-1441]],[[250581,842300],[2340,-771]],[[322136,777316],[-788,-1048]],[[321348,776268],[-4361,-3630]],[[316987,772638],[-2744,-922]],[[314243,771716],[-701,605]],[[313542,772321],[-966,631]],[[312576,772952],[64,1967],[-976,873]],[[311664,775792],[-17,7865],[-1191,1559],[-1647,-845]],[[308809,784371],[-625,541]],[[308184,784912],[1874,1744],[3,2015],[2125,410],[689,-850],[1835,993]],[[314710,789224],[2375,-660]],[[317085,788564],[508,-1273]],[[317593,787291],[1846,893],[830,-723]],[[320269,787461],[-581,-2111]],[[319688,785350],[-1130,-1585]],[[318558,783765],[1355,-239],[-207,-1024]],[[319706,782502],[1012,-3837],[1737,-441]],[[322455,778224],[-319,-908]],[[345948,810042],[-1590,-1233],[641,-1748],[-2483,-5769]],[[342516,801292],[-356,-2642]],[[342160,798650],[1598,2823]],[[343758,801473],[238,-888]],[[343996,800585],[1754,338]],[[345750,800923],[-1417,-1733]],[[344333,799190],[-131,-1498],[2445,179]],[[346647,797871],[-104,-1672]],[[346543,796199],[2153,1954],[2361,-1232]],[[351057,796921],[128,-1069]],[[351185,795852],[-1633,-2094],[1286,-640]],[[350838,793118],[-1027,-1546]],[[349811,791572],[2807,1423]],[[352618,792995],[-218,-1524],[-1316,-1151]],[[351084,790320],[-700,-2418]],[[350384,787902],[716,-812]],[[351100,787090],[892,1987],[1158,683]],[[353150,789760],[-860,-2725]],[[352290,787035],[147,-1173],[945,1861],[357,-1302]],[[353739,786421],[-1156,-5144],[-1518,-6]],[[351065,781271],[-55,2711]],[[351010,783982],[-1493,-1524]],[[349517,782458],[846,3001]],[[350363,785459],[-896,2801]],[[349467,788260],[-1030,-2871]],[[348437,785389],[-817,58]],[[347620,785447],[-1275,-2839]],[[346345,782608],[-1314,-228],[-138,1210],[964,528]],[[345857,784118],[1728,2431],[-1380,533],[-190,-946],[-1188,171]],[[344827,786307],[12,1712]],[[344839,788019],[-1010,-875]],[[343829,787144],[-2031,-574]],[[341798,786570],[-3835,606]],[[337963,787176],[-2177,-629]],[[335786,786547],[-431,2517]],[[335355,789064],[2616,3120]],[[337971,792184],[-1072,450],[868,2880]],[[337767,795514],[1177,861]],[[338944,796375],[-100,1854],[1994,6850],[1522,3414]],[[342360,808493],[2013,1739],[1575,-190]],[[341388,809491],[-2,3304],[-11295,-1],[-6776,0],[-1264,2706],[1811,1369],[-728,2441],[-1408,-1692],[133,-3724],[-444,-2714],[-2352,2525],[-2496,-269],[-1188,1375],[453,3291],[-2132,-841],[361,3400],[-1442,1614],[-834,2561],[683,1201],[-302,1601],[1306,653],[-798,2486],[2184,-1408],[-342,3138],[3051,-3456],[3708,-111],[1638,-676],[667,3290],[1109,1021],[-2479,4488],[-299,3517],[580,1175],[793,4957],[-1188,351],[-906,2275],[1454,1721],[-619,1078],[1438,533],[-3487,1171],[1153,410],[-143,3137],[-1037,72],[424,2165],[-596,853],[738,1519]],[[320515,861997],[-428,-1741]],[[320087,860256],[1347,308]],[[321434,860564],[2066,-4332]],[[323500,856232],[94,-1290]],[[323594,854942],[1756,-2623]],[[325350,852319],[-1433,-1302],[2172,259]],[[326089,851276],[-1036,-2388]],[[325053,848888],[1374,360]],[[326427,849248],[1631,-1734]],[[328058,847514],[-191,-1478],[-1353,-888]],[[326514,845148],[1677,-478]],[[328191,844670],[-351,-790]],[[327840,843880],[1788,-1407],[-105,-1953]],[[329523,840520],[-1919,107]],[[327604,840627],[1770,-2004]],[[329374,838623],[-68,-2163],[1716,-223]],[[331022,836237],[1364,-1026]],[[332386,835211],[413,-1800]],[[332799,833411],[-1180,-2492]],[[331619,830919],[2384,1477]],[[334003,832396],[2116,-949]],[[336119,831447],[602,-1843]],[[336721,829604],[2272,222]],[[338993,829826],[1550,-1809]],[[340543,828017],[-2075,-1304]],[[338468,826713],[-1338,-1782]],[[337130,824931],[-3305,-1274]],[[333825,823657],[-1407,-3367]],[[332418,820290],[2798,2237]],[[335216,822527],[1861,1980],[2011,744]],[[339088,825251],[-733,738]],[[338355,825989],[2156,-387]],[[340511,825602],[780,-2198]],[[341291,823404],[-1089,-1138],[543,-774]],[[340745,821492],[1363,1602]],[[342108,823094],[2030,-901]],[[344138,822193],[867,-2225]],[[345005,819968],[-117,-4172]],[[344888,815796],[403,-2191]],[[345291,813605],[-3903,-4114]],[[322136,777316],[2050,-1544]],[[324186,775772],[1645,-68],[605,-703]],[[326436,775001],[1465,1460]],[[327901,776461],[1287,-1074]],[[329188,775387],[1280,-2341]],[[330468,773046],[-4118,-2655]],[[326350,770391],[-2201,-1192]],[[324149,769199],[-827,242]],[[323322,769441],[-437,-1166]],[[322885,768275],[-662,938],[-2397,-4603]],[[319826,764610],[-2432,-1819]],[[317394,762791],[-1077,1499]],[[316317,764290],[73,3279]],[[316390,767569],[1610,2165]],[[318000,769734],[-380,163]],[[317620,769897],[3683,3252]],[[321303,773149],[-65,-1013]],[[321238,772136],[2739,1342],[-4291,60]],[[319686,773538],[1662,2730]],[[331391,775898],[539,2552]],[[331930,778450],[1778,-263]],[[333708,778187],[103,-1152]],[[333811,777035],[-1550,-1839]],[[332261,775196],[-2494,-479]],[[329767,774717],[-587,2177],[1736,5067]],[[330916,781961],[1283,1226]],[[332199,783187],[212,-1397],[-681,-3528],[-1467,-1348],[128,-1430],[1000,414]],[[164773,916863],[10,-9267],[10642,-6944],[4258,-2777],[7806,-5092],[5096,-31],[3202,-3521],[1510,-733],[12107,-2039],[7265,-1224],[-7,-25044]],[[216662,860191],[-12497,0],[-9721,0]],[[156058,860191],[-2168,3915],[46,1723],[-2210,-955],[-4198,24],[-508,3401],[-2558,2023],[-1576,2404],[-1967,292],[161,2057],[-1411,1885],[248,1402],[-1401,1233],[803,1241],[-2265,2753],[-1240,2668],[-4151,2518],[686,1260],[-1134,1103],[1576,2209],[-1164,2556],[-2708,-394],[-129,3572],[-1167,2600],[-5751,2],[-120,3019],[-768,1376],[6,6805]],[[120990,912883],[3004,-1178]],[[123994,911705],[-1429,1307],[514,2336]],[[123079,915348],[4976,1286],[-762,-1632]],[[127293,915002],[2808,1073]],[[130101,916075],[898,1283]],[[130999,917358],[4733,1519]],[[135732,918877],[1772,1400]],[[137504,920277],[2361,-862]],[[139865,919415],[-3643,-2167],[-2508,-489]],[[133714,916759],[-4211,-3927]],[[129503,912832],[1868,-425],[-35,1566]],[[131336,913973],[2585,2089],[2153,-19]],[[136074,916043],[119,-1301],[1714,2648],[3455,1339]],[[141362,918729],[384,-1004]],[[141746,917725],[3576,3246],[-853,1857]],[[144469,922828],[2368,-1982]],[[146837,920846],[1462,-3015],[3403,-2258]],[[151702,915573],[516,2842]],[[152218,918415],[2102,1669],[889,-2492]],[[155209,917592],[-837,-1840]],[[154372,915752],[2268,-13]],[[156640,915739],[1622,2564]],[[158262,918303],[4151,-203]],[[162413,918100],[2360,-1237]],[[194439,937095],[5,-17659],[-8004,0],[-11594,6],[552,-2089]],[[175398,917353],[-774,2669]],[[174624,920022],[7063,1258],[5429,-517]],[[187116,920763],[2793,495]],[[189909,921258],[-5902,2263]],[[184007,923521],[-6204,-619]],[[177803,922902],[-4434,256]],[[173369,923158],[-1880,1533],[1249,1601],[5341,1323]],[[178079,927615],[846,975]],[[178925,928590],[-5934,-923]],[[172991,927667],[-54,1592],[-3127,163]],[[169810,929422],[-213,1770]],[[169597,931192],[2032,1642]],[[171629,932834],[-448,1607]],[[171181,934441],[2286,1760]],[[173467,936201],[8093,3209]],[[181560,939410],[1318,-609]],[[182878,938801],[152,-2422]],[[183030,936379],[-969,-1663]],[[182061,934716],[2661,676]],[[184722,935392],[811,1698]],[[185533,937090],[5384,-1584]],[[190917,935506],[-1737,-2119]],[[189180,933387],[4693,1808]],[[193873,935195],[-1266,2056],[1832,-156]],[[167398,943794],[1680,-502]],[[169078,943292],[1633,1284]],[[170711,944576],[2859,-77]],[[173570,944499],[5567,-3631]],[[179137,940868],[177,-1066]],[[179314,939802],[-9763,-4471]],[[169551,935331],[-1240,-1918],[-2144,-875],[-1221,-4189]],[[164946,928349],[-3139,-361]],[[161807,927988],[-3297,-2114],[-2976,3493]],[[155534,929367],[-4875,2725]],[[150659,932092],[2154,2668]],[[152813,934760],[419,2893]],[[153232,937653],[2887,4099]],[[156119,941752],[-2498,3437]],[[153621,945189],[9391,1077]],[[163012,946266],[4869,-1760]],[[167881,944506],[-483,-712]],[[168348,952708],[4561,2933],[-1599,-3156],[-2962,223]],[[194437,952245],[0,-4077]],[[194437,948168],[-6992,-2572]],[[187445,945596],[-2762,79]],[[184683,945675],[-1718,1990]],[[182965,947665],[3601,1240],[3236,261]],[[189802,949166],[1770,1229],[-7438,-938]],[[184134,949457],[-847,2167]],[[183287,951624],[-1209,-2052]],[[182078,949572],[-3547,-710]],[[178531,948862],[-5198,1799]],[[173333,950661],[1238,1192]],[[174571,951853],[2992,119]],[[177563,951972],[2654,1260],[-5287,-617]],[[174930,952615],[1909,1655]],[[176839,954270],[-756,1141],[2859,2156]],[[178942,957567],[3852,83]],[[182794,957650],[1029,-1449]],[[183823,956201],[3128,-30],[4569,-3870]],[[191520,952301],[2917,-56]],[[179024,963052],[-2040,-1550]],[[176984,961502],[181,-2907],[-2593,-1854]],[[174572,956741],[-2406,880]],[[172166,957621],[666,2001]],[[172832,959622],[-2809,-1607]],[[170023,958015],[-1047,-2290],[-986,1266],[-1082,-2852]],[[166908,954139],[-3074,957]],[[163834,955096],[-3836,-451]],[[159998,954645],[97,2708],[2088,238],[7011,5116]],[[169194,962707],[5030,50],[2545,1359]],[[176769,964116],[2255,-1064]],[[183799,965371],[-3325,1261]],[[180474,966632],[1941,652]],[[182415,967284],[1384,-1913]],[[193893,964006],[-4872,-1067]],[[189021,962939],[-3367,1102],[-63,2263]],[[185591,966304],[8843,1052],[-541,-3350]],[[190458,968527],[-4765,716],[7619,2064],[1122,-2577],[-3976,-203]],[[275745,817216],[-3632,1793]],[[272113,819009],[533,807]],[[272646,819816],[1977,116]],[[274623,819932],[1122,-2716]],[[280734,838063],[-2959,-1979]],[[277775,836084],[2024,3959]],[[279799,840043],[935,-1980]],[[310717,863514],[897,-667]],[[311614,862847],[-1153,-1236],[256,1903]],[[319909,868276],[-1665,1681]],[[318244,869957],[1785,75],[-120,-1756]],[[279041,874472],[614,-2284]],[[279655,872188],[-958,-2261],[-1656,1029]],[[277041,870956],[677,3109]],[[277718,874065],[1323,407]],[[304619,875284],[-1192,285]],[[303427,875569],[-1024,1666]],[[302403,877235],[1923,-856]],[[304326,876379],[293,-1095]],[[272221,877686],[-315,-1789]],[[271906,875897],[-2506,-2620]],[[269400,873277],[-1897,-294]],[[267503,872983],[-557,1873]],[[266946,874856],[1453,2538]],[[268399,877394],[3822,292]],[[284615,879658],[-1122,-1024]],[[283493,878634],[-1569,1996]],[[281924,880630],[1751,115],[940,-1087]],[[285098,881443],[642,556],[1267,-1708],[-1909,1152]],[[264112,891353],[853,1103],[7118,-4757]],[[272083,887699],[1039,-2559],[-630,-1073]],[[272492,884067],[2983,348]],[[275475,884415],[1463,-1942]],[[276938,882473],[-2067,-1781]],[[274871,880692],[-3699,1453]],[[271172,882145],[-248,1304]],[[270924,883449],[-2853,1020]],[[268071,884469],[-650,-1693]],[[267421,882776],[-2513,-2986]],[[264908,879790],[-2396,-1008]],[[262512,878782],[-858,3361],[-2896,-777]],[[258758,881366],[-950,574]],[[257808,881940],[2603,2752],[-340,2542]],[[260071,887234],[1146,6745]],[[261217,893979],[1131,1269]],[[262348,895248],[1764,-3895]],[[264792,893213],[-1282,1457]],[[263510,894670],[549,1112]],[[264059,895782],[733,-2569]],[[267428,894527],[-1090,-148]],[[266338,894379],[-802,2127],[1892,-1979]],[[295495,906299],[-2644,265],[-370,1413],[3532,-575]],[[296013,907402],[-518,-1103]],[[259456,906015],[-1011,2159]],[[258445,908174],[1496,493]],[[259941,908667],[-485,-2652]],[[291239,908966],[74,-4127]],[[291313,904839],[-1813,-1504],[-3403,-98]],[[286097,903237],[-836,2600],[1571,3113]],[[286832,908950],[2957,540],[1450,-524]],[[291997,909646],[-1443,1047],[1158,724]],[[291712,911417],[285,-1771]],[[279970,912589],[-542,459]],[[279428,913048],[2248,2652]],[[281676,915700],[1021,-396]],[[282697,915304],[-2727,-2715]],[[286124,914356],[-898,1615]],[[285226,915971],[1591,-74],[-693,-1541]],[[277839,916524],[-2223,991]],[[275616,917515],[3743,655],[-1520,-1646]],[[232499,915545],[1985,-3018]],[[234484,912527],[-2266,-2158]],[[232218,910369],[-2974,431]],[[229244,910800],[-5469,2217]],[[223775,913017],[-41,1263],[2777,1206]],[[226511,915486],[521,2488],[1327,635]],[[228359,918609],[975,-1297],[3165,-1767]],[[164773,916863],[2302,-1330],[2734,-506]],[[169809,915027],[2148,-1269],[5655,-1220],[1190,804]],[[178802,913342],[3380,-1855],[1250,-1543]],[[183432,909944],[-2225,-764],[-1858,-2180]],[[179349,907000],[4868,-1198]],[[184217,905802],[3463,-90]],[[187680,905712],[4529,875],[1952,947]],[[194161,907534],[1310,-1538]],[[195471,905996],[2882,-840]],[[198353,905156],[1843,-2750]],[[200196,902406],[-1574,-204]],[[198622,902202],[2183,-2087],[232,1559]],[[201037,901674],[1306,-719]],[[202343,900955],[-2266,5037]],[[200077,905992],[587,1779],[3713,997]],[[204377,908768],[1785,1931]],[[206162,910699],[-2297,-1002]],[[203865,909697],[-2809,-156]],[[201056,909541],[-318,-933]],[[200738,908608],[-2645,614]],[[198093,909222],[1036,1975]],[[199129,911197],[5969,1833],[1736,-1193]],[[206834,911837],[309,-1542]],[[207143,910295],[3430,-2530]],[[210573,907765],[1999,496]],[[212572,908261],[2172,-1799],[3159,-701]],[[217903,905761],[3052,868]],[[220955,906629],[3637,-687],[2041,495]],[[226633,906437],[2659,-1127]],[[229292,905310],[690,1410]],[[229982,906720],[-2512,285],[-1499,2729]],[[225971,909734],[3444,787],[1904,-2578]],[[231319,907943],[2097,1113]],[[233416,909056],[-1115,-4119]],[[232301,904937],[639,-1671]],[[232940,903266],[1171,265]],[[234111,903531],[1016,-1990]],[[235127,901541],[265,1670]],[[235392,903211],[-1089,2813]],[[234831,907706],[1666,120],[3381,3504]],[[239878,911330],[-2552,1651]],[[237326,912981],[1336,1328]],[[238662,914309],[-525,1891],[-4943,2228]],[[233194,918428],[-1377,2939],[1853,1314]],[[233670,922681],[-1862,1538],[398,2755]],[[232206,926974],[2338,374],[-856,1401]],[[233688,928749],[1864,1958]],[[235552,930707],[1789,446]],[[237341,931153],[4468,-4247]],[[241809,926906],[-91,-2428]],[[241718,924478],[2771,-3358]],[[244489,921120],[20,-1462]],[[244509,919658],[-2531,-2195]],[[241978,917463],[2711,-812],[3769,-158]],[[248458,916493],[-1896,-1297]],[[246562,915196],[2137,-2499]],[[248699,912697],[-293,-2305]],[[248406,910392],[1109,-1212],[1411,4410]],[[250926,913590],[1694,1490],[2821,-2691],[413,-3339],[-1262,237],[419,-3095]],[[255011,906192],[2581,-3447]],[[257592,902745],[2028,1968]],[[259620,904713],[1623,3296]],[[261243,908009],[1207,4132]],[[262450,912141],[1821,1801],[-1457,936]],[[262814,914878],[-78,3659],[3259,-87]],[[265995,918450],[1601,-800]],[[267596,917650],[3586,-343],[-1057,-874]],[[270125,916433],[3962,-2218]],[[274087,914215],[-1748,-1400]],[[272339,912815],[1879,-1341]],[[274218,911474],[-3531,-1249]],[[270687,910225],[1600,-3463]],[[272287,906762],[1962,-2382]],[[274249,904380],[-548,-2310],[-3261,-2858]],[[270440,899212],[-2449,-1296]],[[267991,897916],[-1103,1836],[-2571,2073]],[[264317,901825],[2833,-4376]],[[267150,897449],[-1813,-656]],[[265337,896793],[-2677,2121]],[[262660,898914],[-3309,-35]],[[259351,898879],[1640,-3015],[-3467,-3955],[-1885,-36],[-4943,3479]],[[250696,895352],[-3501,175],[3393,-1356],[2262,-2301]],[[252850,891870],[5407,-890]],[[258257,890980],[-703,-2203]],[[257554,888777],[-2293,-3809]],[[255261,884968],[-1977,-1132]],[[253284,883836],[-3751,-80]],[[249533,883756],[-215,-1995],[-1573,-362],[-2862,691],[3042,-2050]],[[247925,880040],[-86,-2251],[-1864,-992]],[[245975,876797],[-2534,90],[981,-1652]],[[244422,875235],[-1510,36]],[[242912,875271],[66,-2240]],[[242978,873031],[-2928,-1341]],[[240050,871690],[750,-1036]],[[240800,870654],[-2080,-2662]],[[238720,867992],[-21,-1061],[-1607,-4281]],[[237092,862650],[-348,-2456]],[[194439,937095],[3463,-2553],[1513,-4739]],[[199415,929803],[2511,850],[-1081,1509]],[[200845,932162],[-1507,5667]],[[199338,937829],[581,1439]],[[199919,939268],[2995,-430],[3162,-1573]],[[206076,937265],[4064,-9341]],[[210140,927924],[-611,-1954]],[[209529,925970],[1711,-2023]],[[211240,923947],[2511,-638],[4131,-3081],[1444,-143]],[[219326,920085],[299,-2344],[-3609,753]],[[216016,918494],[-1075,-1723]],[[214941,916771],[-2050,794]],[[212891,917565],[747,-2805]],[[213638,914760],[2608,1633],[818,-2747],[-2884,-1187]],[[214180,912459],[-6141,574]],[[208039,913033],[240,953]],[[208279,913986],[-3115,478]],[[205164,914464],[-1441,1644],[-2167,-2591]],[[201556,913517],[-4184,-1435],[-6569,-1291]],[[190803,910791],[-5047,-284]],[[185756,910507],[-1359,2040]],[[184397,912547],[-214,2113]],[[184183,914660],[-4069,413]],[[180114,915073],[-3763,947]],[[176351,916020],[-953,1333]],[[202996,940045],[854,1278]],[[203850,941323],[3059,415],[2400,-896]],[[209309,940842],[183,-1543]],[[209492,939299],[-1963,-2571],[-4533,3317]],[[279063,941080],[3474,67]],[[282537,941147],[3000,-985]],[[285537,940162],[2547,-2480]],[[288084,937682],[-308,-1543],[-3987,452]],[[283789,936591],[-4624,-835]],[[279165,935756],[-1897,2777]],[[277268,938533],[-1780,924]],[[275488,939457],[171,2235],[3404,-612]],[[259474,925417],[4151,836]],[[263625,926253],[624,-889]],[[264249,925364],[584,3462]],[[264833,928826],[-3477,2372]],[[261356,931198],[1405,1353],[2929,-962],[-2749,2185]],[[262941,933774],[-843,2091],[1740,3325],[3434,482]],[[267272,939672],[3118,1852],[3481,-563],[3144,-5266],[-2652,-2571]],[[274363,933124],[1030,-2372],[2268,2861]],[[277661,933613],[3730,-255],[2703,-1014]],[[284094,932344],[-2036,2147],[4349,1056]],[[286407,935547],[4442,-1421]],[[290849,934126],[1086,-2253]],[[291935,931873],[1695,-297]],[[293630,931576],[-308,-2239]],[[293322,929337],[4172,31],[4572,-1872]],[[302066,927496],[-663,-1520],[1951,-12]],[[303354,925964],[381,-1376]],[[303735,924588],[-1724,-2195]],[[302011,922393],[3684,2042]],[[305695,924435],[4421,-1908]],[[310116,922527],[-1557,-1872]],[[308559,920655],[485,-1575],[1732,2213]],[[310776,921293],[2102,-1660]],[[312878,919633],[395,-1800]],[[313273,917833],[-2479,139]],[[310794,917972],[-1108,-1048]],[[309686,916924],[4839,-1425]],[[314525,915499],[-89,-1090]],[[314436,914409],[-3154,565]],[[311282,914974],[518,-1241]],[[311800,913733],[-795,-2890]],[[311005,910843],[3678,-624]],[[314683,910219],[-325,-1362]],[[314358,908857],[1719,384],[-560,-2229],[3991,825]],[[319508,907837],[2280,-2491]],[[321788,905346],[889,-2126]],[[322677,903220],[2211,-173],[-1837,-2444]],[[323051,900603],[4814,1166],[1858,-2196]],[[329723,899573],[-1564,-1989]],[[328159,897584],[-1918,557]],[[326241,898141],[1560,-2201]],[[327801,895940],[-1663,-5]],[[326138,895935],[-191,-2337]],[[325947,893598],[-1885,-137],[-747,-4081]],[[323315,889380],[-803,1074]],[[322512,890454],[-1832,43]],[[320680,890497],[-2351,3836]],[[318329,894333],[1103,1858]],[[319432,896191],[-2282,-478]],[[317150,895713],[-2671,3310]],[[314479,899023],[-1455,-1493],[-1548,1105]],[[311476,898635],[1904,-2700]],[[313380,895935],[-2982,-568],[3163,-2952],[-578,-496]],[[312983,891919],[1832,-2268],[2022,-521]],[[316837,889130],[2400,-2661]],[[319237,886469],[-265,-2421]],[[318972,884048],[1365,0]],[[320337,884048],[456,-4526],[-1882,2963]],[[318911,882485],[396,-3137]],[[319307,879348],[1016,-1969]],[[320323,877379],[-1331,179]],[[318992,877558],[313,-1697]],[[319305,875861],[-3261,2731],[-529,-473]],[[315515,878119],[-2126,1645]],[[313389,879764],[-1982,2540]],[[311407,882304],[474,-1842]],[[311881,880462],[-2142,1793],[-1159,-131]],[[308580,882124],[2138,-3145],[1293,-467]],[[312011,878512],[4710,-5241]],[[316721,873271],[-768,-2018]],[[315953,871253],[-3287,1676]],[[312666,872929],[-2607,498],[-1955,1007]],[[308104,874434],[-1285,2011],[-1920,111]],[[304899,876556],[-2826,1653],[-1998,2296],[1436,487],[-2131,1165]],[[299380,882157],[-3390,4234]],[[295990,886391],[-4091,2183]],[[291899,888574],[767,-1391],[-5788,-1869],[-2965,743]],[[283913,886057],[-1065,1485]],[[282848,887542],[219,1905]],[[283067,889447],[2041,1524]],[[285108,890971],[95,1520]],[[285203,892491],[4162,-1340],[333,525]],[[289698,891676],[5994,1005]],[[295692,892681],[-542,1668],[-1911,2205],[3203,3175],[2947,3433]],[[299389,903162],[-3020,6597]],[[296369,909759],[-937,-434]],[[295432,909325],[-2788,2445],[-574,2124],[-4302,-2212]],[[287768,911682],[-427,1878]],[[287341,913560],[1676,127]],[[289017,913687],[463,1704]],[[289480,915391],[-1725,727]],[[287755,916118],[-292,1438]],[[287463,917556],[-2996,958]],[[284467,918514],[-697,2378]],[[283770,920892],[-1223,-106]],[[282547,920786],[-2176,2217],[-781,-1369],[1272,-2338],[-2018,-491]],[[278844,918805],[-5399,1283]],[[273445,920088],[-1608,-1600]],[[271837,918488],[-2646,964]],[[269191,919452],[-2133,-244]],[[267058,919208],[-6842,1081]],[[260216,920289],[-839,1516]],[[259377,921805],[-3546,-884]],[[255831,920921],[-2632,1606]],[[253199,922527],[-1688,3192]],[[251511,925719],[4475,-695]],[[255986,925024],[1957,398]],[[257943,925422],[-2033,1166],[-3353,471]],[[252557,927059],[-2129,1211]],[[250428,928270],[-498,2703]],[[249930,930973],[453,2745]],[[250383,933718],[1662,3892],[4288,3875]],[[256333,941485],[2641,658]],[[258974,942143],[4608,-153]],[[263582,941990],[-4219,-5554]],[[259363,936436],[1647,-6515]],[[261010,929921],[2814,-2475]],[[263824,927446],[-4350,-2029]],[[224561,941535],[4378,925]],[[228939,942460],[1612,-1311],[-753,-1655]],[[229798,939494],[-3234,-2290]],[[226564,937204],[2670,-48]],[[229234,937156],[880,-2070]],[[230114,935086],[1713,331]],[[231827,935417],[-705,-2280]],[[231122,933137],[507,-2844]],[[231629,930293],[-2691,-1209]],[[228938,929084],[-2435,850]],[[226503,929934],[723,-1969]],[[227226,927965],[-2690,-437],[-3965,4651]],[[220571,932179],[-3137,964],[-2750,2773]],[[214684,935916],[2198,1623]],[[216882,937539],[1588,-1840]],[[218470,935699],[2405,158]],[[220875,935857],[1078,1127]],[[221953,936984],[-951,1726]],[[221002,938710],[-2810,1045]],[[218192,939755],[1488,2218]],[[219680,941973],[2536,833]],[[223930,942411],[4686,1359],[-1589,-1423],[-3097,64]],[[241192,944080],[2634,-1117]],[[243826,942963],[5189,-616]],[[249015,942347],[-4898,-6604],[-5815,19]],[[238302,935762],[1822,-1989]],[[240124,933773],[-1340,-2325]],[[238784,931448],[-3209,-8]],[[235575,931440],[-985,4468]],[[234590,935908],[-237,5414]],[[234353,941322],[2598,-189]],[[236951,941133],[-951,2135]],[[236000,943268],[5192,812]],[[210778,949266],[-2132,660]],[[208646,949926],[1503,1671]],[[210149,951597],[1956,-1581]],[[212105,950016],[-1327,-750]],[[240159,949216],[-12,-1995]],[[240147,947221],[-3196,-291]],[[236951,946930],[-5173,2063],[2469,3190]],[[234247,952183],[3455,383]],[[237702,952566],[2457,-3350]],[[216034,955063],[-3020,-1485],[-2878,2478]],[[210136,956056],[4908,588]],[[215044,956644],[990,-1581]],[[211047,958429],[2699,-788]],[[213746,957641],[-3387,-733]],[[210359,956908],[688,1521]],[[228608,957739],[1014,-6202]],[[229622,951537],[-940,-1733]],[[228682,949804],[-2484,-698]],[[226198,949106],[-4627,-9]],[[221571,949097],[-1380,2007]],[[220191,951104],[3167,1830]],[[223358,952934],[-8195,-840]],[[215163,952094],[1662,2193]],[[216825,954287],[235,3289]],[[217060,957576],[5106,-2959]],[[222166,954617],[-2419,3175]],[[219747,957792],[6056,1294]],[[225803,959086],[2805,-1347]],[[194437,952245],[2939,947],[-2365,972]],[[195011,954164],[1016,1458]],[[196027,955622],[-1592,2195],[1847,1661]],[[196282,959478],[2420,-133],[-126,-1769]],[[198576,957576],[1834,-2259]],[[200410,955317],[-468,-1498]],[[199942,953819],[3136,-133]],[[203078,953686],[1376,1646]],[[204454,955332],[2543,-1863]],[[206997,953469],[-1060,-3283]],[[205937,950186],[-3585,-1567],[-4661,816]],[[197691,949435],[-3254,-1267]],[[238068,960381],[3611,-1730]],[[241679,958651],[4696,357],[5273,-2912]],[[251648,956096],[-5202,-173]],[[246446,955923],[5762,-2358],[-1225,-1167],[2026,-658]],[[253009,951740],[4609,970],[3302,-684]],[[260920,952026],[5935,1877]],[[266855,953903],[4940,71]],[[271795,953974],[5088,-1196]],[[276883,952778],[1910,-2546]],[[278793,950232],[-2009,-876],[2656,-793]],[[279440,948563],[-2434,-1991]],[[277006,946572],[-4253,-622]],[[272753,945950],[-9035,772]],[[263718,946722],[-2484,-641],[-6854,-27]],[[254380,946054],[231,1722]],[[254611,947776],[-4179,-1400],[-5881,1450]],[[244551,947826],[-1293,3277]],[[243258,951103],[995,1844],[-2842,4126],[-6062,-532]],[[235349,956541],[-4365,2738]],[[230984,959279],[243,1454]],[[231227,960733],[3111,545]],[[234338,961278],[3730,-897]],[[250463,962485],[-3651,710],[-5,1307]],[[246807,964502],[2715,-79]],[[249522,964423],[941,-1938]],[[209605,962901],[-1869,-923],[-2364,3220],[4233,-2297]],[[234765,965592],[5282,-126]],[[240047,965466],[176,-1756],[-6854,57]],[[233369,963767],[1396,1825]],[[232764,969972],[3660,-1103],[-556,-2089],[-5284,-1106]],[[230584,965674],[-3516,3693]],[[227068,969367],[120,2224]],[[227188,971591],[5576,-1619]],[[215066,972034],[2424,1183],[5817,-2939],[-394,-1659]],[[222913,968619],[1625,-2642]],[[224538,965977],[-3944,205]],[[220594,966182],[-1356,1790],[-4603,1051]],[[214635,969023],[-4425,-603],[-1865,1476]],[[208345,969896],[3420,6]],[[211765,969902],[-1076,2786]],[[210689,972688],[-1849,-1082]],[[208840,971606],[-1995,1336],[411,1724]],[[207256,974666],[5449,-48],[2361,-2584]],[[225579,978561],[-137,-1446],[-3477,1076]],[[221965,978191],[1003,1336],[2611,-966]],[[244762,985385],[3451,-3194]],[[248213,982191],[8245,-1313]],[[256458,980878],[-687,-1627]],[[255771,979251],[2624,-1206],[-882,-1859]],[[257513,976186],[4576,185]],[[262089,976371],[995,-2388]],[[263084,973983],[-6467,-3153]],[[256617,970830],[-3122,-546]],[[253495,970284],[-137,-2320]],[[253358,967964],[-3461,2456]],[[249897,970420],[1472,-2391]],[[251369,968029],[-6645,199]],[[244724,968228],[-6288,4486]],[[238436,972714],[7831,2173]],[[246267,974887],[-4106,527]],[[242161,975414],[-6338,-948]],[[235823,974466],[-4639,5012]],[[231184,979478],[5911,-516],[-4785,1448]],[[232310,980410],[2276,435]],[[234586,980845],[-1622,1924],[6125,-783]],[[239089,981986],[-4409,1652]],[[234680,983638],[680,965],[7938,1643]],[[243298,986246],[1464,-861]],[[306975,996546],[8048,-431]],[[315023,996115],[-2237,-1635]],[[312786,994480],[6923,1379],[5054,-1988]],[[324763,993871],[4467,-581]],[[329230,993290],[-1943,-2511]],[[327287,990779],[-6660,-1835],[-5699,-694]],[[314928,988250],[-4700,-2105]],[[317401,987526],[699,-1242]],[[318100,986284],[-8741,-3591]],[[309359,982693],[-7659,-5433],[-5791,-30],[18,-1548]],[[295927,975682],[-4981,-439]],[[290946,975243],[-4554,541]],[[286392,975784],[6574,-2724],[-11249,133]],[[281717,973193],[1942,-786]],[[283659,972407],[4519,382]],[[288178,972789],[5063,-1675]],[[293241,971114],[-1237,-1062]],[[292004,970052],[-4271,-198],[3396,-1088]],[[291129,968766],[-1868,-1884],[-5963,-377]],[[283298,966505],[-177,-2530]],[[283121,963975],[-2203,-1105]],[[280918,962870],[-6244,-373]],[[274674,962497],[4934,-659]],[[279608,961838],[334,-1317]],[[279942,960521],[2589,247]],[[282531,960768],[12,-2409],[-6683,-2338]],[[275860,956021],[-1334,1992]],[[274526,958013],[-3776,1247],[824,-1525]],[[271574,957735],[-4590,-75]],[[266984,957660],[-3488,-880]],[[263496,956780],[-2707,772]],[[260789,957552],[-6334,-177]],[[254455,957375],[-3261,515]],[[251194,957890],[196,1984],[3059,1641]],[[254449,961515],[4294,418],[-3452,3228]],[[255291,965161],[2992,1025]],[[258283,966186],[3971,-2555]],[[262254,963631],[2361,-592]],[[264615,963039],[2664,1016]],[[267279,964055],[-4194,157]],[[263085,964212],[-717,2184]],[[262368,966396],[1453,2279]],[[263821,968675],[-3315,-1370]],[[260506,967305],[827,1550]],[[261333,968855],[-4533,-984],[2067,3540]],[[258867,971411],[5011,818]],[[263878,972229],[4812,-841]],[[268690,971388],[2313,790]],[[271003,972178],[-3159,889],[-4205,3308],[-3697,1381]],[[259942,977756],[316,2809]],[[260258,980565],[7176,-537],[5189,-2999]],[[272623,977029],[2348,-174],[-5419,3465]],[[269552,980320],[8084,1485]],[[277636,981805],[8891,2071]],[[286527,983876],[-4725,256],[735,1458],[-7557,-3037]],[[274980,982553],[-8526,-584],[-9037,672]],[[257417,982641],[-4421,805]],[[252996,983446],[1412,1150]],[[254408,984596],[-4262,1024],[4079,2323]],[[254225,987943],[-8122,80]],[[246103,988023],[2535,1771]],[[248638,989794],[7920,1232]],[[256558,991026],[7206,-606]],[[263764,990420],[-4267,1212],[4678,1552]],[[264175,993184],[15200,-3524]],[[279375,989660],[-8407,3393]],[[270968,993053],[4003,2085],[6282,-591]],[[281253,994547],[-3906,1373]],[[277347,995920],[20398,1007],[9230,-381]],[[269941,777103],[157,-702],[2294,718]],[[272392,777119],[160,-2629]],[[272552,774490],[-3724,2172]],[[268828,776662],[1113,441]],[[279110,806663],[-539,898]],[[278571,807561],[536,2530]],[[279107,810091],[3,-3428]],[[279112,806382],[18,-19952],[412,-3135],[1622,-3722],[3051,-1226],[1248,-1677],[1138,-142],[1139,-2271],[1415,-451],[2888,1315],[1299,-440],[156,-2093]],[[293498,772588],[-1023,-1248]],[[292475,771340],[-1307,-619]],[[291168,770721],[-1718,-2421]],[[289450,768300],[-720,-735]],[[288730,767565],[-2939,-1148],[656,-1362]],[[286447,765055],[-914,-398]],[[285533,764657],[-1043,963]],[[284490,765620],[-4103,-1205]],[[280387,764415],[-1412,-1521],[-570,-1532]],[[278405,761362],[1213,-722]],[[279618,760640],[767,504]],[[280385,761144],[-13,-1048]],[[280372,760096],[25,-1444]],[[280397,758652],[-3232,-520]],[[277165,758132],[-641,-954],[-2648,99],[-1284,-2203],[-2162,-1310],[-1286,200],[88,1212]],[[269232,755176],[1352,232]],[[270584,755408],[-154,1442]],[[270430,756850],[633,2721]],[[271063,759571],[1757,1834],[213,4762]],[[273033,766167],[1195,3039],[-1165,3598],[1113,-13]],[[274176,772791],[103,-1306]],[[274279,771485],[1011,-1465]],[[275290,770020],[1860,-1609]],[[277150,768411],[327,1762]],[[277477,770173],[1085,-270],[-1023,2062]],[[277539,771965],[124,1381]],[[277663,773346],[-857,337]],[[276806,773683],[-1300,3268],[-2164,93]],[[273342,777044],[-52,905]],[[273290,777949],[-3917,366],[-2962,1065]],[[266411,779380],[-204,1195]],[[266207,780575],[-931,-470]],[[265276,780105],[321,2503]],[[265597,782608],[-1074,776]],[[264523,783384],[493,1680],[-1167,1886],[443,1844]],[[264292,788794],[-2563,120],[-908,1422],[-811,3086]],[[260010,793422],[-2298,265]],[[257712,793687],[-2889,1172]],[[254823,794859],[85,-2231]],[[254908,792628],[-749,1398]],[[254159,794026],[-755,-1481]],[[253404,792545],[-1184,-783]],[[252220,791762],[-1047,-2662]],[[251173,789100],[-3508,1179]],[[247665,790279],[-1884,-843]],[[245781,789436],[-4105,3279]],[[241676,792715],[-1975,-511]],[[239701,792204],[-2537,1286]],[[237164,793490],[-650,3328],[-835,387]],[[252921,841529],[2426,-2274]],[[255347,839255],[1427,-2435]],[[256774,836820],[5235,-2697]],[[262009,834123],[1710,-1869]],[[263719,832254],[3196,172]],[[266915,832426],[3703,-983]],[[270618,831443],[994,-1986],[-551,-2711],[768,-3189]],[[271829,823557],[-331,-5075]],[[271498,818482],[1914,-3518]],[[273412,814964],[61,-774]],[[273473,814190],[2477,-2833]],[[275950,811357],[499,-2673],[1041,-144]],[[277490,808540],[1622,-2158]],[[323107,780571],[1533,-828]],[[324640,779743],[2683,385]],[[327323,780128],[388,-389],[-2373,-2489]],[[325338,777250],[-485,1590]],[[324853,778840],[-622,-690]],[[324231,778150],[-1339,1447],[-978,164]],[[321914,779761],[-770,1278]],[[321144,781039],[1096,2492]],[[322240,783531],[-262,-1695]],[[321978,781836],[1129,-1265]],[[295648,774097],[-1094,-164]],[[294554,773933],[1345,1559],[-251,-1395]],[[308184,784912],[-526,997]],[[307658,785909],[-2124,-4467]],[[305534,781442],[-802,-4757],[-1671,-3813]],[[303061,772872],[-518,187]],[[302543,773059],[-675,-23]],[[301868,773036],[-528,-1674]],[[301340,771362],[-5096,-12]],[[296244,771350],[-3769,-10]],[[292475,771340],[3197,2496]],[[295672,773836],[1107,3465]],[[296779,777301],[3496,3684],[1777,737]],[[302052,781722],[2060,1637]],[[304112,783359],[4257,7361]],[[308369,790720],[3962,3441]],[[312331,794161],[3840,2116],[1819,315]],[[317990,796592],[1909,-441]],[[319899,796151],[1596,-1599]],[[321495,794552],[-242,-2954]],[[321253,791598],[-2530,-2381],[-1853,993]],[[316870,790210],[-2160,-986]],[[327168,795484],[-3740,1896]],[[323428,797380],[-886,1532]],[[322542,798912],[-1543,1007],[858,675]],[[321857,800594],[3536,-1400],[2892,-2499]],[[328285,796695],[45,-1124]],[[341388,809491],[-3917,-879]],[[337471,808612],[-1820,-3052]],[[335651,805560],[-2541,-3112]],[[333110,802448],[-3360,-312]],[[328542,801556],[-541,763]],[[328001,802319],[-2211,408]],[[325790,802727],[-5979,-155]],[[319811,802572],[-1113,263]],[[318698,802835],[-3408,-641]],[[315290,802194],[-1238,-1291],[-1197,-3823]],[[312855,797080],[-1901,-543],[-2424,-2535]],[[308530,794002],[-2069,-3731]],[[306461,790271],[-2297,918],[2016,-1517]],[[306180,789672],[-362,-1575]],[[305818,788097],[-2223,-4104],[-1561,-2036]],[[302034,781957],[-1700,-646]],[[300334,781311],[-3059,-2827]],[[297275,778484],[-1377,-2793]],[[295898,775691],[-1559,-1400]],[[294339,774291],[178,-929]],[[294517,773362],[-1019,-774]],[[279112,806382],[-2,281]],[[279107,810091],[1336,-479]],[[280443,809612],[381,-1561]],[[280824,808051],[477,1760],[-684,1399]],[[280617,811210],[1350,3072]],[[281967,814282],[-644,2225]],[[281323,816507],[-1082,6455]],[[280241,822962],[469,729]],[[280710,823691],[-2003,5078],[2101,1090],[2827,2104],[1573,1889]],[[285208,833852],[1874,3270]],[[287082,837122],[363,3553]],[[287445,840675],[-148,2809]],[[287297,843484],[-1622,4963]],[[285675,848447],[-3773,3931]],[[281902,852378],[157,1131]],[[282059,853509],[1939,3002]],[[283998,856511],[96,1753]],[[284094,858264],[1096,715]],[[285190,858979],[55,1456]],[[285245,860435],[-1331,3540]],[[283914,863975],[522,1098]],[[284436,865073],[-1607,-36]],[[282829,865037],[1853,4366]],[[284682,869403],[-1203,1219]],[[283479,870622],[-335,3516]],[[283144,874138],[1932,1287]],[[285076,875425],[4321,-1521]],[[289397,873904],[3253,-620]],[[292650,873284],[2822,1440]],[[295472,874724],[3900,-3689]],[[299372,871035],[2231,-3984],[3177,-536],[510,-1116],[1732,775],[-927,-6316]],[[306095,859858],[628,-1598],[-284,-1976]],[[307222,856261],[-366,-2776]],[[306856,853485],[2936,-271]],[[309792,853214],[669,-2514]],[[310461,850700],[1845,-1100]],[[312306,849600],[2672,1987]],[[314978,851587],[2782,3329]],[[317760,854916],[556,4055]],[[318316,858971],[1319,2706]],[[319635,861677],[880,320]],[[218424,794974],[-7407,0]],[[211017,794974],[-9175,0],[-7396,0]],[[113732,860194],[-65,2027],[-2483,-952]],[[111184,861269],[-2857,694]],[[108327,861963],[0,55397]],[[108327,917360],[5057,-801],[2926,-2154]],[[116310,914405],[4680,-1522]],[[6513,810872],[1645,1335],[-238,-1097],[-1407,-238]],[[9463,811999],[432,-667]],[[9895,811332],[-1456,-891]],[[8439,810441],[1024,1558]],[[13068,812920],[2748,1149]],[[15816,814069],[-1032,-1074],[-1716,-75]],[[33432,820758],[-3061,-3029]],[[30371,817729],[1969,3695]],[[32340,821424],[1032,595]],[[33372,822019],[60,-1261]],[[34659,820350],[-795,281],[1868,1201]],[[35732,821832],[440,2586]],[[36172,824418],[2075,-180],[-1499,-2705],[-2089,-1183]],[[45900,830448],[327,-1453]],[[46227,828995],[-4071,-1875]],[[42156,827120],[-178,1117],[994,1619]],[[42972,829856],[1839,938]],[[44811,830794],[1089,-346]],[[129708,833783],[-959,-1626]],[[128749,832157],[44,1600],[915,26]],[[136169,833460],[-581,-1676]],[[135588,831784],[-721,1199]],[[134867,832983],[-875,-1440],[-231,1485]],[[133761,833028],[614,2461]],[[135363,836221],[806,-2761]],[[128983,838496],[1009,-115]],[[129992,838381],[2860,-4972]],[[132852,833409],[-1162,-96]],[[131690,833313],[1708,-1515],[-438,-2939]],[[132960,828859],[-1800,1990]],[[131160,830849],[-892,1846]],[[130268,832695],[193,1360],[-1795,1158]],[[128666,835213],[317,3283]],[[132963,836150],[-1464,799]],[[131499,836949],[779,2491]],[[132278,839440],[685,-3290]],[[127916,837243],[-665,-301]],[[127251,836942],[-512,4513]],[[126739,841455],[1067,36],[374,-1556],[-264,-2692]],[[129538,842431],[1145,-729]],[[130683,841702],[-719,-2463]],[[129964,839239],[-1084,-3]],[[128880,839236],[-1046,3232]],[[127834,842468],[1704,-37]],[[125084,844493],[969,-3752]],[[126053,840741],[-93,-2907]],[[125960,837834],[-614,2624],[-1265,897],[363,1217]],[[124444,842572],[-838,1283],[-863,-1389]],[[122743,842466],[69,1825]],[[122812,844291],[1226,1279],[1046,-1077]],[[75283,847292],[1304,11]],[[76587,847303],[590,-1474]],[[77177,845829],[-2940,-2078]],[[74237,843751],[-1341,-2179],[-1352,1686]],[[71544,843258],[-47,-1370]],[[71497,841888],[-1237,2510]],[[70260,844398],[475,1327]],[[70735,845725],[1404,422]],[[72139,846147],[349,1121]],[[72488,847268],[1156,-527],[910,1427]],[[74554,848168],[729,-876]],[[123296,848287],[1697,351]],[[124993,848638],[197,-3377]],[[125190,845261],[-1573,1074],[-541,-1436],[-2434,3271],[684,1462],[1644,151],[326,-1496]],[[125887,849293],[1223,-104],[-74,-1538]],[[127036,847651],[948,-3245]],[[127984,844406],[-1851,-1451],[292,2312],[-1240,5017],[702,-991]],[[76620,850469],[853,-1179],[-1866,-861]],[[75607,848429],[-1668,423],[1502,1950]],[[75441,850802],[1179,-333]],[[89622,859078],[1542,3229]],[[91164,862307],[539,-616]],[[91703,861691],[-2081,-2613]],[[38512,862457],[1126,-411],[385,-2376]],[[40023,859670],[-1431,-816],[-2868,1381]],[[35724,860235],[-825,1173]],[[34899,861408],[1666,62]],[[36565,861470],[1947,987]],[[23714,881749],[2868,349]],[[26582,882098],[2784,-2077],[1977,-223]],[[31343,879798],[-377,-826]],[[30966,878972],[-2572,-459]],[[28394,878513],[-2080,1691]],[[26314,880204],[-3512,269]],[[22802,880473],[912,1276]],[[138819,835824],[-33,-3497]],[[138786,832327],[-1496,-3131],[-762,226],[-550,2074]],[[135978,831496],[591,1033]],[[136569,832529],[-848,3942]],[[135721,836471],[-1788,-716]],[[133933,835755],[-554,-2023]],[[133379,833732],[-667,1102],[1054,2601]],[[133766,837435],[-2970,5257]],[[129261,843431],[-245,3098]],[[129016,846529],[-1819,3187],[-1264,899]],[[125933,850615],[-1300,2714],[-645,3415],[-384,-1286],[1258,-5305]],[[124862,850153],[-2289,517],[-759,2339]],[[121814,853009],[-2340,1455]],[[119474,854464],[2577,-3447],[-1447,-1230]],[[120604,849787],[-2671,1992]],[[117933,851779],[-2246,2998]],[[115687,854777],[-4019,2719]],[[111668,857496],[1302,1960],[-524,830],[-1937,-1721]],[[110509,858565],[-1741,131]],[[108768,858696],[-2296,1310]],[[106472,860006],[-3544,753]],[[102928,860759],[-3338,-478],[-2094,1888]],[[97496,862169],[584,1979]],[[98080,864148],[-1548,-1711],[-1808,580]],[[94724,863017],[-2054,2483]],[[92670,865500],[155,1366]],[[92825,866866],[-2245,-1243],[-1707,299],[705,1484]],[[89578,867406],[-2239,-2466],[1649,-1883]],[[88988,863057],[-1297,-2937]],[[87691,860120],[-2679,691]],[[85012,860811],[-563,-1987]],[[84449,858824],[-2732,-1219]],[[81717,857605],[-980,-1869]],[[80737,855736],[-2234,-359],[-309,1290],[1251,652],[-1261,1574]],[[78184,858893],[1116,2491]],[[79300,861384],[265,3084],[2541,1780],[2359,-176]],[[84465,866072],[-980,1780],[-1853,41],[-3116,-2313]],[[78516,865580],[-46,-924],[-2510,-3060]],[[75960,861596],[-292,-1880]],[[75668,859716],[-1255,-464]],[[74413,859252],[-2436,-2840]],[[71977,856412],[-250,-1231]],[[71727,855181],[2343,-1763]],[[74070,853418],[-1904,-2162]],[[72166,851256],[-630,-1976]],[[71536,849280],[-2112,-851],[-2141,-2652]],[[67283,845777],[-1945,-1424]],[[65338,844353],[-420,-1884]],[[64918,842469],[-3444,-2160],[-1857,-1836]],[[59617,838473],[-700,-2064]],[[58917,836409],[-2649,-848]],[[56268,835561],[-2100,-1816],[-2726,-826]],[[51442,832919],[60,1370]],[[51502,834289],[-1708,-2902]],[[49794,831387],[-1300,613],[-368,-1458],[-1835,-933]],[[46291,829609],[156,1675]],[[46447,831284],[1035,437]],[[47482,831721],[2081,3103]],[[49563,834824],[2616,1788]],[[52179,836612],[2565,-1280]],[[54744,835332],[-655,948],[627,2067]],[[54716,838347],[3644,3235]],[[58360,841582],[3480,4076]],[[61840,845658],[594,5174]],[[62434,850832],[1060,2703]],[[63494,853535],[-2444,-1407]],[[61050,852128],[-2094,1554]],[[58956,853682],[-36,-2735]],[[58920,850947],[-817,171],[-1633,2615]],[[56470,853733],[-694,-540]],[[55776,853193],[-1229,1370]],[[54547,854563],[-2774,-2261]],[[51773,852302],[-2177,-150]],[[49596,852152],[1169,889],[-831,2901]],[[49934,855942],[541,1805],[-1646,4120]],[[48829,861867],[786,2379],[-1519,-2469],[317,-1654],[-3710,-1084],[-2099,2945],[-1921,1407],[1525,2078]],[[42208,865469],[2985,-1789]],[[45193,863680],[860,991]],[[46053,864671],[-4875,1234]],[[41178,865905],[833,718]],[[42011,866623],[-2265,1263],[-1324,2078]],[[38422,869964],[1541,1295]],[[39963,871259],[-262,1369]],[[39701,872628],[1425,2210],[1153,46]],[[42279,874884],[-183,1894]],[[42096,876778],[2050,2730],[2079,-1279]],[[46225,878229],[2048,1303],[940,1561]],[[49213,881093],[3286,170],[893,1546]],[[53392,882809],[-581,2562]],[[52811,885371],[-1186,1629]],[[51625,887000],[1341,313]],[[52966,887313],[-708,2043],[-1590,-638]],[[50668,888718],[-2910,-2619]],[[47758,886099],[-2518,1268]],[[45240,887367],[-3294,-756]],[[41946,886611],[-3455,724]],[[38491,887335],[-757,2036]],[[37734,889371],[-1426,1365],[2032,880]],[[38340,891616],[-3353,690],[-1899,1397]],[[33088,893703],[2816,1300]],[[35904,895003],[4514,3156]],[[40418,898159],[4783,1224]],[[45201,899383],[-850,-2375]],[[44351,897008],[938,-782],[5221,-177]],[[50510,896049],[1001,1348],[-1034,531]],[[50477,897928],[-2165,3101]],[[48312,901029],[1639,-653]],[[49951,900376],[300,-1330]],[[50251,899046],[3496,-1106]],[[53747,897940],[1079,1182]],[[54826,899122],[-1671,583],[-1484,-705]],[[51671,899000],[-1273,880]],[[50398,899880],[651,1653]],[[51049,901533],[-3832,284]],[[47217,901817],[-1997,997]],[[45220,902814],[-1125,2436],[-3501,2600]],[[40594,907850],[-3890,1860],[1128,388],[476,2726]],[[38308,912824],[5295,304],[3169,2674],[582,2193],[2440,2392],[2994,846]],[[52788,921233],[4671,3400],[3655,-197],[4246,3333],[6320,-3594]],[[71680,924175],[2673,778],[2778,-723]],[[77131,924230],[-661,-929]],[[76470,923301],[3461,-1391]],[[79931,921910],[5430,486],[4345,-1681],[5228,-339],[1740,-896]],[[96674,919480],[5497,637],[5029,-2743]],[[107200,917374],[1127,-14]],[[256972,684688],[-834,-509]],[[256138,684179],[-614,2384]],[[255524,686563],[-345,-1940]],[[255179,684623],[-734,25]],[[254445,684648],[-241,9003],[1116,18025],[-246,391]],[[255074,712067],[-48,154],[7130,-143]],[[262156,712078],[1175,-12113],[753,-4205],[-540,-2169],[324,-5206]],[[263868,688385],[-7184,-6],[478,-1950],[-190,-1741]],[[250820,718007],[-663,-1911],[-553,-3346],[-420,-671]],[[249184,712079],[-949,-3479],[-1208,-2604],[-383,-1769],[162,-3909]],[[246806,700318],[-8032,-23]],[[238774,700295],[-17,3214],[-1213,556]],[[237544,704065],[125,10302],[-498,6598]],[[237171,720965],[6963,0],[5415,0],[240,-1210],[-848,-1801],[1879,53]],[[197092,723926],[-3,-33609]],[[197089,690317],[-5539,-21]],[[191550,690296],[-3952,2631]],[[187598,692927],[-6587,4384]],[[181011,697311],[308,1227]],[[181319,698538],[688,750],[-632,1942],[544,4349],[1065,2267],[-681,1197],[-666,2977]],[[181637,712020],[55,2143],[-347,4338],[1868,573],[8,4872]],[[183221,723946],[5202,-7],[8669,-13]],[[154920,753549],[5885,-2],[5859,0]],[[166664,753547],[2,-17775],[5205,-7749],[5485,-8879],[4281,-7124]],[[181319,698538],[-6676,-1079],[-942,4515],[-1702,2528],[-917,129]],[[171082,704631],[-267,1621],[-1770,560]],[[169045,706812],[-1284,1813]],[[167761,708625],[-2431,318]],[[165330,708943],[-454,642]],[[164876,709585],[31,2941]],[[164907,712526],[-630,1712]],[[164277,714238],[-2826,5721],[-9,3601]],[[161442,723560],[-1429,1591]],[[160013,725151],[-331,3345],[1233,-1741]],[[160915,726755],[-875,2857],[1857,436]],[[161897,730048],[-2131,443],[-103,-1673]],[[159663,728818],[-1141,1356],[-525,2334]],[[157997,732508],[-1611,2714],[-511,5649],[-1220,2317]],[[154655,743188],[-132,1417]],[[154523,744605],[663,2836]],[[155186,747441],[179,2455]],[[155365,749896],[-445,3653]],[[216598,741701],[33,-17775]],[[216631,723926],[-2745,0]],[[213886,723926],[-5248,0],[-7347,0],[-4199,0]],[[197092,723926],[1,23698]],[[197093,747624],[8724,0],[5234,0]],[[211051,747624],[5546,-1],[1,-5922]],[[300553,753615],[-115,-4008]],[[300438,749607],[-3007,-298]],[[297431,749309],[-1960,-1738]],[[295471,747571],[416,6302]],[[295887,753873],[4666,-258]],[[290497,740602],[-463,-1035]],[[290034,739567],[543,-3247]],[[290577,736320],[985,-3774]],[[291562,732546],[-1860,-4],[-215,7508]],[[289487,740050],[1010,552]],[[273600,686784],[707,-5555]],[[274307,681229],[971,-4407]],[[275278,676822],[1044,-3340]],[[276322,673482],[-873,1608],[249,-2231]],[[275698,672859],[1451,-6955]],[[277149,665904],[514,-3783],[-327,-4089]],[[277336,658032],[-578,-3241]],[[276758,654791],[-1026,-1036]],[[275732,653755],[-1039,-109]],[[274693,653646],[-8,1358]],[[274685,655004],[-699,2747],[-974,902]],[[273012,658653],[-419,2677]],[[272593,661330],[-481,693]],[[272112,662023],[-76,2012]],[[272036,664035],[-620,-123]],[[271416,663912],[-960,3873]],[[270456,667785],[653,1841],[-497,729],[-226,-1422]],[[270386,668933],[-507,756]],[[269879,669689],[508,3791]],[[270387,673480],[25,2380]],[[270412,675860],[-1775,3344],[-1122,2808]],[[266544,683066],[-941,-1164]],[[265603,681902],[-2600,-1346]],[[263003,680556],[-97,1158]],[[262906,681714],[-1117,1726]],[[261789,683440],[-1941,1375]],[[259848,684815],[-2876,-127]],[[263868,688385],[343,-1663],[7345,-921],[494,-954],[351,2459],[1199,-522]],[[275354,694475],[-669,-895]],[[274685,693580],[-486,-3642],[-426,-380]],[[273773,689558],[-173,-2774]],[[262156,712078],[3609,-75]],[[265765,712003],[3360,78]],[[269125,712081],[-669,-1736],[1411,-1608],[718,-2485],[1621,-2930],[1433,-3479],[1153,-4894],[562,-474]],[[67829,617353],[-833,347],[-465,4024],[635,1566]],[[67166,623290],[-33,1550]],[[67133,624840],[1759,-1668],[1095,-2783]],[[69987,620389],[-1404,-1566]],[[68583,618823],[-754,-1470]],[[65314,628731],[1381,-1039]],[[66695,627692],[-1247,-826],[-134,1865]],[[63295,630407],[1393,-358]],[[64688,630049],[-410,-585]],[[64278,629464],[-983,943]],[[61668,631836],[456,-883]],[[62124,630953],[-1320,65]],[[60804,631018],[-453,1580]],[[60351,632598],[863,688]],[[61214,633286],[454,-1450]],[[57298,634654],[-1158,648],[1215,1054]],[[57355,636356],[-57,-1702]],[[248192,756583],[1342,-2445],[-500,-2774],[-1946,-1558],[262,-2094],[-1356,-3769]],[[245994,743943],[-804,1453],[-5606,-69],[-5598,-66]],[[233986,745261],[-894,7225],[-1095,4087]],[[231997,756573],[-391,1293],[467,4571]],[[232073,762437],[4516,2],[9952,6]],[[246541,762445],[488,-4229],[1163,-1633]],[[191524,768349],[3,-14800]],[[191527,753549],[-8312,2]],[[183215,753551],[-8261,-4]],[[174954,753547],[-10,10711],[301,2067],[-806,1652],[1813,6046],[79,1571],[-1045,1660]],[[175286,777254],[-356,2530],[-40,15190]],[[177643,794974],[-1,-5855],[875,-1768],[87,-1592],[981,-1046],[2077,-3560],[696,44],[-495,-6470],[643,-521],[986,1149],[1678,-5175],[942,-1941],[2134,476],[2032,-94],[450,1184],[796,-1456]],[[256077,756489],[41,-1692]],[[256118,754797],[767,-2866]],[[256885,751931],[-25,-16700],[-293,-2170],[-659,-1467],[-469,-2998]],[[255439,728596],[-182,-1509],[-1076,-1007],[64,-1635],[-1596,560],[-300,-1130]],[[252349,723875],[-924,1307],[-101,2558],[-1806,2515],[-558,1426],[687,3098],[-1491,918],[-710,2366],[-1407,2606],[-45,3274]],[[248192,756583],[7885,-94]],[[264455,751775],[-71,-15460]],[[264384,736315],[58,-1614],[-1789,-808],[-38,-905],[-1792,-3190],[-586,1018],[-650,-1461],[-890,350],[-756,-1013],[-642,701],[-1860,-797]],[[256885,751931],[1830,194]],[[258715,752125],[5739,-4],[1,-346]],[[237172,723926],[-5135,0],[-6419,0],[-8987,0]],[[216598,741701],[7569,0],[6405,0],[4575,3]],[[235147,741704],[1158,-829],[-355,-2089],[1210,-2279],[12,-12581]],[[270520,732502],[66,-2065],[858,-2444],[875,-874]],[[272319,727119],[-1990,-2379],[-1302,-2226],[-1437,-932]],[[267590,721582],[-175,-108],[-7714,462],[-3699,-124],[-611,-854],[-3865,1]],[[251526,720959],[817,1278],[6,1638]],[[264384,736315],[1288,-249],[363,-1094],[1493,-1278],[2248,360],[744,-1552]],[[251331,683591],[-1957,1106],[-522,-1415]],[[248852,683282],[661,-659]],[[249513,682623],[1216,846]],[[250729,683469],[1064,-2084],[-1018,-1190]],[[250775,680195],[576,-1180]],[[251351,679015],[1383,-1287]],[[252734,677728],[-939,-786],[-1454,2297],[-487,-157]],[[249854,679082],[-446,-1934]],[[249408,677148],[-463,1127]],[[248945,678275],[-1032,-973],[-1497,937],[127,996],[-1802,2244]],[[244741,681479],[-1021,-1654]],[[243720,679825],[-1141,239],[-1400,1077],[-1968,184]],[[239211,681325],[250,991]],[[239461,682316],[205,3442],[527,2891],[-1425,5646],[6,6000]],[[246806,700318],[0,-2316],[561,-2280],[-941,-2110],[-921,-3596],[-107,-1630],[5336,-4],[-277,-1602],[874,-3189]],[[302128,751805],[-426,1839],[-1149,-29]],[[295887,753873],[631,4128]],[[296518,758001],[2185,-130]],[[298703,757871],[3159,-165],[1454,1032]],[[303316,758738],[196,-1229]],[[303512,757509],[-863,-2003]],[[302649,755506],[856,-606],[621,-2521]],[[304126,752379],[1425,135]],[[305551,752514],[147,-883]],[[305698,751631],[-1968,-847],[-122,1071]],[[303608,751855],[-1299,-1336]],[[302309,750519],[-181,1286]],[[291562,732546],[-940,-2552]],[[290622,729994],[-787,-421]],[[289835,729573],[-532,105]],[[289303,729678],[-105,2275],[-897,34]],[[288301,731987],[-146,1414]],[[288155,733401],[731,10]],[[288886,733411],[-652,3495]],[[288234,736906],[1007,1892],[-1521,-1694],[-224,-4105]],[[287496,732999],[130,-2125]],[[287626,730874],[-2186,1904]],[[285440,732778],[587,2337]],[[286027,735115],[-1935,2708]],[[284092,737823],[-357,1501],[-905,509],[-873,-902],[-784,552],[-1973,-2463],[29,3033]],[[279229,740053],[10258,-3]],[[313542,772321],[-185,-2926]],[[313357,769395],[-1799,-588]],[[311558,768807],[-605,-1137]],[[310953,767670],[-1094,730]],[[309859,768400],[-228,-1475],[-1191,1038]],[[308440,767963],[-297,-1992]],[[308143,765971],[-2055,-1928]],[[306088,764043],[-436,492],[-2133,-4652]],[[303519,759883],[-618,1892],[-358,11284]],[[270430,756850],[-395,631]],[[270035,757481],[-1853,-5444]],[[268182,752037],[-3727,-262]],[[258715,752125],[639,894]],[[259354,753019],[925,2999]],[[260279,756018],[215,2861]],[[260494,758879],[-889,4365]],[[259605,763244],[64,2586]],[[259669,765830],[620,1522]],[[260289,767352],[164,2308],[1723,2801],[186,-2496],[483,2993]],[[262845,772958],[1135,685]],[[263980,773643],[-40,2219]],[[263940,775862],[582,164]],[[264522,776026],[3593,-2701]],[[268115,773325],[512,-3884]],[[268627,769441],[-101,-1826]],[[268526,767615],[-1523,-2444]],[[267003,765171],[399,-1990]],[[267402,763181],[979,1761]],[[268381,764942],[1210,848]],[[269591,765790],[788,-1261]],[[270379,764529],[684,-4958]],[[256650,771959],[-721,1537],[157,1886],[-952,1536],[-5464,2348],[-802,1488]],[[248868,780754],[2923,1713],[1957,2074]],[[253748,784541],[560,-2551]],[[254308,781990],[647,889]],[[254955,782879],[1606,-741],[777,-1790],[2002,-424],[1404,1387]],[[260744,781311],[3234,497]],[[263978,781808],[-185,-1570]],[[263793,780238],[1964,-86]],[[265757,780152],[279,-1823]],[[266036,778329],[906,-1200],[-1723,81],[-780,-712]],[[264439,776498],[-1881,1381],[-3012,-1333]],[[259546,776546],[-1141,-944]],[[258405,775602],[-23,1149]],[[258382,776751],[-1732,-4792]],[[254939,784414],[-604,-1184]],[[254335,783230],[-490,967]],[[253845,784197],[615,1271]],[[254460,785468],[1815,267]],[[256275,785735],[-1336,-1321]],[[251173,789100],[-3832,-2831]],[[247341,786269],[-3194,-4511]],[[244147,781758],[-513,-602],[-3,-3414],[-1114,-1040],[-553,-1861],[516,-598],[-256,-4170],[3935,-4735],[382,-2893]],[[232073,762437],[0,10641],[-1088,1770],[803,2055]],[[231788,776903],[-640,4178],[-200,5571],[-742,3466],[-278,4857]],[[251526,720959],[-706,-2952]],[[237171,720965],[1,2961]],[[235147,741704],[-1161,3557]],[[254445,684648],[-1404,262]],[[253041,684910],[-1710,-1319]],[[249184,712079],[5890,-12]],[[211080,776905],[-72,-5581]],[[211008,771324],[-4871,0],[-8525,0],[-6087,0],[-1,-2975]],[[211017,794974],[63,-18069]],[[281766,705417],[-3026,5452],[-3141,189],[-842,1937],[-3524,272],[-2108,-1186]],[[265765,712003],[104,1260],[1155,1860],[2447,1692],[285,896],[2323,1014],[882,1378],[208,1511]],[[273169,721614],[3063,-141],[6030,-100],[6720,-109]],[[288982,721264],[232,-2226]],[[289214,719038],[-2343,-1292]],[[286871,717746],[2650,-342]],[[289521,717404],[-4,-1498]],[[289517,715906],[-1111,-1735]],[[288406,714171],[-996,914]],[[287410,715085],[-1228,-297],[1282,-1113]],[[287464,713675],[-11,-2922]],[[287453,710753],[-1714,-411]],[[285739,710342],[-1714,-2505]],[[284025,707837],[-491,-2046]],[[283534,705791],[-1768,-374]],[[231788,776903],[-9060,0],[-7118,0],[-4530,2]],[[211051,747624],[-20,11852]],[[211031,759476],[9614,0],[5906,-2],[827,-881],[2514,49],[2105,-2069]],[[303519,759883],[-203,-1145]],[[298703,757871],[-230,926],[457,3807],[983,4571],[967,885],[460,3302]],[[291460,741597],[943,1013],[-1262,2615],[184,2389],[1177,2122]],[[292502,749736],[2192,-2162]],[[294694,747574],[-921,-3176]],[[293773,744398],[789,-758],[-607,-3565],[-1718,-4293]],[[292237,735782],[-2027,2893]],[[290210,738675],[284,1774],[966,1148]],[[213888,720966],[-116,-3],[-66,-26647],[-10007,-11],[619,-1378]],[[204318,692927],[-4907,65]],[[199411,692992],[-7,-2665]],[[199404,690327],[-2315,-10]],[[213886,723926],[2,-2960]],[[166664,753547],[8290,0]],[[183215,753551],[6,-29605]],[[300269,747979],[-3587,-2408]],[[296682,745571],[-2327,-92]],[[294355,745479],[1273,1665],[4641,835]],[[296244,771350],[86,-4365],[-195,-4089],[400,-151],[-17,-4744]],[[295471,747571],[-991,-1425],[214,1428]],[[292502,749736],[-899,1159],[-913,2639],[-4595,6],[-7659,10],[0,1627]],[[278436,755177],[2459,3101]],[[280895,758278],[-523,1818]],[[280385,761144],[2462,663]],[[282847,761807],[1794,-755],[1535,60]],[[286176,761112],[2069,1617],[-157,1768]],[[288088,764497],[604,887]],[[288692,765384],[-782,637]],[[287910,766021],[1540,2279]],[[276336,745528],[-1234,-6373],[-1928,-1773],[-626,-2238],[-329,693],[-881,-3149],[-818,-186]],[[268182,752037],[2706,-2059]],[[270888,749978],[2103,669]],[[272991,750647],[1374,1537]],[[274365,752184],[1967,1298]],[[276332,753482],[4,-7954]],[[237544,704065],[-2094,1826],[-1955,-437],[-1037,-1017],[-1841,1344],[-393,-1204],[-1503,1512],[-506,-676],[-711,1594],[-1098,-333],[-1924,869],[-483,1308],[-756,-401],[-1019,1174],[-9,11341],[-8327,1]],[[157719,778152],[775,-229],[601,-2614],[1453,-490],[1500,753],[1715,-468],[1927,510],[3699,1630],[5897,10]],[[154920,753549],[-351,723]],[[154569,754272],[-513,4087],[1086,5208]],[[155142,763567],[249,6434]],[[155391,770001],[361,4735]],[[155752,774736],[49,3585]],[[155801,778321],[1918,-169]],[[291460,741597],[-963,-995]],[[279229,740053],[-2895,-2],[2,5477]],[[276332,753482],[2104,1695]],[[302128,751805],[-583,-1497]],[[301545,750308],[-1107,-701]],[[281766,705417],[-770,-903]],[[280996,704514],[-1078,-3193]],[[279918,701321],[-2201,-3349]],[[277717,697972],[-947,-706]],[[276770,697266],[-1416,-2791]],[[211031,759476],[-23,11848]],[[267590,721582],[5579,32]],[[239461,682316],[-849,-1818]],[[238612,680498],[-1885,-726]],[[236727,679772],[101,1197],[-781,-282]],[[236047,680687],[374,-1965]],[[236421,678722],[-1070,-2410]],[[235351,676312],[-1612,-1917]],[[233739,674395],[-2185,405],[609,-1489]],[[232163,673311],[-1703,-2153]],[[230460,671158],[-707,-2508]],[[229753,668650],[-739,-4166],[424,-3382]],[[229438,661102],[711,-2578],[-590,-538]],[[229559,657986],[-2011,1148],[-2847,2266],[-933,3494],[-172,3032]],[[223596,667926],[-2196,4617]],[[221400,672543],[-1008,4389]],[[220392,676932],[-2171,4196],[-2508,523],[-1087,-1310]],[[214626,680341],[-364,-2286]],[[214262,678055],[-1089,-1522]],[[213173,676533],[-2369,2281]],[[210804,678814],[-1093,1727]],[[209711,680541],[-1319,5736],[-825,956]],[[207567,687233],[-2722,4369]],[[204845,691602],[-527,1325]],[[191527,753549],[0,-5925],[5566,0]],[[290622,729994],[-612,-2333]],[[290010,727661],[-1114,-2176]],[[288896,725485],[939,4088]],[[286027,735115],[-787,-2919]],[[285240,732196],[2122,-1790]],[[287362,730406],[793,-1190]],[[288155,729216],[2,-3179]],[[288157,726037],[-965,-204]],[[287192,725833],[910,-1599]],[[288102,724234],[-323,-965]],[[287779,723269],[1111,136],[92,-2141]],[[272319,727119],[1112,-1954],[1725,545],[1743,1231],[86,992],[1910,5332],[993,-596],[553,1856],[1683,2198],[314,1724],[1351,-1816],[303,1192]],[[157719,778152],[-677,696]],[[157042,778848],[-1689,49]],[[155353,778897],[-108,4478]],[[155245,783375],[-734,3693]],[[154511,787068],[-681,1455]],[[153830,788523],[-247,2821]],[[153583,791344],[2040,-1255]],[[155623,790089],[2642,-516]],[[158265,789573],[683,333]],[[158948,789906],[557,-5003]],[[159505,784903],[623,464],[-109,2660],[419,1128],[-1185,2692]],[[159253,791847],[344,1760]],[[159597,793607],[-677,1367]],[[258356,772744],[-801,-2405]],[[257555,770339],[-323,707]],[[257232,771046],[1124,1698]],[[256650,771959],[-1009,-3296]],[[255641,768663],[1013,1747]],[[256654,770410],[742,-421]],[[257396,769989],[-1568,-9070],[249,-4430]],[[244147,781758],[1689,23]],[[245836,781781],[1536,1111]],[[247372,782892],[581,-1569]],[[247953,781323],[915,-569]]],"transform":{"scale":[0.00036000036000036,0.00016879196566696583],"translate":[-180,-85.19218750000006]}} diff --git a/desktop/angular/.gitignore b/desktop/angular/.gitignore index 28f76669..d86cd691 100644 --- a/desktop/angular/.gitignore +++ b/desktop/angular/.gitignore @@ -1,4 +1,5 @@ node_modules dist dist-extension -dist-lib \ No newline at end of file +dist-lib +.angular \ No newline at end of file diff --git a/desktop/angular/README.md b/desktop/angular/README.md new file mode 100644 index 00000000..657f3808 --- /dev/null +++ b/desktop/angular/README.md @@ -0,0 +1,104 @@ +# Portmaster + +Welcome to the new Portmaster User-Interface. It's based on Angular and is built, unit and e2e tested using `@angular/cli`. + +## Running locally + +This section explains how to prepare your Ubuntu machine to build and test the new Portmaster User-Interface. It's recommended to use +a virtual machine but running it on bare metal will work as well. You can use the new Portmaster UI as well as the old one in parallel so +you can simply switch back when something is still missing or buggy. + +1. **Prepare your tooling** + +There's a simple dockerized way to build and test the new UI. Just make sure to have docker installed: + +```bash +sudo apt update +sudo apt install -y docker.io git +sudo systemctl enable --now docker +sudo gpasswd -a $USER docker +``` + +2. **Portmaster installation** + +Next, make sure to install the Portmaster using the official .deb installer from [here](https://updates.safing.io/latest/linux_amd64/packages/portmaster-installer.deb). See the [Wiki](https://github.com/safing/portmaster/wiki/Linux) for more information. + +Once the Portmaster is installed we need to add two new configuration flags. Execute the following: + +```bash +echo 'PORTMASTER_ARGS="--experimental-nfqueue --devmode"' | sudo tee /etc/default/portmaster +sudo systemctl daemon-reload +sudo systemctl restart portmaster +``` + +3. **Build and run the new UI** + +Now, clone this repository and execute the `docker.sh` script: + +```bash +# Clone the repository +git clone https://github.com/safing/portmaster-ui + +# Enter the repo and checkout the correct branch +cd portmaster-ui +git checkout feature/new-ui + +# Enter the directory and run docker.sh +cd modules/portmaster +sudo bash ./docker.sh +``` + +Finally open your browser and point it to http://localhost:8080. + +## Hacking Quick Start + +Although everything should work in the docker container as well, for the best development experience it's recommended to install `@angular/cli` locally. + +It's highly recommended to: +- Use [VSCode](https://code.visualstudio.com/) (or it's oss or server-side variant) with + - the official [Angular Language Service](https://marketplace.visualstudio.com/items?itemName=Angular.ng-template) extension + - the [Tailwind CSS Extension Pack](https://marketplace.visualstudio.com/items?itemName=andrewmcodes.tailwindcss-extension-pack) extension + - the [formate: CSS/LESS/SCSS formatter](https://github.com/mblander/formate) extension + +### Folder Structure + +From the project root (the folder containing this [README.md](./)) there are only two folders with the following content and structure: + +- **`src/`** contains the actual application sources: + - **`app/`** contains the actual application sources (components, services, uni tests ...) + - **`layout/`** contains components that form the overall application layout. For example the navigation bar and the side dash are located there. + - **`pages/`** contains the different pages of the application. A page is something that is associated with a dedicated application route and is rendered at the applications main content. + - **`services/`** contains shared services (like PortAPI and friends) + - **`shared/`** contains shared components that are likely used accross other components or pages. + - **`widgets/`** contains widgets and their settings components for the application side dash. + - **`debug/`** contains a debug sidebar component + - **`assets/`** contains static assets that must be shipped seperately. + - **`environments/`** contains build and production related environment settings (those are handled by `@angular/cli` automatically, see [angular.json](angular.json)) +- **`e2e/`** contains end-to-end testing sources. + + +### Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +In development mode (that is, you don't pass `--prod`) the UI expects portmaster running at `ws://127.0.0.1:817/api/database/v1`. See [environment](./src/app/environments/environment.ts). + +### Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +### Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. + +### Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +### Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). + +### Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/desktop/angular/angular.json b/desktop/angular/angular.json new file mode 100644 index 00000000..d99f44d0 --- /dev/null +++ b/desktop/angular/angular.json @@ -0,0 +1,457 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "portmaster": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + }, + "@schematics/angular:application": { + "strict": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "aot": true, + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/theme.less", + "src/styles.scss", + "node_modules/prismjs/themes/prism-okaidia.css", + "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "dist-lib/" + ] + }, + "scripts": [ + "node_modules/marked/marked.min.js", + "node_modules/emoji-toolkit/lib/js/joypixels.min.js", + "node_modules/prismjs/prism.js", + "node_modules/prismjs/components/prism-yaml.min.js", + "node_modules/prismjs/components/prism-json.min.js", + "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js" + ], + "vendorChunk": true, + "extractLicenses": false, + "buildOptimizer": false, + "sourceMap": true, + "optimization": false, + "namedChunks": true + }, + "configurations": { + "development": {}, + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": { + "scripts": true, + "styles": { + "minify": true, + "inlineCritical": false + } + }, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": true, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "4mb", + "maximumError": "16mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4mb", + "maximumError": "16mb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "portmaster:build" + }, + "configurations": { + "production": { + "browserTarget": "portmaster:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "portmaster:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [] + } + }, + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "portmaster:serve" + }, + "configurations": { + "production": { + "devServerTarget": "portmaster:serve:production" + } + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] + } + } + } + }, + "@safing/ui": { + "projectType": "library", + "root": "projects/safing/ui", + "sourceRoot": "projects/safing/ui/src", + "prefix": "lib", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:ng-packagr", + "options": { + "project": "projects/safing/ui/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/safing/ui/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "projects/safing/ui/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/safing/ui/src/test.ts", + "tsConfig": "projects/safing/ui/tsconfig.spec.json", + "karmaConfig": "projects/safing/ui/karma.conf.js" + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "projects/safing/ui/**/*.ts", + "projects/safing/ui/**/*.html" + ] + } + } + } + }, + "portmaster-chrome-extension": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "projects/portmaster-chrome-extension", + "sourceRoot": "projects/portmaster-chrome-extension/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-builders/custom-webpack:browser", + "options": { + "customWebpackConfig": { + "path": "./browser-extension.config.ts" + }, + "outputPath": "dist-extension", + "index": "projects/portmaster-chrome-extension/src/index.html", + "main": "projects/portmaster-chrome-extension/src/main.ts", + "polyfills": "projects/portmaster-chrome-extension/src/polyfills.ts", + "tsConfig": "projects/portmaster-chrome-extension/tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "projects/portmaster-chrome-extension/src/favicon.ico", + "projects/portmaster-chrome-extension/src/assets", + "projects/portmaster-chrome-extension/src/manifest.json" + ], + "styles": [ + "projects/portmaster-chrome-extension/src/styles.scss" + ], + "scripts": [], + "optimization": { + "styles": { + "inlineCritical": false + } + }, + "outputHashing": "none" + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [ + { + "replace": "projects/portmaster-chrome-extension/src/environments/environment.ts", + "with": "projects/portmaster-chrome-extension/src/environments/environment.prod.ts" + } + ], + "outputHashing": "none" + }, + "development": { + "customWebpackConfig": { + "path": "./browser-extension-dev.config.ts" + }, + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "portmaster-chrome-extension:build:production" + }, + "development": { + "browserTarget": "portmaster-chrome-extension:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "portmaster-chrome-extension:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/portmaster-chrome-extension/src/test.ts", + "polyfills": "projects/portmaster-chrome-extension/src/polyfills.ts", + "tsConfig": "projects/portmaster-chrome-extension/tsconfig.spec.json", + "karmaConfig": "projects/portmaster-chrome-extension/karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "projects/portmaster-chrome-extension/src/favicon.ico", + "projects/portmaster-chrome-extension/src/assets" + ], + "styles": [ + "projects/portmaster-chrome-extension/src/styles.scss" + ], + "scripts": [] + } + } + } + }, + "@safing/portmaster-api": { + "projectType": "library", + "root": "projects/safing/portmaster-api", + "sourceRoot": "projects/safing/portmaster-api/src", + "prefix": "lib", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:ng-packagr", + "options": { + "project": "projects/safing/portmaster-api/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "projects/safing/portmaster-api/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "projects/safing/portmaster-api/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "projects/safing/portmaster-api/src/test.ts", + "tsConfig": "projects/safing/portmaster-api/tsconfig.spec.json", + "karmaConfig": "projects/safing/portmaster-api/karma.conf.js" + } + } + } + }, + "tauri-builtin": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "skipTests": true, + "style": "scss", + "standalone": true + }, + "@schematics/angular:class": { + "skipTests": true + }, + "@schematics/angular:directive": { + "skipTests": true, + "standalone": true + }, + "@schematics/angular:guard": { + "skipTests": true + }, + "@schematics/angular:interceptor": { + "skipTests": true + }, + "@schematics/angular:pipe": { + "skipTests": true, + "standalone": true + }, + "@schematics/angular:resolver": { + "skipTests": true + }, + "@schematics/angular:service": { + "skipTests": true + } + }, + "root": "projects/tauri-builtin", + "sourceRoot": "projects/tauri-builtin/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/tauri-builtin", + "index": "projects/tauri-builtin/src/index.html", + "main": "projects/tauri-builtin/src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "projects/tauri-builtin/tsconfig.app.json", + "assets": [ + "projects/tauri-builtin/src/favicon.ico", + "projects/tauri-builtin/src/assets" + ], + "styles": [ + "projects/tauri-builtin/src/styles.scss" + ], + "inlineStyleLanguage": "scss", + "stylePreprocessorOptions": { + "includePaths": [ + "dist-lib/" + ] + }, + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "tauri-builtin:build:production" + }, + "development": { + "browserTarget": "tauri-builtin:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "tauri-builtin:build" + } + } + } + } + }, + "cli": { + "analytics": false + }, + "schematics": { + "@angular-eslint/schematics:application": { + "setParserOptionsProject": true + }, + "@angular-eslint/schematics:library": { + "setParserOptionsProject": true + } + } +} \ No newline at end of file diff --git a/desktop/angular/assets b/desktop/angular/assets new file mode 120000 index 00000000..41aef43f --- /dev/null +++ b/desktop/angular/assets @@ -0,0 +1 @@ +../../assets \ No newline at end of file diff --git a/desktop/angular/browser-extension-dev.config.ts b/desktop/angular/browser-extension-dev.config.ts new file mode 100644 index 00000000..a05dbcb1 --- /dev/null +++ b/desktop/angular/browser-extension-dev.config.ts @@ -0,0 +1,16 @@ +import type { Configuration } from 'webpack'; +const ExtensionReloader = require('webpack-ext-reloader'); +const config = require('./browser-extension.config'); + +module.exports = { + ...config, + mode: 'development', + plugins: [ + new ExtensionReloader({ + reloadPage: true, // Force the reload of the page also + entries: { // The entries used for the content/background scripts or extension pages + background: 'background', + } + }) + ] +} as Configuration; diff --git a/desktop/angular/browser-extension.config.ts b/desktop/angular/browser-extension.config.ts new file mode 100644 index 00000000..df5de5d3 --- /dev/null +++ b/desktop/angular/browser-extension.config.ts @@ -0,0 +1,5 @@ +import type { Configuration } from 'webpack'; + +module.exports = { + entry: { background: { import: 'projects/portmaster-chrome-extension/src/background.ts', runtime: false } }, +} as Configuration; diff --git a/desktop/angular/docker.sh b/desktop/angular/docker.sh new file mode 100755 index 00000000..bbd896e7 --- /dev/null +++ b/desktop/angular/docker.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# cd to script dir +baseDir="$( cd "$(dirname "$0")" && pwd )" +cd "$baseDir" + +# get base dir for mounting +mnt="$( cd ../.. && pwd )" + +# run container and start dev server +docker run \ + -ti \ + --rm \ + -v $mnt:/portmaster-ui \ + -w /portmaster-ui/modules/portmaster \ + -p 8081:8080 \ + node:latest \ + npm start -- --host 0.0.0.0 --port 8080 diff --git a/desktop/angular/e2e/protractor.conf.js b/desktop/angular/e2e/protractor.conf.js new file mode 100644 index 00000000..f238c0bb --- /dev/null +++ b/desktop/angular/e2e/protractor.conf.js @@ -0,0 +1,36 @@ +// @ts-check +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); + +/** + * @type { import("protractor").Config } + */ +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + browserName: 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ + spec: { + displayStacktrace: StacktraceOption.PRETTY + } + })); + } +}; \ No newline at end of file diff --git a/desktop/angular/e2e/src/app.e2e-spec.ts b/desktop/angular/e2e/src/app.e2e-spec.ts new file mode 100644 index 00000000..ada7d128 --- /dev/null +++ b/desktop/angular/e2e/src/app.e2e-spec.ts @@ -0,0 +1,23 @@ +import { AppPage } from './app.po'; +import { browser, logging } from 'protractor'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', () => { + page.navigateTo(); + expect(page.getTitleText()).toEqual('portmaster app is running!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + } as logging.Entry)); + }); +}); diff --git a/desktop/angular/e2e/src/app.po.ts b/desktop/angular/e2e/src/app.po.ts new file mode 100644 index 00000000..b68475e0 --- /dev/null +++ b/desktop/angular/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo(): Promise { + return browser.get(browser.baseUrl) as Promise; + } + + getTitleText(): Promise { + return element(by.css('app-root .content span')).getText() as Promise; + } +} diff --git a/desktop/angular/e2e/tsconfig.json b/desktop/angular/e2e/tsconfig.json new file mode 100644 index 00000000..426058ef --- /dev/null +++ b/desktop/angular/e2e/tsconfig.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es2018", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} diff --git a/desktop/angular/karma.conf.js b/desktop/angular/karma.conf.js new file mode 100644 index 00000000..344d4317 --- /dev/null +++ b/desktop/angular/karma.conf.js @@ -0,0 +1,32 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + coverageIstanbulReporter: { + dir: require('path').join(__dirname, './coverage/portmaster'), + reports: ['html', 'lcovonly', 'text-summary'], + fixWebpackSourcePaths: true + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/desktop/angular/package-lock.json b/desktop/angular/package-lock.json new file mode 100644 index 00000000..13f80b4f --- /dev/null +++ b/desktop/angular/package-lock.json @@ -0,0 +1,34959 @@ +{ + "name": "portmaster", + "version": "0.8.3", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "portmaster", + "version": "0.8.3", + "dependencies": { + "@angular/animations": "^16.0.1", + "@angular/cdk": "^16.0.1", + "@angular/common": "^16.0.1", + "@angular/compiler": "^16.0.1", + "@angular/core": "^16.0.1", + "@angular/forms": "^16.0.1", + "@angular/localize": "^16.0.1", + "@angular/platform-browser": "^16.0.1", + "@angular/platform-browser-dynamic": "^16.0.1", + "@angular/router": "^16.0.1", + "@fortawesome/angular-fontawesome": "^0.13.0", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@tauri-apps/api": "^2.0.0-beta.3", + "@tauri-apps/plugin-cli": "^2.0.0-beta.1", + "@tauri-apps/plugin-clipboard-manager": "^2.0.0-alpha.4", + "@tauri-apps/plugin-dialog": "^2.0.0-alpha.4", + "@tauri-apps/plugin-notification": "^2.0.0-alpha.4", + "@tauri-apps/plugin-os": "^2.0.0-alpha.5", + "@tauri-apps/plugin-shell": "^2.0.0-alpha.4", + "autoprefixer": "^10.4.14", + "d3": "^7.8.4", + "data-urls": "^5.0.0", + "emoji-toolkit": "^7.0.1", + "fuse.js": "^6.6.2", + "ng-zorro-antd": "^16.1.0", + "ngx-markdown": "^16.0.0", + "postcss": "^8.4.23", + "prismjs": "^1.29.0", + "psl": "^1.9.0", + "rxjs": "~7.8.1", + "topojson-client": "^3.1.0", + "topojson-simplify": "^3.0.3", + "tslib": "^2.5.0", + "whatwg-encoding": "^3.1.1", + "zone.js": "^0.13.0" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "^16.0.0-beta.1", + "@angular-devkit/build-angular": "^16.0.1", + "@angular-eslint/builder": "16.0.1", + "@angular-eslint/eslint-plugin": "16.0.1", + "@angular-eslint/eslint-plugin-template": "16.0.1", + "@angular-eslint/schematics": "16.0.1", + "@angular-eslint/template-parser": "16.0.1", + "@angular/cli": "^16.0.1", + "@angular/compiler-cli": "^16.0.1", + "@fullhuman/postcss-purgecss": "^5.0.0", + "@types/chrome": "^0.0.236", + "@types/d3": "^7.4.0", + "@types/data-urls": "^3.0.4", + "@types/jasmine": "^4.3.1", + "@types/jasminewd2": "~2.0.10", + "@types/node": "^20.1.5", + "@types/psl": "^1.1.0", + "@types/topojson-client": "^3.1.1", + "@types/topojson-simplify": "^3.0.1", + "@types/whatwg-encoding": "^2.0.3", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", + "eslint": "^8.40.0", + "jasmine-core": "^5.0.0", + "jasmine-spec-reporter": "^7.0.0", + "js-yaml-loader": "^1.2.2", + "ng-packagr": "^16.0.1", + "npm-run-all": "^4.1.5", + "postcss-import": "^15.1.0", + "postcss-loader": "^7.3.0", + "postcss-scss": "^4.0.6", + "protractor": "~7.0.0", + "tailwindcss": "^3.3.2", + "ts-node": "^10.9.1", + "tslint": "~6.1.0", + "typescript": "4.9", + "webpack-bundle-analyzer": "^4.8.0", + "webpack-ext-reloader": "^1.1.9", + "zip-a-folder": "^1.1.5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", + "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-builders/custom-webpack": { + "version": "16.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-16.0.0-beta.1.tgz", + "integrity": "sha512-C0tpgKJt++ciJ2nXtP2+fHOgzHUNyk5Su7bgTKY3yWMWlC9YfUMOlXHvNnCRUDaLqxXTsxQjGp56o9hPNd5miA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": ">=0.1600.0 < 0.1700.0", + "@angular-devkit/build-angular": "^16.0.0", + "@angular-devkit/core": "^16.0.0", + "lodash": "^4.17.15", + "ts-node": "^10.0.0", + "tsconfig-paths": "^4.1.0", + "webpack-merge": "^5.7.3" + }, + "engines": { + "node": "^14.20.0 || ^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0" + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/@angular-devkit/build-angular": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.0.2.tgz", + "integrity": "sha512-jh6ez6k1tPmLTQ8J2T0CY+aRqLbhCvaExH6pqB7q6/bkDItcLPrybDGfJf05F0dHvZPB2fQEK0xYz9i92POofQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.2.1", + "@angular-devkit/architect": "0.1600.2", + "@angular-devkit/build-webpack": "0.1600.2", + "@angular-devkit/core": "16.0.2", + "@babel/core": "7.21.4", + "@babel/generator": "7.21.4", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/helper-split-export-declaration": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.20.7", + "@babel/plugin-transform-async-to-generator": "7.20.7", + "@babel/plugin-transform-runtime": "7.21.4", + "@babel/preset-env": "7.21.4", + "@babel/runtime": "7.21.0", + "@babel/template": "7.20.7", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "16.0.2", + "@vitejs/plugin-basic-ssl": "1.0.1", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.14", + "babel-loader": "9.1.2", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "4.21.5", + "cacache": "17.0.6", + "chokidar": "3.5.3", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.3", + "esbuild-wasm": "0.17.18", + "glob": "8.1.0", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.0", + "mini-css-extract-plugin": "2.7.5", + "mrmime": "1.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "2.3.1", + "piscina": "3.2.0", + "postcss": "8.4.23", + "postcss-loader": "7.2.4", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.62.1", + "sass-loader": "13.2.2", + "semver": "7.4.0", + "source-map-loader": "4.0.1", + "source-map-support": "0.5.21", + "terser": "5.17.1", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.5.0", + "vite": "4.3.1", + "webpack": "5.80.0", + "webpack-dev-middleware": "6.0.2", + "webpack-dev-server": "4.13.2", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.17.18" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0", + "@angular/localize": "^16.0.0", + "@angular/platform-server": "^16.0.0", + "@angular/service-worker": "^16.0.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^16.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=4.9.3 <5.1" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/@angular-devkit/build-webpack": { + "version": "0.1600.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1600.2.tgz", + "integrity": "sha512-B7EYoRMZOT3RcorxkXaHvMqwuNSttJCicZ99DmwBC41YlZOxpVVP6uM6wvYINGO0TMtu9bCmKkrSD8IC/hHetQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1600.2", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/@angular-devkit/core": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.2.tgz", + "integrity": "sha512-V4+t0BHO+QML9O2IiG2mJi8DtjeMOm4LAuG6tNDeiHZGAPOflvSPsKBtVl2JlXX/JxdLmyF4B6kRoAXRMKcwTg==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/@ngtools/webpack": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.0.2.tgz", + "integrity": "sha512-8nPAOs2JLdMrAUf3sMkySzh66sPIkukO6HT8KVj726Dqm0Jtabjnxh0EI15Gkykj7HqH0Zw7/VyxpNQRfTA2UQ==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0", + "typescript": ">=4.9.3 <5.1", + "webpack": "^5.54.0" + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-builders/custom-webpack/node_modules/postcss-loader": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz", + "integrity": "sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.1.3", + "cosmiconfig-typescript-loader": "^4.3.0", + "klona": "^2.0.6", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "ts-node": ">=10", + "typescript": ">=4", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1600.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.2.tgz", + "integrity": "sha512-2AOP3/dwLywcjkRr3ixR/lb0uBn1jzaMWwQR3o7ye3IuEA2sRtyWhUzsy6V7smKBKWPDIbXvX2TcqYZAJ87ccA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.0.2", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.2.tgz", + "integrity": "sha512-V4+t0BHO+QML9O2IiG2mJi8DtjeMOm4LAuG6tNDeiHZGAPOflvSPsKBtVl2JlXX/JxdLmyF4B6kRoAXRMKcwTg==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.0.1.tgz", + "integrity": "sha512-VFhUViBfONOf6Ji4Lfkxlk+GN5l8Owm4Z0McqUIegrXsq3aSSStBBFdaDESpzhS6GIGqEBjjHMUQK8IlWT+EIQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.2.1", + "@angular-devkit/architect": "0.1600.1", + "@angular-devkit/build-webpack": "0.1600.1", + "@angular-devkit/core": "16.0.1", + "@babel/core": "7.21.4", + "@babel/generator": "7.21.4", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/helper-split-export-declaration": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.20.7", + "@babel/plugin-transform-async-to-generator": "7.20.7", + "@babel/plugin-transform-runtime": "7.21.4", + "@babel/preset-env": "7.21.4", + "@babel/runtime": "7.21.0", + "@babel/template": "7.20.7", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "16.0.1", + "@vitejs/plugin-basic-ssl": "1.0.1", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.14", + "babel-loader": "9.1.2", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "4.21.5", + "cacache": "17.0.6", + "chokidar": "3.5.3", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.3", + "esbuild-wasm": "0.17.18", + "glob": "8.1.0", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.0", + "mini-css-extract-plugin": "2.7.5", + "mrmime": "1.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "2.3.1", + "piscina": "3.2.0", + "postcss": "8.4.23", + "postcss-loader": "7.2.4", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.62.1", + "sass-loader": "13.2.2", + "semver": "7.4.0", + "source-map-loader": "4.0.1", + "source-map-support": "0.5.21", + "terser": "5.17.1", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.5.0", + "vite": "4.3.1", + "webpack": "5.80.0", + "webpack-dev-middleware": "6.0.2", + "webpack-dev-server": "4.13.2", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.17.18" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0", + "@angular/localize": "^16.0.0", + "@angular/platform-server": "^16.0.0", + "@angular/service-worker": "^16.0.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^16.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=4.9.3 <5.1" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.1.tgz", + "integrity": "sha512-7N3Dugrp3Fyyn3Q6RsxFNJJ2m1QuqcF3GHJcX7siINL37Hp6xI/q5gKffcd9rf20H1DYZE0VIbR1Sk31G6hMWg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.0.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/postcss-loader": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz", + "integrity": "sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.1.3", + "cosmiconfig-typescript-loader": "^4.3.0", + "klona": "^2.0.6", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "ts-node": ">=10", + "typescript": ">=4", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1600.1.tgz", + "integrity": "sha512-yCy5A1UwGzpst3QJ/CRo2Y8HWRqTPOfwAPAVl91Lbch7gBFViRvq6E7N1XfQunPu/eXvKxbuq2mFSDqtyZ1mWw==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1600.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.1.tgz", + "integrity": "sha512-7N3Dugrp3Fyyn3Q6RsxFNJJ2m1QuqcF3GHJcX7siINL37Hp6xI/q5gKffcd9rf20H1DYZE0VIbR1Sk31G6hMWg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.0.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.1.tgz", + "integrity": "sha512-2uz98IqkKJlgnHbWQ7VeL4pb+snGAZXIama2KXi+k9GsRntdcw+udX8rL3G9SdUGUF+m6+147Y1oRBMHsO/v4w==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.0.1.tgz", + "integrity": "sha512-A9D0LTYmiqiBa90GKcSuWb7hUouGIbm/AHbJbjL85WLLRbQA2PwKl7P5Mpd6nS/ZC0kfG4VQY3VOaDvb3qpI9g==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.0.1", + "jsonc-parser": "3.2.0", + "magic-string": "0.30.0", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-eslint/builder": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-16.0.1.tgz", + "integrity": "sha512-yjFltV+r3YjisVjASMPmWB/ASz39wdh0q5g0l6/4G+8yaxl6hEYs5o0ZOGeGdTFstCql8FGY+QKwKgsq9Ec4QQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "16.0.2", + "nx": "16.0.2" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-16.0.1.tgz", + "integrity": "sha512-amvTgKHtZoygivW3LAYZ9qjLWsXM7/7eaRvaHdmAEdjyFnYQZ7UbWMPSQNz1mlW/AzTFvk9lGGQORglNOSDnww==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-16.0.1.tgz", + "integrity": "sha512-CM9keS9cH1QAfSVfsvhw/oGCZcP/D8gfekWwVNjN/uEMEAak0czn1KOG7JQkE36NXOGtwCpTspMi1aa9CVKo9g==", + "dev": true, + "dependencies": { + "@angular-eslint/utils": "16.0.1", + "@typescript-eslint/utils": "5.59.2" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-16.0.1.tgz", + "integrity": "sha512-1hyfs+Iq7K2x3mDDE4985d8vDcMyknbE9HKHKUtRLfLKC9gnV3N5d4+UeySQ7Rrjvgzkc1g9qHADyuhwRWpDSA==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "16.0.1", + "@angular-eslint/utils": "16.0.1", + "@typescript-eslint/type-utils": "5.59.2", + "@typescript-eslint/utils": "5.59.2", + "aria-query": "5.1.3", + "axobject-query": "3.1.1" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", + "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@typescript-eslint/type-utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz", + "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.2", + "@typescript-eslint/utils": "5.59.2", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@typescript-eslint/types": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", + "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", + "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@typescript-eslint/utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", + "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", + "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", + "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", + "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", + "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", + "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", + "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@angular-eslint/schematics": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-16.0.1.tgz", + "integrity": "sha512-1oJJEWVbgPkNK1E8rAJfrgxzNWWzJKv3frTHeAm8gvZ7GftYhHjDcrcnxLWrYNxb9+q8Awi0hvGta/4HROmmnA==", + "dev": true, + "dependencies": { + "@angular-eslint/eslint-plugin": "16.0.1", + "@angular-eslint/eslint-plugin-template": "16.0.1", + "@nx/devkit": "16.0.2", + "ignore": "5.2.4", + "nx": "16.0.2", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "peerDependencies": { + "@angular/cli": ">= 16.0.0 < 17.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-16.0.1.tgz", + "integrity": "sha512-x0+SwSeqa3TiVZan6fE5grHsCkjGqU+zAS2DB6wAw5pyvgNAIjrI4cZEQ8pkgHfXe5tuumTKztlkpisah5s/hg==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "16.0.1", + "eslint-scope": "^7.0.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@angular-eslint/template-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-16.0.1.tgz", + "integrity": "sha512-2xnJuhIrMZEYK6UyBym6FaFXZgopIIbqfQ4sAtMWY6zYkCEsVUvx5qKIrsnXAwvpDQrv0WiMXteqi/5ICpVMZQ==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "16.0.1", + "@typescript-eslint/utils": "5.59.2" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", + "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", + "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", + "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/utils/node_modules/@typescript-eslint/utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", + "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@angular-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", + "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@angular-eslint/utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-eslint/utils/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@angular-eslint/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-eslint/utils/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@angular/animations": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.0.1.tgz", + "integrity": "sha512-ziRq1hGJJuQqQUHqNpEMp9uy1pVutvL8oNvawblh32u4bnLsVQU5gMd6sTonn0x4sphEwMNnuEmp/q6QRIx+pA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/core": "16.0.1" + } + }, + "node_modules/@angular/cdk": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.0.1.tgz", + "integrity": "sha512-GupYss6x84RWEoy3JTYu4Igr2SxHuV6whVKMScQG2/Gm+winOsOn7YWm0IZQuFnjSWIF2Va5B0Tp0IjFHWxTvA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^16.0.0 || ^17.0.0", + "@angular/core": "^16.0.0 || ^17.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.0.1.tgz", + "integrity": "sha512-0vIAcq/S+3NXXN4/gBQFVGaxLUQ0zhRxxHQQuiT7GGII73UySuhwvaFB1BEhYG5HVJjRrP1F0ZYbvsvrmFzfXQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1600.1", + "@angular-devkit/core": "16.0.1", + "@angular-devkit/schematics": "16.0.1", + "@schematics/angular": "16.0.1", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "npm-package-arg": "10.1.0", + "npm-pick-manifest": "8.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "pacote": "15.1.3", + "resolve": "1.22.2", + "semver": "7.4.0", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.1.tgz", + "integrity": "sha512-7N3Dugrp3Fyyn3Q6RsxFNJJ2m1QuqcF3GHJcX7siINL37Hp6xI/q5gKffcd9rf20H1DYZE0VIbR1Sk31G6hMWg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.0.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/common": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.0.1.tgz", + "integrity": "sha512-ic9Ri4Mepf4c0BTff7o4Oyl/a1vACNXXUzuoTwIjWnIqrH89dtwg7ncTD9Rv0N1lon7r4gXokTbn9A/Yk/0jbw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/core": "16.0.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.0.1.tgz", + "integrity": "sha512-7zNo6H1qVQow3T4EUul76SaIDSMRSl0hmtyWUzPjtWkxMjrCPSduqjA4/NHaG0KX1BsUvUtQEoDJ5jv/7EHWTQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/core": "16.0.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.0.1.tgz", + "integrity": "sha512-EW7Oxp8EuTz3vCNd4RAncZGB7dCUYviUkBA4PzuyPmL2copZPt12j9qx0pXXF3T6ydjoZ+99ZEgfkKOV6FeU3g==", + "dependencies": { + "@babel/core": "7.19.3", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/compiler": "16.0.1", + "typescript": ">=4.9.3 <5.1" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", + "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.3", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "dependencies": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.0.1.tgz", + "integrity": "sha512-3s4XBbzWgyWcjI0WFlNDKRxsbm4J+OKIL4mJCM9r8gWwno9y0K/giziAm9YMIJ4VOBIvrcMbOh85o44FCk8cRA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.13.0" + } + }, + "node_modules/@angular/forms": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.0.1.tgz", + "integrity": "sha512-VbH/YnEBau0q97zI7BjSk0pu/i2S0Y/zmhvA2wgI2CCvtbqT6hCNdE/3rW6ZFBcnuCe+dFhuchXe6dX28epsvg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.0.1", + "@angular/core": "16.0.1", + "@angular/platform-browser": "16.0.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/localize": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-16.0.1.tgz", + "integrity": "sha512-2zC7KE/JUA/JCHP+kEDSF8iZ9cyvd6OAPFE74yH8FjixQsaq9WhXiPtGkHC0bg9hWH858bRcCmA9BZr+zjntvA==", + "dependencies": { + "@babel/core": "7.19.3", + "glob": "8.1.0", + "yargs": "^17.2.1" + }, + "bin": { + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/compiler": "16.0.1", + "@angular/compiler-cli": "16.0.1" + } + }, + "node_modules/@angular/localize/node_modules/@babel/core": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", + "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.3", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/localize/node_modules/@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "dependencies": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/localize/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@angular/localize/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@angular/localize/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/localize/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/platform-browser": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.0.1.tgz", + "integrity": "sha512-7XLIOnTnGDJLE4Q0zBz6eI9q5V3NnsTAJqIICJHc4gk6jNgVz90gtejAQ4EFbo0d83XGzwFL22hxID5Dj1WRIA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/animations": "16.0.1", + "@angular/common": "16.0.1", + "@angular/core": "16.0.1" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.0.1.tgz", + "integrity": "sha512-qrGlRPqJM42WZcHCbzwTA8SiK90xrhM/VrOL/8/1okuHn82gSWbbynpqycdZnsI9XMbW+HNhpKR2n8HKV38Jug==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.0.1", + "@angular/compiler": "16.0.1", + "@angular/core": "16.0.1", + "@angular/platform-browser": "16.0.1" + } + }, + "node_modules/@angular/router": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.0.1.tgz", + "integrity": "sha512-4GH0SxPbuY08B/M0f3NEHf9yIFH+D3wlzWJHI75chfdqQ8gGAMG6B6PSmo3haicDxHcSnZTYNJXDLOQvaBAHcA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.0.1", + "@angular/core": "16.0.1", + "@angular/platform-browser": "16.0.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.0.0.tgz", + "integrity": "sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-angular": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-angular/-/icons-angular-16.0.0.tgz", + "integrity": "sha512-KWBmWZl2so49R/MdAT7aG+xaBlMKl9SArR3Du/iPA0Am9GI1i9R89KgnnLWz+gkzHTye15S1IBXpgts4GPPU/w==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": "^16.0.0", + "@angular/core": "^16.0.0", + "@angular/platform-browser": "^16.0.0", + "rxjs": "^6.4.0 || ^7.4.0" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", + "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.4", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "dependencies": { + "@babel/types": "^7.21.4", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz", + "integrity": "sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", + "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "dependencies": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz", + "integrity": "sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.21.5", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.21.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz", + "integrity": "sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.3.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", + "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz", + "integrity": "sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dependencies": { + "@babel/types": "^7.21.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", + "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz", + "integrity": "sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-member-expression-to-functions": "^7.21.5", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", + "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", + "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz", + "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz", + "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/template": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz", + "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz", + "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-simple-access": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz", + "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz", + "integrity": "sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-plugin-utils": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz", + "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.4.tgz", + "integrity": "sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.20.7", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.20.7", + "@babel/plugin-transform-destructuring": "^7.21.3", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.21.0", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.2", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.21.3", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.20.5", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.20.7", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.21.4", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "dependencies": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz", + "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==", + "optional": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.18.tgz", + "integrity": "sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.18.tgz", + "integrity": "sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.18.tgz", + "integrity": "sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.18.tgz", + "integrity": "sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.18.tgz", + "integrity": "sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.18.tgz", + "integrity": "sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.18.tgz", + "integrity": "sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.18.tgz", + "integrity": "sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.18.tgz", + "integrity": "sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.18.tgz", + "integrity": "sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.18.tgz", + "integrity": "sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.18.tgz", + "integrity": "sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.18.tgz", + "integrity": "sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.18.tgz", + "integrity": "sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.18.tgz", + "integrity": "sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.18.tgz", + "integrity": "sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.18.tgz", + "integrity": "sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.18.tgz", + "integrity": "sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.18.tgz", + "integrity": "sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.18.tgz", + "integrity": "sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.18.tgz", + "integrity": "sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.18.tgz", + "integrity": "sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", + "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fortawesome/angular-fontawesome": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz", + "integrity": "sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==", + "dependencies": { + "tslib": "^2.4.1" + }, + "peerDependencies": { + "@angular/core": "^16.0.0", + "@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2 || ^6.1.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz", + "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", + "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fullhuman/postcss-purgecss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-5.0.0.tgz", + "integrity": "sha512-onDS/b/2pMRzqSoj4qOs2tYFmOpaspjTAgvACIHMPiicu1ptajiBruTrjBzTKdxWdX0ldaBb7wj8nEaTLyFkJw==", + "dev": true, + "dependencies": { + "purgecss": "^5.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@ngtools/webpack": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.0.1.tgz", + "integrity": "sha512-CZHFPMiJuOe241kO1VSSPOQ5Z9hWWkY7eSs3hnS50Ntgd4YzlHAydqexmEFpXD2YLOFjdbNETCyJ2BQTM4Kwtw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0", + "typescript": ">=4.9.3 <5.1", + "webpack": "^5.54.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.4.tgz", + "integrity": "sha512-5yZghx+u5M47LghaybLCkdSyFzV/w4OuH12d96HO389Ik9CDsLaDZJVynSGGVJOLn6gy/k7Dz5XYcplM3uxXRg==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", + "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", + "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@nrwl/devkit": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-16.0.2.tgz", + "integrity": "sha512-SAEcImeQHdSTauO05FUn2vVl9/y5Kx1LNCZ4YE+SdY5/QRq18fuo/DCWmjOGG9M8r06vYGsAgMzkiB4soimcyA==", + "dev": true, + "dependencies": { + "@nx/devkit": "16.0.2" + } + }, + "node_modules/@nrwl/tao": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-16.0.2.tgz", + "integrity": "sha512-wimEe4OTpI7/nDK67RnpZpEXCU+fzA0sDgpIhMgbpPd0vPmKgaZv4nbs8zrm0goFlacmmnLaGRhhGYMOxE+1Lg==", + "dev": true, + "dependencies": { + "nx": "16.0.2" + }, + "bin": { + "tao": "index.js" + } + }, + "node_modules/@nx/devkit": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-16.0.2.tgz", + "integrity": "sha512-BY1Bj0BbAl6XJL0O+QGTWPs/3WMJTEQ+Y4Lfoq4dZM7RllE6rAylr54NA2wa4lsgordZhq1+0g5PVhKKvSVRRw==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "16.0.2", + "ejs": "^3.1.7", + "ignore": "^5.0.4", + "semver": "7.3.4", + "tmp": "~0.2.1", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "nx": ">= 15 <= 17" + } + }, + "node_modules/@nx/devkit/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nx/devkit/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@nx/nx-darwin-arm64": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.0.2.tgz", + "integrity": "sha512-nAT8WJ/qKGEvUcoFLHHye1dbwCd7b8CTZJlDF+ZkyCD/UZRHt4eJxy8gvKmxgkZTFb2+PPMQt4UORCUGpZzuoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-darwin-x64": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-16.0.2.tgz", + "integrity": "sha512-r0rfOrZaOyrwFR5a0UT05xkYRumfkP65cRSZM1TjCA027AG9llYtkLT1hlz8uMKt+P12zrWVzXSqGLDi022ZZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.0.2.tgz", + "integrity": "sha512-TfDQaGvCIDjn9sPg5U1Fr2rsSul/4PIQB59qrLBJRPiCWgpzwO71Il1qwSX68En+JH3lwXr+g5EjcDIEQ8fGYA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.0.2.tgz", + "integrity": "sha512-MICaUp7uz8WVQFXWPrmQaX1o4bdL7f3C7b3MDDf6+Zau6RcyQuw97UEKaYi9OqrV3w8yuPplqoLosFblAgb8uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.0.2.tgz", + "integrity": "sha512-wcBURG+6A2srm+6ujj8SShjwmYWs0eHI5D8vgZr8Bni+lXbKP/IosE9JGXKtRoh27/owyR8PGHhDVzjv46tlFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-x64-gnu": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.0.2.tgz", + "integrity": "sha512-Xyml2gFdVDHUj2g67DKz2aD78x1BciN1ZaaBTCxXL4MHfwR78SZa7mtRtE+1kj5OgVIwupZP50jq7C8GuSn3Hw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-x64-musl": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.0.2.tgz", + "integrity": "sha512-j3xdN8I5DlTgW5N5eCquyBZswrrYf6EazUCvnEpeejygwh3N6XN7DlD68Bs0CB4Zmd0tWLfTjNVAtUJSP6g2mA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.0.2.tgz", + "integrity": "sha512-R2pzoW3SUFBbe9C1vifJnXuysPl6kmutQHN2yQ9lwJptzPvMxfDU1FuXmKCGRUGmEwFxk/XPhwDL/ZcbABTrzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.0.2.tgz", + "integrity": "sha512-r4H/SsqfpIJa8QLSpnscgkMnLsnkRYXj8TcILDrf+nJazfEdJZLUvVhN9O85OB7pskv86NuGfnJmJHHXy6QVQg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", + "integrity": "sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^3.2.1", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "node_modules/@rollup/plugin-json": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz", + "integrity": "sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", + "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@schematics/angular": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.0.1.tgz", + "integrity": "sha512-MNgH/iB3WWxMLFVHJjtXCHZ8YHtfx2e3mX2Ds5P43OTgSnTk6tHabqvwxJ4wzjoyoPUyXWLhHt0diCmVtDTNeQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.0.1", + "@angular-devkit/schematics": "16.0.1", + "jsonc-parser": "3.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz", + "integrity": "sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@tauri-apps/api": { + "version": "2.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-beta.3.tgz", + "integrity": "sha512-gDSJzKpBs6efXw2ZWqjl9QVNImY5GR5qygXqB7JK4y7prcQInxnTj2ARFR0vD4wuzkrUHGrlIKraiJJPHWJ9vg==", + "engines": { + "node": ">= 18", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/plugin-cli": { + "version": "2.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-cli/-/plugin-cli-2.0.0-beta.1.tgz", + "integrity": "sha512-8VB0RTFi6SrCZvWDiOW+DVhCo7IsBenWfTIF6f8YAU+TnLSOAxpVc2MOM5PimVdKU2hu+mlpjSmPhd9RSCRfAw==", + "dependencies": { + "@tauri-apps/api": "2.0.0-beta.2" + } + }, + "node_modules/@tauri-apps/plugin-cli/node_modules/@tauri-apps/api": { + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-beta.2.tgz", + "integrity": "sha512-4r1r6kgttzIWxJ3HxkZQH+b7EiUtKhdUCPbi0KSalD+2T3j6klw+v8VyxhKwEdjM/eo60NE+J33v1E/Urq8puw==", + "engines": { + "node": ">= 18", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/plugin-clipboard-manager": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-alpha.4.tgz", + "integrity": "sha512-/xPQBXuzD8cSh81xkTphIAKxSD2kGsv8deKK+Qoh+89puay1xJjjnxVv5b9IKKn0G8r8HPm+JDEamlKxQbOgnA==", + "dependencies": { + "@tauri-apps/api": "2.0.0-alpha.12" + } + }, + "node_modules/@tauri-apps/plugin-clipboard-manager/node_modules/@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==", + "engines": { + "node": ">= 18", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-alpha.4.tgz", + "integrity": "sha512-4NxBgDzxrZ8hPE9OMRYwsXYN2BxQYI/5l1UKEI5V4srFTZK81Vj5GGksCf7gQREZg7CmBRCk95qYx338A6oCag==", + "dependencies": { + "@tauri-apps/api": "2.0.0-alpha.12" + } + }, + "node_modules/@tauri-apps/plugin-dialog/node_modules/@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==", + "engines": { + "node": ">= 18", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/plugin-notification": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.0.0-alpha.4.tgz", + "integrity": "sha512-mXUuZoZEEMAedGNJxPZPLET3vY4lSmHCpfrfZIytJRU6eSxbec90L3fB4YqvW9+yqkplyXkvpiThILbT5A4Q4w==", + "dependencies": { + "@tauri-apps/api": "2.0.0-alpha.12" + } + }, + "node_modules/@tauri-apps/plugin-notification/node_modules/@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==", + "engines": { + "node": ">= 18", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/plugin-os": { + "version": "2.0.0-alpha.5", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0-alpha.5.tgz", + "integrity": "sha512-dedPdad+ykMSZz2KUfrhUDyy32G2WH5aLkYdcACF58KC6GBvKuyR5sQ1ZE/pddo2L6VRhyujLp8zJEfRN3AUcQ==", + "dependencies": { + "@tauri-apps/api": "2.0.0-alpha.12" + } + }, + "node_modules/@tauri-apps/plugin-os/node_modules/@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==", + "engines": { + "node": ">= 18", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/plugin-shell": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-alpha.4.tgz", + "integrity": "sha512-Go/+EwGVuAXbSg2l2M5E2gT6cir66KV4CXC9P4gPHeead8Ar/B9wQvuINzcrYzL/HCcL7fFfKlqqu/XPTN2qvQ==", + "dependencies": { + "@tauri-apps/api": "2.0.0-alpha.12" + } + }, + "node_modules/@tauri-apps/plugin-shell/node_modules/@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==", + "engines": { + "node": ">= 18", + "npm": ">= 6.6.0", + "yarn": ">= 1.19.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@tufjs/canonical-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", + "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", + "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chrome": { + "version": "0.0.236", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.236.tgz", + "integrity": "sha512-ArQoxO9WtDY6GWcT2cpo+D+hyASPeFt7PHQEUDXwQhRS00Rbop07rnEOA046yws0HkM83Tcew/hW6Dgvnj4iMQ==", + "dev": true, + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.2.tgz", + "integrity": "sha512-5mjGjz6XOXKOCdTajXTZ/pMsg236RdiwKPrRPWAEf/2S/+PzwY+LLYShUpeysWaMvsdS7LArh6GdUefoxpchsQ==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz", + "integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.0.2.tgz", + "integrity": "sha512-WVx6zBiz4sWlboCy7TCgjeyHpNjMsoF36yaagny1uXfbadc9f+5BeBf7U+lRmQqY3EHbGQpP8UdW8AC+cywSwQ==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.0.tgz", + "integrity": "sha512-iGm7ZaGLq11RK3e69VeMM6Oqj2SjKUB9Qhcyd1zIcqn2uE8w9GFB445yCY46NOQO3ByaNyktX1DK+Etz7ZaX+w==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.0.2.tgz", + "integrity": "sha512-+krnrWOZ+aQB6v+E+jEkmkAx9HvsNAD+1LCD0vlBY3t+HwjKnsBFbpVLx6WWzDzCIuiTWdAxXMEnGnVXpB09qQ==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.2.tgz", + "integrity": "sha512-d29EDd0iUBrRoKhPndhDY6U/PYxOWqgIZwKTooy2UkBfU7TNZNpRho0yLWPxlatQrFWk2mnTu71IZQ4+LRgKlQ==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.0.2.tgz", + "integrity": "sha512-5+ButCmIfNX8id5seZ7jKj3igdcxx+S9IDBiT35fQGTLZUfkFgTv+oBH34xgeoWDKpWcMITSzBILWQtBoN5Piw==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/data-urls": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/data-urls/-/data-urls-3.0.4.tgz", + "integrity": "sha512-XRY2WVaOFSTKpNMaplqY1unPgAGk/DosOJ+eFrB6LJcFFbRH3nVbwJuGqLmDwdTWWx+V7U614/kmrj1JmCDl2A==", + "dev": true, + "dependencies": { + "@types/whatwg-mimetype": "*", + "@types/whatwg-url": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.2.tgz", + "integrity": "sha512-nQxgB8/Sg+QKhnV8e0WzPpxjIGT3tuJDDzybkDi8ItE/IgTlHo07U0shaIjzhcvQxlq9SDRE42lsJ23uvEgJ2A==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/filesystem": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz", + "integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==", + "dev": true, + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz", + "integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==", + "dev": true + }, + "node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/har-format": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.8.tgz", + "integrity": "sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.1.tgz", + "integrity": "sha512-Vu8l+UGcshYmV1VWwULgnV/2RDbBaO6i2Ptx7nd//oJPIZGhoI1YLST4VKagD2Pq/Bc2/7zvtvhM7F3p4SN7kQ==", + "dev": true + }, + "node_modules/@types/jasminewd2": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.10.tgz", + "integrity": "sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g==", + "dev": true, + "dependencies": { + "@types/jasmine": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/@types/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-zK4gSFMjgslsv5Lyvr3O1yCjgmnE4pr8jbG8qVn4QglMwtpvPCf4YT2Wma7Nk95OxUUJI8Z+kzdXohbM7mVpGw==", + "peer": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.1.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz", + "integrity": "sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg==", + "dev": true + }, + "node_modules/@types/psl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/psl/-/psl-1.1.0.tgz", + "integrity": "sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==", + "dev": true + }, + "node_modules/@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/selenium-webdriver": { + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz", + "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "node_modules/@types/topojson-client": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.1.tgz", + "integrity": "sha512-E4/Z2Xg56kVLRzYWem/6uOKVcVNqqxEqlWM9qCG2tCV1BxuzvvXC02/ELoGJWgtKkQhfycBPlMFEuTFdA/YiTg==", + "dev": true, + "dependencies": { + "@types/geojson": "*", + "@types/topojson-specification": "*" + } + }, + "node_modules/@types/topojson-simplify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/topojson-simplify/-/topojson-simplify-3.0.1.tgz", + "integrity": "sha512-H7SS2X11Lo3iRT3e7R6jPTAazOoSLD0LKIGq1b+4m/76Md46JfeU3zVIhxfIX9FY7oiyEbXwGumjK1GUXwIIMA==", + "dev": true, + "dependencies": { + "@types/geojson": "*", + "@types/topojson-specification": "*" + } + }, + "node_modules/@types/topojson-specification": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.2.tgz", + "integrity": "sha512-SGc1NdX9g3UGDp6S+p+uyG+Z8CehS51sUJ9bejA25Xgn2kkAguILk6J9nxXK+0M/mbTBN7ypMA7+4HVLNMJ8ag==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/webextension-polyfill": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.8.3.tgz", + "integrity": "sha512-GN+Hjzy9mXjWoXKmaicTegv3FJ0WFZ3aYz77Wk8TMp1IY3vEzvzj1vnsa0ggV7vMI1i+PUxe4qqnIJKCzf9aTg==", + "dev": true + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "dev": true + }, + "node_modules/@types/webpack": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.0.tgz", + "integrity": "sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==", + "dev": true, + "dependencies": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, + "node_modules/@types/webpack-sources": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", + "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + } + }, + "node_modules/@types/whatwg-encoding": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/whatwg-encoding/-/whatwg-encoding-2.0.3.tgz", + "integrity": "sha512-7TJfeaSFIWAKQ4ZynOb5zV3xzJQEEmL0U0j+uH7tnqfL97apXDTwMo0dB2uAWXAbr2dRRi5/eO9jV9dK/1GkiA==", + "dev": true + }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dev": true, + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", + "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/type-utils": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", + "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", + "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", + "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@typescript-eslint/type-utils/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", + "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", + "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", + "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", + "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.6", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", + "integrity": "sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/@yarnpkg/parsers": { + "version": "3.0.0-rc.43", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.43.tgz", + "integrity": "sha512-AhFF3mIDfA+jEwQv2WMHmiYhOvmdbh2qhUkDVQfiqzQtUwS4BgoWwom5NpSPg4Ix5vOul+w1690Bt21CkVLpgg==", + "dev": true, + "dependencies": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.15.0" + } + }, + "node_modules/@zkochan/js-yaml": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz", + "integrity": "sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@zkochan/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/archiver": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", + "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.3", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/argparse/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "blocking-proxy": "built/lib/bin.js" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserstack": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", + "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + } + }, + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.6.tgz", + "integrity": "sha512-ixcYmEBExFa/+ajIPjcwypxL97CjJyOsH9A/W+4qgEPIpJvKlC+HmVY8nkIck6n3PwUTdgq9c489niJGwl+5Cw==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.4.tgz", + "integrity": "sha512-fDboBse/sl1oXSLhIp0FcCJgzW9KmhC/q8ULTKC82zc+DL3TL7FNb8qlt5qqXN53MsKEUSIcb+7DLmEygOE5Yw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.0", + "minipass": "^5.0.0 || ^6.0.0", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001487", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz", + "integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", + "dev": true, + "dependencies": { + "del": "^4.1.1" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": ">=4.0.0 <6.0.0" + } + }, + "node_modules/clean-webpack-plugin/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-webpack-plugin/node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin/node_modules/del/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-webpack-plugin/node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin/node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin/node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin/node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/clean-webpack-plugin/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "optional": true, + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "dev": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", + "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "optional": true, + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cosmiconfig": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "dev": true, + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz", + "integrity": "sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=7", + "ts-node": ">=10", + "typescript": ">=3" + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + } + }, + "node_modules/critters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/critters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/critters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/critters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/critters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/critters/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/critters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "dev": true + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/cytoscape": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.25.0.tgz", + "integrity": "sha512-7MW3Iz57mCUo6JQCho6CmPBCbTlJr7LzyEtIkutG255HLVd4XuBg2I9BkTZLI/e4HoaOB/BiAzXuQybQ95+r9Q==", + "optional": true, + "dependencies": { + "heap": "^0.2.6", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "optional": true, + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "optional": true, + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "optional": true, + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "optional": true + }, + "node_modules/d3": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz", + "integrity": "sha512-q2WHStdhiBtD8DMmhDPyJmXUxr6VWRngKyiJ5EfXMxPw+tqT6BhNjhJZ4w3BHsNm3QoVfZLY8Orq/qPFczwKRA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", + "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz", + "integrity": "sha512-LtAIu54UctRmhGKllleflmHalttH3zkfSi4NlKrTAoFKjC+AFBJohsCAdgCBYQwH0F8hIOGY89X1pPqAchlMkA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", + "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz", + "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==", + "optional": true, + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/date-format": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.11.tgz", + "integrity": "sha512-VS20KRyorrbMCQmpdl2hg5KaOUsda1RbnsJg461FfrcyCUg+pkd0b40BSW4niQyTheww4DBXQnS7HwSrKkipLw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==", + "optional": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-equal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "dependencies": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "dependencies": { + "robust-predicates": "^3.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", + "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", + "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", + "optional": true + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.396.tgz", + "integrity": "sha512-pqKTdqp/c5vsrc0xUPYXTDBo9ixZuGY8es4ZOjjd6HD6bFYbu5QA09VoW3fkY4LF1T0zYk86lN6bZnNlBuOpdQ==" + }, + "node_modules/elkjs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", + "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==", + "optional": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/emoji-toolkit": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/emoji-toolkit/-/emoji-toolkit-7.0.1.tgz", + "integrity": "sha512-l5aJyAhpC5s4mDuoVuqt4SzVjwIsIvakPh4ZGJJE4KWuWFCEHaXacQFkStVdD9zbRR+/BbRXob7u99o0lQFr8A==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", + "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", + "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", + "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.18.tgz", + "integrity": "sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.18", + "@esbuild/android-arm64": "0.17.18", + "@esbuild/android-x64": "0.17.18", + "@esbuild/darwin-arm64": "0.17.18", + "@esbuild/darwin-x64": "0.17.18", + "@esbuild/freebsd-arm64": "0.17.18", + "@esbuild/freebsd-x64": "0.17.18", + "@esbuild/linux-arm": "0.17.18", + "@esbuild/linux-arm64": "0.17.18", + "@esbuild/linux-ia32": "0.17.18", + "@esbuild/linux-loong64": "0.17.18", + "@esbuild/linux-mips64el": "0.17.18", + "@esbuild/linux-ppc64": "0.17.18", + "@esbuild/linux-riscv64": "0.17.18", + "@esbuild/linux-s390x": "0.17.18", + "@esbuild/linux-x64": "0.17.18", + "@esbuild/netbsd-x64": "0.17.18", + "@esbuild/openbsd-x64": "0.17.18", + "@esbuild/sunos-x64": "0.17.18", + "@esbuild/win32-arm64": "0.17.18", + "@esbuild/win32-ia32": "0.17.18", + "@esbuild/win32-x64": "0.17.18" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.17.18.tgz", + "integrity": "sha512-h4m5zVa+KaDuRFIbH9dokMwovvkIjTQJS7/Ry+0Z1paVuS9aIkso2vdA2GmwH9GSvGX6w71WveJ3PfkoLuWaRw==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", + "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.40.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.2.tgz", + "integrity": "sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuse.js": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", + "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", + "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "optional": true, + "dependencies": { + "delegate": "^3.1.2" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/har-validator/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/har-validator/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "dependencies": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "node_modules/hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "optional": true + }, + "node_modules/hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", + "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "node_modules/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.0.0.tgz", + "integrity": "sha512-t0ikzf5qkSFqRl1e6ejKBe+Tk2bsQd8ivEkcisyGXsku2t8NvXZ1Y3RRz5vxrDgOrTBOi13CvGsVoI5wVpd7xg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/injection-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.4.0.tgz", + "integrity": "sha512-6jiJt0tCAo9zjHbcwLiPL+IuNe9SQ6a9g0PEzafThW3fOQi0mrmiJGBJvDD6tmhPh8cQHIQtCOrJuBfQME4kPA==", + "dev": true, + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-builtin-module/node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/jackspeak": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.0.tgz", + "integrity": "sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.8.6", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.6.tgz", + "integrity": "sha512-G43Ub9IYEFfu72sua6rzooi8V8Gz2lkfk48rW20vEWCGizeaEPlKB1Kh8JIA84yQbiAEfqlPmSpGgCKKxH3rDA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.0.0.tgz", + "integrity": "sha512-BJLxZlSVyWPN/oyaS1IIvIjChghI9/xWsLAIJqL9J5Fz47CN3JNr8Lmik3S2S7QS2RxclYjvSVSXP7IR35PAmg==", + "dev": true + }, + "node_modules/jasmine-spec-reporter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", + "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", + "dev": true, + "dependencies": { + "colors": "1.4.0" + } + }, + "node_modules/jasmine/node_modules/jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + }, + "node_modules/jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true, + "engines": { + "node": ">= 6.9.x" + } + }, + "node_modules/jest-worker": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", + "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml-loader": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/js-yaml-loader/-/js-yaml-loader-1.2.2.tgz", + "integrity": "sha512-H+NeuNrG6uOs/WMjna2SjkaCw13rMWiT/D7l9+9x5n8aq88BDsh2sRmdfxckWPIHtViYHWRG6XiCKYvS1dfyLg==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "loader-utils": "^1.2.3", + "un-eval": "^1.2.0" + } + }, + "node_modules/js-yaml-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/js-yaml-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/karma": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", + "integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/karma/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/katex": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.0.tgz", + "integrity": "sha512-wPRB4iUPysfH97wTgG5/tRLYxmKVq6Q4jRAWRVOUxXB1dsiv4cvcNjqabHkrOvJHM1Bpk3WrgmllSO1vIvP24w==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "optional": true, + "dependencies": { + "commander": "^8.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", + "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==", + "optional": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "optional": true + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", + "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "dev": true, + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "devOptional": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "optional": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.0.tgz", + "integrity": "sha512-3v8R7fd45UB6THucSht6wN2/7AZEruQbXdjygPZcxt5TA/msO6si9CN5MefUuKXbYnJHTBnYcx4famwcyQd+sA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "date-format": "^4.0.11", + "debug": "^4.3.4", + "flatted": "^3.2.5", + "rfdc": "^1.3.0", + "streamroller": "^3.1.1" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", + "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/make-fetch-happen/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.1.tgz", + "integrity": "sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.4.3.tgz", + "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==", + "optional": true, + "dependencies": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.9", + "dayjs": "^1.11.7", + "dompurify": "2.4.3", + "elkjs": "^0.8.2", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.2", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "node_modules/mermaid/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.5.tgz", + "integrity": "sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/ng-packagr": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-16.0.1.tgz", + "integrity": "sha512-MiJvSR+8olzCViwkQ6ihHLFWVNLdsfUNPCxrZqR7u1nOC/dXlWPf//l2IG0KLdVhHNCiM64mNdwaTpgDEBMD3w==", + "dev": true, + "dependencies": { + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-node-resolve": "^15.0.0", + "ajv": "^8.11.0", + "ansi-colors": "^4.1.3", + "autoprefixer": "^10.4.12", + "browserslist": "^4.21.4", + "cacache": "^17.0.0", + "chokidar": "^3.5.3", + "commander": "^10.0.0", + "convert-source-map": "^2.0.0", + "dependency-graph": "^0.11.0", + "esbuild-wasm": "^0.17.0", + "fast-glob": "^3.2.12", + "find-cache-dir": "^3.3.2", + "injection-js": "^2.4.0", + "jsonc-parser": "^3.2.0", + "less": "^4.1.3", + "ora": "^5.1.0", + "piscina": "^3.2.0", + "postcss": "^8.4.16", + "postcss-url": "^10.1.3", + "rollup": "^3.0.0", + "rxjs": "^7.5.6", + "sass": "^1.55.0" + }, + "bin": { + "ng-packagr": "cli/main.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "optionalDependencies": { + "esbuild": "^0.17.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0-next.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "tslib": "^2.3.0", + "typescript": ">=4.9.3 <5.1" + }, + "peerDependenciesMeta": { + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/ng-packagr/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/ng-packagr/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/ng-zorro-antd": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/ng-zorro-antd/-/ng-zorro-antd-16.1.0.tgz", + "integrity": "sha512-+KjXoA0+v/liTtVIHswmOAzB9UaGADrO1tL9AOZsTLq5sZM8+DmhtixGRoSMD8HkkhpMFhsgEIxoHlkxtn1SXg==", + "dependencies": { + "@angular/cdk": "^16.0.0", + "@ant-design/icons-angular": "^16.0.0", + "date-fns": "^2.16.1", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^16.0.0", + "@angular/common": "^16.0.0", + "@angular/core": "^16.0.0", + "@angular/forms": "^16.0.0", + "@angular/platform-browser": "^16.0.0", + "@angular/router": "^16.0.0" + } + }, + "node_modules/ngx-markdown": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-16.0.0.tgz", + "integrity": "sha512-/rlbXi+HBscJCDdwaTWIUrRkvwJicPnuAgeugOCZa0UbZ4VCWV3U0+uB1Zv6krRDF6FXJNXNLTUrMZV7yH8I6A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "clipboard": "^2.0.11", + "emoji-toolkit": "^7.0.0", + "katex": "^0.16.0", + "mermaid": "^9.1.2", + "prismjs": "^1.28.0" + }, + "peerDependencies": { + "@angular/common": "^16.0.0", + "@angular/core": "^16.0.0", + "@angular/platform-browser": "^16.0.0", + "@types/marked": "^4.3.0", + "marked": "^4.3.0", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.13.0" + } + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" + }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "optional": true + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz", + "integrity": "sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", + "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nx": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/nx/-/nx-16.0.2.tgz", + "integrity": "sha512-8Z9Bo1D2VbYjyC/F2ONensKjm10snz1UfkzURZiFA+oXikBPldiH1u67TOTpoCYZfyYQg4l6h6EpOaAvHF6Abg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@nrwl/tao": "16.0.2", + "@parcel/watcher": "2.0.4", + "@yarnpkg/lockfile": "^1.1.0", + "@yarnpkg/parsers": "^3.0.0-rc.18", + "@zkochan/js-yaml": "0.0.6", + "axios": "^1.0.0", + "chalk": "^4.1.0", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.1", + "cliui": "^7.0.2", + "dotenv": "~10.0.0", + "enquirer": "~2.3.6", + "fast-glob": "3.2.7", + "figures": "3.2.0", + "flat": "^5.0.2", + "fs-extra": "^11.1.0", + "glob": "7.1.4", + "ignore": "^5.0.4", + "js-yaml": "4.1.0", + "jsonc-parser": "3.2.0", + "lines-and-columns": "~2.0.3", + "minimatch": "3.0.5", + "npm-run-path": "^4.0.1", + "open": "^8.4.0", + "semver": "7.3.4", + "string-width": "^4.2.3", + "strong-log-transformer": "^2.1.0", + "tar-stream": "~2.2.0", + "tmp": "~0.2.1", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0", + "v8-compile-cache": "2.3.0", + "yargs": "^17.6.2", + "yargs-parser": "21.1.1" + }, + "bin": { + "nx": "bin/nx.js" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "16.0.2", + "@nx/nx-darwin-x64": "16.0.2", + "@nx/nx-linux-arm-gnueabihf": "16.0.2", + "@nx/nx-linux-arm64-gnu": "16.0.2", + "@nx/nx-linux-arm64-musl": "16.0.2", + "@nx/nx-linux-x64-gnu": "16.0.2", + "@nx/nx-linux-x64-musl": "16.0.2", + "@nx/nx-win32-arm64-msvc": "16.0.2", + "@nx/nx-win32-x64-msvc": "16.0.2" + }, + "peerDependencies": { + "@swc-node/register": "^1.4.2", + "@swc/core": "^1.2.173" + }, + "peerDependenciesMeta": { + "@swc-node/register": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/nx/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nx/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/nx/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/nx/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/nx/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/nx/node_modules/fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nx/node_modules/fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/nx/node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nx/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nx/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/nx/node_modules/lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/nx/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nx/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nx/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.1.3.tgz", + "integrity": "sha512-aRts8cZqxiJVDitmAh+3z+FxuO3tLNWEmwDRPEpDDiZJaRz06clP4XX112ynMT5uF0QNoMPajBBHnaStUEPJXA==", + "dev": true, + "dependencies": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^5.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.3.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "devOptional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "devOptional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.1.tgz", + "integrity": "sha512-UgmoiySyjFxP6tscZDgWGEAgsW5ok8W3F5CJDnnH2pozwSTGE6eH7vwTotMwATWA2r5xqdkKdxYPkwlJjAI/3g==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1", + "minipass": "^5.0.0 || ^6.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", + "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "dependencies": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0" + }, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.0.tgz", + "integrity": "sha512-qLAFjvR2BFNz1H930P7mj1iuWJFjGey/nVhimfOAAQ1ZyPpcClAxP8+A55Sl8mBvM+K2a9Pjgdj10KpANWrNfw==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.1.3", + "jiti": "^1.18.2", + "klona": "^2.0.6", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-loader/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz", + "integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + } + ], + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.19" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-url": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", + "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", + "dev": true, + "dependencies": { + "make-dir": "~3.1.0", + "mime": "~2.5.2", + "minimatch": "~3.0.4", + "xxhashjs": "~0.2.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-url/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protractor": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", + "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", + "dev": true, + "dependencies": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.1.7", + "yargs": "^15.3.1" + }, + "bin": { + "protractor": "bin/protractor", + "webdriver-manager": "bin/webdriver-manager" + }, + "engines": { + "node": ">=10.13.x" + } + }, + "node_modules/protractor/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/protractor/node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/protractor/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/protractor/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, + "node_modules/protractor/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/protractor/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/protractor/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/protractor/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/protractor/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/protractor/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/purgecss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-5.0.0.tgz", + "integrity": "sha512-RAnuxrGuVyLLTr8uMbKaxDRGWMgK5CCYDfRyUNNcaz5P3kGgD2b7ymQGYEyo2ST7Tl/ScwFgf5l3slKMxHSbrw==", + "dev": true, + "dependencies": { + "commander": "^9.0.0", + "glob": "^8.0.3", + "postcss": "^8.4.4", + "postcss-selector-parser": "^6.0.7" + }, + "bin": { + "purgecss": "bin/purgecss.js" + } + }, + "node_modules/purgecss/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/purgecss/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/purgecss/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/purgecss/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-package-json": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.3.tgz", + "integrity": "sha512-4QbpReW4kxFgeBQ0vPAqh2y8sXEB3D4t3jsXbJKIhBiF80KT6XRo45reqwtftju5J6ru1ax06A2Gb/wM1qCOEQ==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/read-package-json/node_modules/glob": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.4.tgz", + "integrity": "sha512-fDboBse/sl1oXSLhIp0FcCJgzW9KmhC/q8ULTKC82zc+DL3TL7FNb8qlt5qqXN53MsKEUSIcb+7DLmEygOE5Yw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.0", + "minipass": "^5.0.0 || ^6.0.0", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json/node_modules/minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.2.tgz", + "integrity": "sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==", + "dev": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, + "node_modules/rollup": { + "version": "3.21.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.8.tgz", + "integrity": "sha512-SSFV2T2fWtQ/vvBip85u2Nr0GNKireabH9d7nXswBg+XSH+jbVDSYptRAEbCEsquhs503rpPA9POYAp0/Jhasw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sass": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", + "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz", + "integrity": "sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==", + "dev": true, + "dependencies": { + "klona": "^2.0.6", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/saucelabs/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/saucelabs/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/saucelabs/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz", + "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", + "optional": true + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "dependencies": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "engines": { + "node": ">= 6.9.0" + } + }, + "node_modules/selenium-webdriver/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/selenium-webdriver/node_modules/tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", + "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sigstore": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.5.1.tgz", + "integrity": "sha512-FIPThk7S1oeFXn8O8yh7gpyiQb6lYXzMIlOBzXhId/f81VvU587xNCHc4jd2lZ9724UkKUYYTuKSYcjhDSRD/Q==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.1.0", + "make-fetch-happen": "^11.0.1", + "tuf-js": "^1.1.3" + }, + "bin": { + "sigstore": "bin/sigstore.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/sigstore/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/sigstore/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/sigstore/node_modules/minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz", + "integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.4.tgz", + "integrity": "sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamroller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.1.tgz", + "integrity": "sha512-iPhtd9unZ6zKdWgMeYGfSBuqCngyJy1B/GPi/lTpwGpa3bajuX30GjUVd0/Tn/Xhg0mr4DOSENozz9Y06qyonQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "date-format": "^4.0.10", + "debug": "^4.3.4", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz", + "integrity": "sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strong-log-transformer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", + "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.1", + "minimist": "^1.2.0", + "through": "^2.3.4" + }, + "bin": { + "sl-log-transformer": "bin/sl-log-transformer.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "optional": true + }, + "node_modules/stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + }, + "bin": { + "stylus": "bin/stylus" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://opencollective.com/stylus" + } + }, + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz", + "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/terser": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", + "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz", + "integrity": "sha512-WiHL3ElchZMsK27P8uIUh4604IgJyAW47LVXGbEoB21DbQcZ+OuMpGjVYnEUaqcWM6dO8uS2qUbA7LSCWqvsbg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/terser": { + "version": "5.17.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.3.tgz", + "integrity": "sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-simplify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topojson-simplify/-/topojson-simplify-3.0.3.tgz", + "integrity": "sha512-V+pBjLVzSQ3+hSOxBiV01OVXgFiCmMO8ia3huxKEyIMTC1ApQHBcdXdOqcQ6U2JJJD31TZduwY6KyF15R8sUgg==", + "dependencies": { + "commander": "2", + "topojson-client": "3" + }, + "bin": { + "toposimplify": "bin/toposimplify" + } + }, + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "optional": true, + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tslint/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tuf-js": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.6.tgz", + "integrity": "sha512-CXwFVIsXGbVY4vFiWF7TJKWmlKJAT8TWkH4RmiohJRcDJInix++F0dznDmoVbtJNzZ8yLprKUG4YrDIhv3nBMg==", + "dev": true, + "dependencies": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/tuf-js/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", + "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "optional": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/un-eval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/un-eval/-/un-eval-1.2.0.tgz", + "integrity": "sha512-Wlj/pum6dQtGTPD/lclDtoVPkSfpjPfy1dwnnKw/sZP5DpBH9fLhBgQfsqNhe5/gS1D+vkZUuB771NRMUPA5CA==", + "dev": true + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "dependencies": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + } + }, + "node_modules/useragent/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/useragent/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==", + "dev": true, + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.21", + "rollup": "^3.20.2" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "optional": true + }, + "node_modules/webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "dev": true, + "dependencies": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/webdriver-manager": { + "version": "12.1.9", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.9.tgz", + "integrity": "sha512-Yl113uKm8z4m/KMUVWHq1Sjtla2uxEBtx2Ue3AmIlnlPAKloDn/Lvmy6pqWCUersVISpdMeVpAaGbNnvMuT2LQ==", + "dev": true, + "dependencies": { + "adm-zip": "^0.5.2", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + }, + "bin": { + "webdriver-manager": "bin/webdriver-manager" + }, + "engines": { + "node": ">=6.9.x" + } + }, + "node_modules/webdriver-manager/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/webdriver-manager/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/webdriver-manager/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/webdriver-manager/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver-manager/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webextension-polyfill": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.8.0.tgz", + "integrity": "sha512-a19+DzlT6Kp9/UI+mF9XQopeZ+n2ussjhxHJ4/pmIGge9ijCDz7Gn93mNnjpZAk95T4Tae8iHZ6sSf869txqiQ==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.80.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", + "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.13.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz", + "integrity": "sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", + "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.0.2.tgz", + "integrity": "sha512-iOddiJzPcQC6lwOIu60vscbGWth8PCRcWRCwoQcTQf9RMoOWBHg5EyzpGdtSmGMrSPd5vHEfFXmVErQEmkRngQ==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.12", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz", + "integrity": "sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-ext-reloader": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/webpack-ext-reloader/-/webpack-ext-reloader-1.1.9.tgz", + "integrity": "sha512-6AVXGrjcVHKtIQn4yGGghJpiIV2h9F7hNKLsh1oP8m+d6H3QLF3jTNu3vNdKu/8Lab3J/gwb7Bm7tjZLa+DS6g==", + "dev": true, + "dependencies": { + "@types/webextension-polyfill": "^0.8.2", + "@types/webpack": "^5.28.0", + "@types/webpack-sources": "^3.2.0", + "clean-webpack-plugin": "^4.0.0", + "colors": "^1.4.0", + "cross-env": "^7.0.3", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "useragent": "^2.3.0", + "webextension-polyfill": "^0.8.0", + "webpack-sources": "^3.2.3", + "ws": "^8.4.2" + }, + "bin": { + "webpack-ext-reloader": "dist/webpack-ext-reloader-cli.js" + }, + "peerDependencies": { + "webpack": "^5.61.0" + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/which-typed-array": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "dependencies": { + "cuint": "^0.2.2" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-a-folder": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/zip-a-folder/-/zip-a-folder-1.1.5.tgz", + "integrity": "sha512-w6I4mvWc6D0Q4pdzCSFbQih/ezYBdjwGZVbWRRFMOYcOdtE9TONZ7YtXCPnHj4XJQmXQxTOWcRGnPYxRn+d0mw==", + "dev": true, + "dependencies": { + "archiver": "^5.3.1" + } + }, + "node_modules/zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zone.js": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.0.tgz", + "integrity": "sha512-7m3hNNyswsdoDobCkYNAy5WiUulkMd3+fWaGT9ij6iq3Zr/IwJo4RMCYPSDjT+r7tnPErmY9sZpKhWQ8S5k6XQ==", + "dependencies": { + "tslib": "^2.3.0" + } + } + }, + "dependencies": { + "@adobe/css-tools": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", + "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "dev": true, + "optional": true, + "peer": true + }, + "@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@angular-builders/custom-webpack": { + "version": "16.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-16.0.0-beta.1.tgz", + "integrity": "sha512-C0tpgKJt++ciJ2nXtP2+fHOgzHUNyk5Su7bgTKY3yWMWlC9YfUMOlXHvNnCRUDaLqxXTsxQjGp56o9hPNd5miA==", + "dev": true, + "requires": { + "@angular-devkit/architect": ">=0.1600.0 < 0.1700.0", + "@angular-devkit/build-angular": "^16.0.0", + "@angular-devkit/core": "^16.0.0", + "lodash": "^4.17.15", + "ts-node": "^10.0.0", + "tsconfig-paths": "^4.1.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "@angular-devkit/build-angular": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.0.2.tgz", + "integrity": "sha512-jh6ez6k1tPmLTQ8J2T0CY+aRqLbhCvaExH6pqB7q6/bkDItcLPrybDGfJf05F0dHvZPB2fQEK0xYz9i92POofQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "2.2.1", + "@angular-devkit/architect": "0.1600.2", + "@angular-devkit/build-webpack": "0.1600.2", + "@angular-devkit/core": "16.0.2", + "@babel/core": "7.21.4", + "@babel/generator": "7.21.4", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/helper-split-export-declaration": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.20.7", + "@babel/plugin-transform-async-to-generator": "7.20.7", + "@babel/plugin-transform-runtime": "7.21.4", + "@babel/preset-env": "7.21.4", + "@babel/runtime": "7.21.0", + "@babel/template": "7.20.7", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "16.0.2", + "@vitejs/plugin-basic-ssl": "1.0.1", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.14", + "babel-loader": "9.1.2", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "4.21.5", + "cacache": "17.0.6", + "chokidar": "3.5.3", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.3", + "esbuild": "0.17.18", + "esbuild-wasm": "0.17.18", + "glob": "8.1.0", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.0", + "mini-css-extract-plugin": "2.7.5", + "mrmime": "1.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "2.3.1", + "piscina": "3.2.0", + "postcss": "8.4.23", + "postcss-loader": "7.2.4", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.62.1", + "sass-loader": "13.2.2", + "semver": "7.4.0", + "source-map-loader": "4.0.1", + "source-map-support": "0.5.21", + "terser": "5.17.1", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.5.0", + "vite": "4.3.1", + "webpack": "5.80.0", + "webpack-dev-middleware": "6.0.2", + "webpack-dev-server": "4.13.2", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + } + }, + "@angular-devkit/build-webpack": { + "version": "0.1600.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1600.2.tgz", + "integrity": "sha512-B7EYoRMZOT3RcorxkXaHvMqwuNSttJCicZ99DmwBC41YlZOxpVVP6uM6wvYINGO0TMtu9bCmKkrSD8IC/hHetQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1600.2", + "rxjs": "7.8.1" + } + }, + "@angular-devkit/core": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.2.tgz", + "integrity": "sha512-V4+t0BHO+QML9O2IiG2mJi8DtjeMOm4LAuG6tNDeiHZGAPOflvSPsKBtVl2JlXX/JxdLmyF4B6kRoAXRMKcwTg==", + "dev": true, + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + }, + "@ngtools/webpack": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.0.2.tgz", + "integrity": "sha512-8nPAOs2JLdMrAUf3sMkySzh66sPIkukO6HT8KVj726Dqm0Jtabjnxh0EI15Gkykj7HqH0Zw7/VyxpNQRfTA2UQ==", + "dev": true, + "requires": {} + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "postcss-loader": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz", + "integrity": "sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==", + "dev": true, + "requires": { + "cosmiconfig": "^8.1.3", + "cosmiconfig-typescript-loader": "^4.3.0", + "klona": "^2.0.6", + "semver": "^7.3.8" + } + } + } + }, + "@angular-devkit/architect": { + "version": "0.1600.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.2.tgz", + "integrity": "sha512-2AOP3/dwLywcjkRr3ixR/lb0uBn1jzaMWwQR3o7ye3IuEA2sRtyWhUzsy6V7smKBKWPDIbXvX2TcqYZAJ87ccA==", + "dev": true, + "requires": { + "@angular-devkit/core": "16.0.2", + "rxjs": "7.8.1" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.2.tgz", + "integrity": "sha512-V4+t0BHO+QML9O2IiG2mJi8DtjeMOm4LAuG6tNDeiHZGAPOflvSPsKBtVl2JlXX/JxdLmyF4B6kRoAXRMKcwTg==", + "dev": true, + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + } + } + }, + "@angular-devkit/build-angular": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.0.1.tgz", + "integrity": "sha512-VFhUViBfONOf6Ji4Lfkxlk+GN5l8Owm4Z0McqUIegrXsq3aSSStBBFdaDESpzhS6GIGqEBjjHMUQK8IlWT+EIQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "2.2.1", + "@angular-devkit/architect": "0.1600.1", + "@angular-devkit/build-webpack": "0.1600.1", + "@angular-devkit/core": "16.0.1", + "@babel/core": "7.21.4", + "@babel/generator": "7.21.4", + "@babel/helper-annotate-as-pure": "7.18.6", + "@babel/helper-split-export-declaration": "7.18.6", + "@babel/plugin-proposal-async-generator-functions": "7.20.7", + "@babel/plugin-transform-async-to-generator": "7.20.7", + "@babel/plugin-transform-runtime": "7.21.4", + "@babel/preset-env": "7.21.4", + "@babel/runtime": "7.21.0", + "@babel/template": "7.20.7", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "16.0.1", + "@vitejs/plugin-basic-ssl": "1.0.1", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.14", + "babel-loader": "9.1.2", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "4.21.5", + "cacache": "17.0.6", + "chokidar": "3.5.3", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.16", + "css-loader": "6.7.3", + "esbuild": "0.17.18", + "esbuild-wasm": "0.17.18", + "glob": "8.1.0", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.0", + "mini-css-extract-plugin": "2.7.5", + "mrmime": "1.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "2.3.1", + "piscina": "3.2.0", + "postcss": "8.4.23", + "postcss-loader": "7.2.4", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.62.1", + "sass-loader": "13.2.2", + "semver": "7.4.0", + "source-map-loader": "4.0.1", + "source-map-support": "0.5.21", + "terser": "5.17.1", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.5.0", + "vite": "4.3.1", + "webpack": "5.80.0", + "webpack-dev-middleware": "6.0.2", + "webpack-dev-server": "4.13.2", + "webpack-merge": "5.8.0", + "webpack-subresource-integrity": "5.1.0" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.1.tgz", + "integrity": "sha512-7N3Dugrp3Fyyn3Q6RsxFNJJ2m1QuqcF3GHJcX7siINL37Hp6xI/q5gKffcd9rf20H1DYZE0VIbR1Sk31G6hMWg==", + "dev": true, + "requires": { + "@angular-devkit/core": "16.0.1", + "rxjs": "7.8.1" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "postcss-loader": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz", + "integrity": "sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==", + "dev": true, + "requires": { + "cosmiconfig": "^8.1.3", + "cosmiconfig-typescript-loader": "^4.3.0", + "klona": "^2.0.6", + "semver": "^7.3.8" + } + } + } + }, + "@angular-devkit/build-webpack": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1600.1.tgz", + "integrity": "sha512-yCy5A1UwGzpst3QJ/CRo2Y8HWRqTPOfwAPAVl91Lbch7gBFViRvq6E7N1XfQunPu/eXvKxbuq2mFSDqtyZ1mWw==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1600.1", + "rxjs": "7.8.1" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.1.tgz", + "integrity": "sha512-7N3Dugrp3Fyyn3Q6RsxFNJJ2m1QuqcF3GHJcX7siINL37Hp6xI/q5gKffcd9rf20H1DYZE0VIbR1Sk31G6hMWg==", + "dev": true, + "requires": { + "@angular-devkit/core": "16.0.1", + "rxjs": "7.8.1" + } + } + } + }, + "@angular-devkit/core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.1.tgz", + "integrity": "sha512-2uz98IqkKJlgnHbWQ7VeL4pb+snGAZXIama2KXi+k9GsRntdcw+udX8rL3G9SdUGUF+m6+147Y1oRBMHsO/v4w==", + "dev": true, + "requires": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + } + }, + "@angular-devkit/schematics": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.0.1.tgz", + "integrity": "sha512-A9D0LTYmiqiBa90GKcSuWb7hUouGIbm/AHbJbjL85WLLRbQA2PwKl7P5Mpd6nS/ZC0kfG4VQY3VOaDvb3qpI9g==", + "dev": true, + "requires": { + "@angular-devkit/core": "16.0.1", + "jsonc-parser": "3.2.0", + "magic-string": "0.30.0", + "ora": "5.4.1", + "rxjs": "7.8.1" + } + }, + "@angular-eslint/builder": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-16.0.1.tgz", + "integrity": "sha512-yjFltV+r3YjisVjASMPmWB/ASz39wdh0q5g0l6/4G+8yaxl6hEYs5o0ZOGeGdTFstCql8FGY+QKwKgsq9Ec4QQ==", + "dev": true, + "requires": { + "@nx/devkit": "16.0.2", + "nx": "16.0.2" + } + }, + "@angular-eslint/bundled-angular-compiler": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-16.0.1.tgz", + "integrity": "sha512-amvTgKHtZoygivW3LAYZ9qjLWsXM7/7eaRvaHdmAEdjyFnYQZ7UbWMPSQNz1mlW/AzTFvk9lGGQORglNOSDnww==", + "dev": true + }, + "@angular-eslint/eslint-plugin": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-16.0.1.tgz", + "integrity": "sha512-CM9keS9cH1QAfSVfsvhw/oGCZcP/D8gfekWwVNjN/uEMEAak0czn1KOG7JQkE36NXOGtwCpTspMi1aa9CVKo9g==", + "dev": true, + "requires": { + "@angular-eslint/utils": "16.0.1", + "@typescript-eslint/utils": "5.59.2" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", + "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2" + } + }, + "@typescript-eslint/types": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", + "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", + "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", + "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", + "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@angular-eslint/eslint-plugin-template": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-16.0.1.tgz", + "integrity": "sha512-1hyfs+Iq7K2x3mDDE4985d8vDcMyknbE9HKHKUtRLfLKC9gnV3N5d4+UeySQ7Rrjvgzkc1g9qHADyuhwRWpDSA==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "16.0.1", + "@angular-eslint/utils": "16.0.1", + "@typescript-eslint/type-utils": "5.59.2", + "@typescript-eslint/utils": "5.59.2", + "aria-query": "5.1.3", + "axobject-query": "3.1.1" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", + "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz", + "integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.59.2", + "@typescript-eslint/utils": "5.59.2", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", + "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", + "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", + "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", + "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@angular-eslint/schematics": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-16.0.1.tgz", + "integrity": "sha512-1oJJEWVbgPkNK1E8rAJfrgxzNWWzJKv3frTHeAm8gvZ7GftYhHjDcrcnxLWrYNxb9+q8Awi0hvGta/4HROmmnA==", + "dev": true, + "requires": { + "@angular-eslint/eslint-plugin": "16.0.1", + "@angular-eslint/eslint-plugin-template": "16.0.1", + "@nx/devkit": "16.0.2", + "ignore": "5.2.4", + "nx": "16.0.2", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "dependencies": { + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } + } + }, + "@angular-eslint/template-parser": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-16.0.1.tgz", + "integrity": "sha512-x0+SwSeqa3TiVZan6fE5grHsCkjGqU+zAS2DB6wAw5pyvgNAIjrI4cZEQ8pkgHfXe5tuumTKztlkpisah5s/hg==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "16.0.1", + "eslint-scope": "^7.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "@angular-eslint/utils": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-16.0.1.tgz", + "integrity": "sha512-2xnJuhIrMZEYK6UyBym6FaFXZgopIIbqfQ4sAtMWY6zYkCEsVUvx5qKIrsnXAwvpDQrv0WiMXteqi/5ICpVMZQ==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "16.0.1", + "@typescript-eslint/utils": "5.59.2" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz", + "integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2" + } + }, + "@typescript-eslint/types": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz", + "integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz", + "integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/visitor-keys": "5.59.2", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz", + "integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.2", + "@typescript-eslint/types": "5.59.2", + "@typescript-eslint/typescript-estree": "5.59.2", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz", + "integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@angular/animations": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.0.1.tgz", + "integrity": "sha512-ziRq1hGJJuQqQUHqNpEMp9uy1pVutvL8oNvawblh32u4bnLsVQU5gMd6sTonn0x4sphEwMNnuEmp/q6QRIx+pA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/cdk": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.0.1.tgz", + "integrity": "sha512-GupYss6x84RWEoy3JTYu4Igr2SxHuV6whVKMScQG2/Gm+winOsOn7YWm0IZQuFnjSWIF2Va5B0Tp0IjFHWxTvA==", + "requires": { + "parse5": "^7.1.2", + "tslib": "^2.3.0" + } + }, + "@angular/cli": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.0.1.tgz", + "integrity": "sha512-0vIAcq/S+3NXXN4/gBQFVGaxLUQ0zhRxxHQQuiT7GGII73UySuhwvaFB1BEhYG5HVJjRrP1F0ZYbvsvrmFzfXQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": "0.1600.1", + "@angular-devkit/core": "16.0.1", + "@angular-devkit/schematics": "16.0.1", + "@schematics/angular": "16.0.1", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.0.0", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "npm-package-arg": "10.1.0", + "npm-pick-manifest": "8.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "pacote": "15.1.3", + "resolve": "1.22.2", + "semver": "7.4.0", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1600.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.1.tgz", + "integrity": "sha512-7N3Dugrp3Fyyn3Q6RsxFNJJ2m1QuqcF3GHJcX7siINL37Hp6xI/q5gKffcd9rf20H1DYZE0VIbR1Sk31G6hMWg==", + "dev": true, + "requires": { + "@angular-devkit/core": "16.0.1", + "rxjs": "7.8.1" + } + } + } + }, + "@angular/common": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.0.1.tgz", + "integrity": "sha512-ic9Ri4Mepf4c0BTff7o4Oyl/a1vACNXXUzuoTwIjWnIqrH89dtwg7ncTD9Rv0N1lon7r4gXokTbn9A/Yk/0jbw==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.0.1.tgz", + "integrity": "sha512-7zNo6H1qVQow3T4EUul76SaIDSMRSl0hmtyWUzPjtWkxMjrCPSduqjA4/NHaG0KX1BsUvUtQEoDJ5jv/7EHWTQ==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/compiler-cli": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.0.1.tgz", + "integrity": "sha512-EW7Oxp8EuTz3vCNd4RAncZGB7dCUYviUkBA4PzuyPmL2copZPt12j9qx0pXXF3T6ydjoZ+99ZEgfkKOV6FeU3g==", + "requires": { + "@babel/core": "7.19.3", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", + "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.3", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "requires": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + } + } + }, + "@angular/core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.0.1.tgz", + "integrity": "sha512-3s4XBbzWgyWcjI0WFlNDKRxsbm4J+OKIL4mJCM9r8gWwno9y0K/giziAm9YMIJ4VOBIvrcMbOh85o44FCk8cRA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/forms": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.0.1.tgz", + "integrity": "sha512-VbH/YnEBau0q97zI7BjSk0pu/i2S0Y/zmhvA2wgI2CCvtbqT6hCNdE/3rW6ZFBcnuCe+dFhuchXe6dX28epsvg==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/localize": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-16.0.1.tgz", + "integrity": "sha512-2zC7KE/JUA/JCHP+kEDSF8iZ9cyvd6OAPFE74yH8FjixQsaq9WhXiPtGkHC0bg9hWH858bRcCmA9BZr+zjntvA==", + "requires": { + "@babel/core": "7.19.3", + "glob": "8.1.0", + "yargs": "^17.2.1" + }, + "dependencies": { + "@babel/core": { + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", + "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.3", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + } + }, + "@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "requires": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@angular/platform-browser": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.0.1.tgz", + "integrity": "sha512-7XLIOnTnGDJLE4Q0zBz6eI9q5V3NnsTAJqIICJHc4gk6jNgVz90gtejAQ4EFbo0d83XGzwFL22hxID5Dj1WRIA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/platform-browser-dynamic": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.0.1.tgz", + "integrity": "sha512-qrGlRPqJM42WZcHCbzwTA8SiK90xrhM/VrOL/8/1okuHn82gSWbbynpqycdZnsI9XMbW+HNhpKR2n8HKV38Jug==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/router": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.0.1.tgz", + "integrity": "sha512-4GH0SxPbuY08B/M0f3NEHf9yIFH+D3wlzWJHI75chfdqQ8gGAMG6B6PSmo3haicDxHcSnZTYNJXDLOQvaBAHcA==", + "requires": { + "tslib": "^2.3.0" + } + }, + "@ant-design/colors": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.0.0.tgz", + "integrity": "sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg==", + "requires": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "@ant-design/icons-angular": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-angular/-/icons-angular-16.0.0.tgz", + "integrity": "sha512-KWBmWZl2so49R/MdAT7aG+xaBlMKl9SArR3Du/iPA0Am9GI1i9R89KgnnLWz+gkzHTye15S1IBXpgts4GPPU/w==", + "requires": { + "@ant-design/colors": "^7.0.0", + "tslib": "^2.0.0" + } + }, + "@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", + "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==" + }, + "@babel/core": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.4", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@babel/generator": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "requires": { + "@babel/types": "^7.21.4", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz", + "integrity": "sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", + "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "requires": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz", + "integrity": "sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.21.5", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.21.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz", + "integrity": "sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.3.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", + "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==" + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz", + "integrity": "sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "requires": { + "@babel/types": "^7.21.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", + "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "requires": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + } + }, + "@babel/helper-replace-supers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz", + "integrity": "sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-member-expression-to-functions": "^7.21.5", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==" + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==" + }, + "@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + } + }, + "@babel/helpers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", + "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", + "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.19.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz", + "integrity": "sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz", + "integrity": "sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/template": "^7.20.7" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz", + "integrity": "sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz", + "integrity": "sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helper-plugin-utils": "^7.21.5", + "@babel/helper-simple-access": "^7.21.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz", + "integrity": "sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5", + "regenerator-transform": "^0.15.1" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz", + "integrity": "sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-plugin-utils": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.9" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz", + "integrity": "sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.21.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/preset-env": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.4.tgz", + "integrity": "sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.20.7", + "@babel/plugin-transform-async-to-generator": "^7.20.7", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.20.7", + "@babel/plugin-transform-destructuring": "^7.21.3", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.21.0", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.2", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.21.3", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.20.5", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.20.7", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.21.4", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "requires": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "requires": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + } + } + }, + "@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "requires": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@braintree/sanitize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz", + "integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==", + "optional": true + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "peer": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==" + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@esbuild/android-arm": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.18.tgz", + "integrity": "sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.18.tgz", + "integrity": "sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.18.tgz", + "integrity": "sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.18.tgz", + "integrity": "sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.18.tgz", + "integrity": "sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.18.tgz", + "integrity": "sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.18.tgz", + "integrity": "sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.18.tgz", + "integrity": "sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.18.tgz", + "integrity": "sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.18.tgz", + "integrity": "sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.18.tgz", + "integrity": "sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.18.tgz", + "integrity": "sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.18.tgz", + "integrity": "sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.18.tgz", + "integrity": "sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.18.tgz", + "integrity": "sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.18.tgz", + "integrity": "sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.18.tgz", + "integrity": "sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.18.tgz", + "integrity": "sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.18.tgz", + "integrity": "sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.18.tgz", + "integrity": "sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.18.tgz", + "integrity": "sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.18.tgz", + "integrity": "sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==", + "dev": true, + "optional": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", + "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "dev": true + }, + "@fortawesome/angular-fontawesome": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz", + "integrity": "sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==", + "requires": { + "tslib": "^2.4.1" + } + }, + "@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz", + "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz", + "integrity": "sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fullhuman/postcss-purgecss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-5.0.0.tgz", + "integrity": "sha512-onDS/b/2pMRzqSoj4qOs2tYFmOpaspjTAgvACIHMPiicu1ptajiBruTrjBzTKdxWdX0ldaBb7wj8nEaTLyFkJw==", + "dev": true, + "requires": { + "purgecss": "^5.0.0" + } + }, + "@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "@ngtools/webpack": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.0.1.tgz", + "integrity": "sha512-CZHFPMiJuOe241kO1VSSPOQ5Z9hWWkY7eSs3hnS50Ntgd4YzlHAydqexmEFpXD2YLOFjdbNETCyJ2BQTM4Kwtw==", + "dev": true, + "requires": {} + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.4.tgz", + "integrity": "sha512-5yZghx+u5M47LghaybLCkdSyFzV/w4OuH12d96HO389Ik9CDsLaDZJVynSGGVJOLn6gy/k7Dz5XYcplM3uxXRg==", + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@npmcli/installed-package-contents": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", + "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "dev": true, + "requires": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true + }, + "@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "requires": { + "which": "^3.0.0" + }, + "dependencies": { + "which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@npmcli/run-script": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", + "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "dev": true, + "requires": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "dependencies": { + "which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "@nrwl/devkit": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-16.0.2.tgz", + "integrity": "sha512-SAEcImeQHdSTauO05FUn2vVl9/y5Kx1LNCZ4YE+SdY5/QRq18fuo/DCWmjOGG9M8r06vYGsAgMzkiB4soimcyA==", + "dev": true, + "requires": { + "@nx/devkit": "16.0.2" + } + }, + "@nrwl/tao": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-16.0.2.tgz", + "integrity": "sha512-wimEe4OTpI7/nDK67RnpZpEXCU+fzA0sDgpIhMgbpPd0vPmKgaZv4nbs8zrm0goFlacmmnLaGRhhGYMOxE+1Lg==", + "dev": true, + "requires": { + "nx": "16.0.2" + } + }, + "@nx/devkit": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-16.0.2.tgz", + "integrity": "sha512-BY1Bj0BbAl6XJL0O+QGTWPs/3WMJTEQ+Y4Lfoq4dZM7RllE6rAylr54NA2wa4lsgordZhq1+0g5PVhKKvSVRRw==", + "dev": true, + "requires": { + "@nrwl/devkit": "16.0.2", + "ejs": "^3.1.7", + "ignore": "^5.0.4", + "semver": "7.3.4", + "tmp": "~0.2.1", + "tslib": "^2.3.0" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } + } + }, + "@nx/nx-darwin-arm64": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-16.0.2.tgz", + "integrity": "sha512-nAT8WJ/qKGEvUcoFLHHye1dbwCd7b8CTZJlDF+ZkyCD/UZRHt4eJxy8gvKmxgkZTFb2+PPMQt4UORCUGpZzuoA==", + "dev": true, + "optional": true + }, + "@nx/nx-darwin-x64": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-16.0.2.tgz", + "integrity": "sha512-r0rfOrZaOyrwFR5a0UT05xkYRumfkP65cRSZM1TjCA027AG9llYtkLT1hlz8uMKt+P12zrWVzXSqGLDi022ZZg==", + "dev": true, + "optional": true + }, + "@nx/nx-linux-arm-gnueabihf": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-16.0.2.tgz", + "integrity": "sha512-TfDQaGvCIDjn9sPg5U1Fr2rsSul/4PIQB59qrLBJRPiCWgpzwO71Il1qwSX68En+JH3lwXr+g5EjcDIEQ8fGYA==", + "dev": true, + "optional": true + }, + "@nx/nx-linux-arm64-gnu": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-16.0.2.tgz", + "integrity": "sha512-MICaUp7uz8WVQFXWPrmQaX1o4bdL7f3C7b3MDDf6+Zau6RcyQuw97UEKaYi9OqrV3w8yuPplqoLosFblAgb8uw==", + "dev": true, + "optional": true + }, + "@nx/nx-linux-arm64-musl": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-16.0.2.tgz", + "integrity": "sha512-wcBURG+6A2srm+6ujj8SShjwmYWs0eHI5D8vgZr8Bni+lXbKP/IosE9JGXKtRoh27/owyR8PGHhDVzjv46tlFg==", + "dev": true, + "optional": true + }, + "@nx/nx-linux-x64-gnu": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-16.0.2.tgz", + "integrity": "sha512-Xyml2gFdVDHUj2g67DKz2aD78x1BciN1ZaaBTCxXL4MHfwR78SZa7mtRtE+1kj5OgVIwupZP50jq7C8GuSn3Hw==", + "dev": true, + "optional": true + }, + "@nx/nx-linux-x64-musl": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-16.0.2.tgz", + "integrity": "sha512-j3xdN8I5DlTgW5N5eCquyBZswrrYf6EazUCvnEpeejygwh3N6XN7DlD68Bs0CB4Zmd0tWLfTjNVAtUJSP6g2mA==", + "dev": true, + "optional": true + }, + "@nx/nx-win32-arm64-msvc": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-16.0.2.tgz", + "integrity": "sha512-R2pzoW3SUFBbe9C1vifJnXuysPl6kmutQHN2yQ9lwJptzPvMxfDU1FuXmKCGRUGmEwFxk/XPhwDL/ZcbABTrzw==", + "dev": true, + "optional": true + }, + "@nx/nx-win32-x64-msvc": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.0.2.tgz", + "integrity": "sha512-r4H/SsqfpIJa8QLSpnscgkMnLsnkRYXj8TcILDrf+nJazfEdJZLUvVhN9O85OB7pskv86NuGfnJmJHHXy6QVQg==", + "dev": true, + "optional": true + }, + "@parcel/watcher": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", + "integrity": "sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg==", + "dev": true, + "requires": { + "node-addon-api": "^3.2.1", + "node-gyp-build": "^4.3.0" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "@rollup/plugin-json": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz", + "integrity": "sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1" + } + }, + "@rollup/plugin-node-resolve": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", + "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + } + }, + "@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + } + }, + "@schematics/angular": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.0.1.tgz", + "integrity": "sha512-MNgH/iB3WWxMLFVHJjtXCHZ8YHtfx2e3mX2Ds5P43OTgSnTk6tHabqvwxJ4wzjoyoPUyXWLhHt0diCmVtDTNeQ==", + "dev": true, + "requires": { + "@angular-devkit/core": "16.0.1", + "@angular-devkit/schematics": "16.0.1", + "jsonc-parser": "3.2.0" + } + }, + "@sigstore/protobuf-specs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz", + "integrity": "sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==", + "dev": true + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true, + "optional": true, + "peer": true + }, + "@tauri-apps/api": { + "version": "2.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-beta.3.tgz", + "integrity": "sha512-gDSJzKpBs6efXw2ZWqjl9QVNImY5GR5qygXqB7JK4y7prcQInxnTj2ARFR0vD4wuzkrUHGrlIKraiJJPHWJ9vg==" + }, + "@tauri-apps/plugin-cli": { + "version": "2.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-cli/-/plugin-cli-2.0.0-beta.1.tgz", + "integrity": "sha512-8VB0RTFi6SrCZvWDiOW+DVhCo7IsBenWfTIF6f8YAU+TnLSOAxpVc2MOM5PimVdKU2hu+mlpjSmPhd9RSCRfAw==", + "requires": { + "@tauri-apps/api": "2.0.0-beta.2" + }, + "dependencies": { + "@tauri-apps/api": { + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-beta.2.tgz", + "integrity": "sha512-4r1r6kgttzIWxJ3HxkZQH+b7EiUtKhdUCPbi0KSalD+2T3j6klw+v8VyxhKwEdjM/eo60NE+J33v1E/Urq8puw==" + } + } + }, + "@tauri-apps/plugin-clipboard-manager": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-alpha.4.tgz", + "integrity": "sha512-/xPQBXuzD8cSh81xkTphIAKxSD2kGsv8deKK+Qoh+89puay1xJjjnxVv5b9IKKn0G8r8HPm+JDEamlKxQbOgnA==", + "requires": { + "@tauri-apps/api": "2.0.0-alpha.12" + }, + "dependencies": { + "@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==" + } + } + }, + "@tauri-apps/plugin-dialog": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-alpha.4.tgz", + "integrity": "sha512-4NxBgDzxrZ8hPE9OMRYwsXYN2BxQYI/5l1UKEI5V4srFTZK81Vj5GGksCf7gQREZg7CmBRCk95qYx338A6oCag==", + "requires": { + "@tauri-apps/api": "2.0.0-alpha.12" + }, + "dependencies": { + "@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==" + } + } + }, + "@tauri-apps/plugin-notification": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.0.0-alpha.4.tgz", + "integrity": "sha512-mXUuZoZEEMAedGNJxPZPLET3vY4lSmHCpfrfZIytJRU6eSxbec90L3fB4YqvW9+yqkplyXkvpiThILbT5A4Q4w==", + "requires": { + "@tauri-apps/api": "2.0.0-alpha.12" + }, + "dependencies": { + "@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==" + } + } + }, + "@tauri-apps/plugin-os": { + "version": "2.0.0-alpha.5", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0-alpha.5.tgz", + "integrity": "sha512-dedPdad+ykMSZz2KUfrhUDyy32G2WH5aLkYdcACF58KC6GBvKuyR5sQ1ZE/pddo2L6VRhyujLp8zJEfRN3AUcQ==", + "requires": { + "@tauri-apps/api": "2.0.0-alpha.12" + }, + "dependencies": { + "@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==" + } + } + }, + "@tauri-apps/plugin-shell": { + "version": "2.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-alpha.4.tgz", + "integrity": "sha512-Go/+EwGVuAXbSg2l2M5E2gT6cir66KV4CXC9P4gPHeead8Ar/B9wQvuINzcrYzL/HCcL7fFfKlqqu/XPTN2qvQ==", + "requires": { + "@tauri-apps/api": "2.0.0-alpha.12" + }, + "dependencies": { + "@tauri-apps/api": { + "version": "2.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-alpha.12.tgz", + "integrity": "sha512-acpNZQxFgHMHC5qV/IUg4IL/xmypzfxHB4ECkwb58fT48H4zBmklNd5TC0k7BvLUBoSmmgHc4InbYwQai392Yw==" + } + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@tufjs/canonical-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", + "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "dev": true + }, + "@tufjs/models": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", + "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "dev": true, + "requires": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/chrome": { + "version": "0.0.236", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.236.tgz", + "integrity": "sha512-ArQoxO9WtDY6GWcT2cpo+D+hyASPeFt7PHQEUDXwQhRS00Rbop07rnEOA046yws0HkM83Tcew/hW6Dgvnj4iMQ==", + "dev": true, + "requires": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true, + "optional": true, + "peer": true + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "*" + } + }, + "@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "@types/d3-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.2.tgz", + "integrity": "sha512-5mjGjz6XOXKOCdTajXTZ/pMsg236RdiwKPrRPWAEf/2S/+PzwY+LLYShUpeysWaMvsdS7LArh6GdUefoxpchsQ==", + "dev": true + }, + "@types/d3-axis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz", + "integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, + "@types/d3-color": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.0.2.tgz", + "integrity": "sha512-WVx6zBiz4sWlboCy7TCgjeyHpNjMsoF36yaagny1uXfbadc9f+5BeBf7U+lRmQqY3EHbGQpP8UdW8AC+cywSwQ==", + "dev": true + }, + "@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "@types/d3-delaunay": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.0.tgz", + "integrity": "sha512-iGm7ZaGLq11RK3e69VeMM6Oqj2SjKUB9Qhcyd1zIcqn2uE8w9GFB445yCY46NOQO3ByaNyktX1DK+Etz7ZaX+w==", + "dev": true + }, + "@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "requires": { + "@types/d3-dsv": "*" + } + }, + "@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.0.2.tgz", + "integrity": "sha512-+krnrWOZ+aQB6v+E+jEkmkAx9HvsNAD+1LCD0vlBY3t+HwjKnsBFbpVLx6WWzDzCIuiTWdAxXMEnGnVXpB09qQ==", + "dev": true + }, + "@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, + "@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "dev": true, + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, + "@types/d3-selection": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.2.tgz", + "integrity": "sha512-d29EDd0iUBrRoKhPndhDY6U/PYxOWqgIZwKTooy2UkBfU7TNZNpRho0yLWPxlatQrFWk2mnTu71IZQ4+LRgKlQ==", + "dev": true + }, + "@types/d3-shape": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.0.2.tgz", + "integrity": "sha512-5+ButCmIfNX8id5seZ7jKj3igdcxx+S9IDBiT35fQGTLZUfkFgTv+oBH34xgeoWDKpWcMITSzBILWQtBoN5Piw==", + "dev": true, + "requires": { + "@types/d3-path": "*" + } + }, + "@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "@types/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "requires": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "@types/data-urls": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/data-urls/-/data-urls-3.0.4.tgz", + "integrity": "sha512-XRY2WVaOFSTKpNMaplqY1unPgAGk/DosOJ+eFrB6LJcFFbRH3nVbwJuGqLmDwdTWWx+V7U614/kmrj1JmCDl2A==", + "dev": true, + "requires": { + "@types/whatwg-mimetype": "*", + "@types/whatwg-url": "*" + } + }, + "@types/eslint": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.2.tgz", + "integrity": "sha512-nQxgB8/Sg+QKhnV8e0WzPpxjIGT3tuJDDzybkDi8ItE/IgTlHo07U0shaIjzhcvQxlq9SDRE42lsJ23uvEgJ2A==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/filesystem": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz", + "integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==", + "dev": true, + "requires": { + "@types/filewriter": "*" + } + }, + "@types/filewriter": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz", + "integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==", + "dev": true + }, + "@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==", + "dev": true + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/har-format": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.8.tgz", + "integrity": "sha512-OP6L9VuZNdskgNN3zFQQ54ceYD8OLq5IbqO4VK91ORLfOm7WdT/CiT/pHEBSQEqCInJ2y3O6iCm/zGtPElpgJQ==", + "dev": true + }, + "@types/http-proxy": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jasmine": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.3.1.tgz", + "integrity": "sha512-Vu8l+UGcshYmV1VWwULgnV/2RDbBaO6i2Ptx7nd//oJPIZGhoI1YLST4VKagD2Pq/Bc2/7zvtvhM7F3p4SN7kQ==", + "dev": true + }, + "@types/jasminewd2": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.10.tgz", + "integrity": "sha512-J7mDz7ovjwjc+Y9rR9rY53hFWKATcIkrr9DwQWmOas4/pnIPJTXawnzjwpHm3RSxz/e3ZVUvQ7cRbd5UQLo10g==", + "dev": true, + "requires": { + "@types/jasmine": "*" + } + }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "@types/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-zK4gSFMjgslsv5Lyvr3O1yCjgmnE4pr8jbG8qVn4QglMwtpvPCf4YT2Wma7Nk95OxUUJI8Z+kzdXohbM7mVpGw==", + "peer": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "@types/node": { + "version": "20.1.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.5.tgz", + "integrity": "sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg==", + "dev": true + }, + "@types/psl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/psl/-/psl-1.1.0.tgz", + "integrity": "sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==", + "dev": true + }, + "@types/q": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", + "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/selenium-webdriver": { + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz", + "integrity": "sha512-OFUilxQg+rWL2FMxtmIgCkUDlJB6pskkpvmew7yeXfzzsOBb5rc+y2+DjHm+r3r1ZPPcJefK3DveNSYWGiy68g==", + "dev": true + }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", + "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", + "dev": true, + "requires": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/topojson-client": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.1.tgz", + "integrity": "sha512-E4/Z2Xg56kVLRzYWem/6uOKVcVNqqxEqlWM9qCG2tCV1BxuzvvXC02/ELoGJWgtKkQhfycBPlMFEuTFdA/YiTg==", + "dev": true, + "requires": { + "@types/geojson": "*", + "@types/topojson-specification": "*" + } + }, + "@types/topojson-simplify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/topojson-simplify/-/topojson-simplify-3.0.1.tgz", + "integrity": "sha512-H7SS2X11Lo3iRT3e7R6jPTAazOoSLD0LKIGq1b+4m/76Md46JfeU3zVIhxfIX9FY7oiyEbXwGumjK1GUXwIIMA==", + "dev": true, + "requires": { + "@types/geojson": "*", + "@types/topojson-specification": "*" + } + }, + "@types/topojson-specification": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.2.tgz", + "integrity": "sha512-SGc1NdX9g3UGDp6S+p+uyG+Z8CehS51sUJ9bejA25Xgn2kkAguILk6J9nxXK+0M/mbTBN7ypMA7+4HVLNMJ8ag==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/webextension-polyfill": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.8.3.tgz", + "integrity": "sha512-GN+Hjzy9mXjWoXKmaicTegv3FJ0WFZ3aYz77Wk8TMp1IY3vEzvzj1vnsa0ggV7vMI1i+PUxe4qqnIJKCzf9aTg==", + "dev": true + }, + "@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "dev": true + }, + "@types/webpack": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.0.tgz", + "integrity": "sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==", + "dev": true, + "requires": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, + "@types/webpack-sources": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", + "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + } + }, + "@types/whatwg-encoding": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/whatwg-encoding/-/whatwg-encoding-2.0.3.tgz", + "integrity": "sha512-7TJfeaSFIWAKQ4ZynOb5zV3xzJQEEmL0U0j+uH7tnqfL97apXDTwMo0dB2uAWXAbr2dRRi5/eO9jV9dK/1GkiA==", + "dev": true + }, + "@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true + }, + "@types/whatwg-url": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", + "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", + "dev": true, + "requires": { + "@types/webidl-conversions": "*" + } + }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", + "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/type-utils": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", + "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", + "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", + "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/utils": "5.59.6", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/types": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", + "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", + "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", + "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", + "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.6", + "eslint-visitor-keys": "^3.3.0" + } + }, + "@vitejs/plugin-basic-ssl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", + "integrity": "sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==", + "dev": true, + "requires": {} + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "@yarnpkg/parsers": { + "version": "3.0.0-rc.43", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.43.tgz", + "integrity": "sha512-AhFF3mIDfA+jEwQv2WMHmiYhOvmdbh2qhUkDVQfiqzQtUwS4BgoWwom5NpSPg4Ix5vOul+w1690Bt21CkVLpgg==", + "dev": true, + "requires": { + "js-yaml": "^3.10.0", + "tslib": "^2.4.0" + } + }, + "@zkochan/js-yaml": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz", + "integrity": "sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } + } + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "peer": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, + "adm-zip": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", + "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "archiver": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", + "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "dev": true, + "requires": { + "archiver-utils": "^2.1.0", + "async": "^3.2.3", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + } + }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + } + } + }, + "array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + } + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "requires": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, + "babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "optional": true, + "peer": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "blocking-proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", + "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + } + } + }, + "bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "requires": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "browserstack": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.6.1.tgz", + "integrity": "sha512-GxtFjpIaKdbAyzHfFDKixKO8IBT7wR3NjbzrGc78nNs/Ciys9wU3/nBtsqsWv5nDSrdI5tz0peKuzCPuNXNUiw==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + } + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "requires": { + "semver": "^7.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "cacache": { + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.6.tgz", + "integrity": "sha512-ixcYmEBExFa/+ajIPjcwypxL97CjJyOsH9A/W+4qgEPIpJvKlC+HmVY8nkIck6n3PwUTdgq9c489niJGwl+5Cw==", + "dev": true, + "requires": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.4.tgz", + "integrity": "sha512-fDboBse/sl1oXSLhIp0FcCJgzW9KmhC/q8ULTKC82zc+DL3TL7FNb8qlt5qqXN53MsKEUSIcb+7DLmEygOE5Yw==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.0", + "minipass": "^5.0.0 || ^6.0.0", + "path-scurry": "^1.7.0" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001487", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz", + "integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "clean-webpack-plugin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", + "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", + "dev": true, + "requires": { + "del": "^4.1.1" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "optional": true, + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "dev": true, + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "optional": true, + "peer": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "requires": { + "is-what": "^3.14.1" + } + }, + "copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "requires": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, + "core-js-compat": { + "version": "3.30.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", + "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", + "dev": true, + "requires": { + "browserslist": "^4.21.5" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "optional": true, + "requires": { + "layout-base": "^1.0.0" + } + }, + "cosmiconfig": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", + "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "dev": true, + "requires": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + } + } + }, + "cosmiconfig-typescript-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz", + "integrity": "sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==", + "dev": true, + "requires": {} + }, + "crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true + }, + "crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dev": true, + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "critters": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.16.tgz", + "integrity": "sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "css-select": "^4.2.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "postcss": "^8.3.7", + "pretty-bytes": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "dependencies": { + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==", + "dev": true + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true, + "optional": true, + "peer": true + }, + "cytoscape": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.25.0.tgz", + "integrity": "sha512-7MW3Iz57mCUo6JQCho6CmPBCbTlJr7LzyEtIkutG255HLVd4XuBg2I9BkTZLI/e4HoaOB/BiAzXuQybQ95+r9Q==", + "optional": true, + "requires": { + "heap": "^0.2.6", + "lodash": "^4.17.21" + } + }, + "cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "optional": true, + "requires": { + "cose-base": "^1.0.0" + } + }, + "cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "optional": true, + "requires": { + "cose-base": "^2.2.0" + }, + "dependencies": { + "cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "optional": true, + "requires": { + "layout-base": "^2.0.0" + } + }, + "layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "optional": true + } + } + }, + "d3": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz", + "integrity": "sha512-q2WHStdhiBtD8DMmhDPyJmXUxr6VWRngKyiJ5EfXMxPw+tqT6BhNjhJZ4w3BHsNm3QoVfZLY8Orq/qPFczwKRA==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + } + }, + "d3-array": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + } + }, + "d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "requires": { + "d3-path": "1 - 3" + } + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-contour": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.0.tgz", + "integrity": "sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==", + "requires": { + "d3-array": "^3.2.0" + } + }, + "d3-delaunay": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz", + "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==", + "requires": { + "delaunator": "5" + } + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + } + } + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "requires": { + "d3-dsv": "1 - 3" + } + }, + "d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-geo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.0.1.tgz", + "integrity": "sha512-Wt23xBych5tSy9IYAM1FR2rWIBFWa52B/oF/GYe5zbdHrg08FU8+BuI6X4PvTwPDdqdAdq04fuWJpELtsaEjeA==", + "requires": { + "d3-array": "2.5.0 - 3" + } + }, + "d3-hierarchy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz", + "integrity": "sha512-LtAIu54UctRmhGKllleflmHalttH3zkfSi4NlKrTAoFKjC+AFBJohsCAdgCBYQwH0F8hIOGY89X1pPqAchlMkA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", + "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" + }, + "d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + }, + "d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + }, + "d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "requires": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", + "requires": { + "d3-path": "1 - 3" + } + }, + "d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + }, + "dagre-d3-es": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz", + "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==", + "optional": true, + "requires": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "requires": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + } + }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "date-format": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.11.tgz", + "integrity": "sha512-VS20KRyorrbMCQmpdl2hg5KaOUsda1RbnsJg461FfrcyCUg+pkd0b40BSW4niQyTheww4DBXQnS7HwSrKkipLw==", + "dev": true, + "optional": true, + "peer": true + }, + "dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==", + "optional": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-equal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "requires": { + "execa": "^5.0.0" + } + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "delaunator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", + "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", + "requires": { + "robust-predicates": "^3.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "optional": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true, + "optional": true, + "peer": true + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "dns-packet": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", + "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0" + } + }, + "dompurify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", + "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", + "optional": true + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, + "electron-to-chromium": { + "version": "1.4.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.396.tgz", + "integrity": "sha512-pqKTdqp/c5vsrc0xUPYXTDBo9ixZuGY8es4ZOjjd6HD6bFYbu5QA09VoW3fkY4LF1T0zYk86lN6bZnNlBuOpdQ==" + }, + "elkjs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", + "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==", + "optional": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "emoji-toolkit": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/emoji-toolkit/-/emoji-toolkit-7.0.1.tgz", + "integrity": "sha512-l5aJyAhpC5s4mDuoVuqt4SzVjwIsIvakPh4ZGJJE4KWuWFCEHaXacQFkStVdD9zbRR+/BbRXob7u99o0lQFr8A==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.2.tgz", + "integrity": "sha512-FKn/3oMiJjrOEOeUub2WCox6JhxBXq/Zn3fZOMCBxKnNYtsdKjxhl7yR3fZhM9PV+rdE75SU5SYMc+2PGzo+Tg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + } + }, + "engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "dev": true, + "optional": true, + "peer": true + }, + "enhanced-resolve": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", + "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true, + "optional": true, + "peer": true + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", + "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + } + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "esbuild": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.18.tgz", + "integrity": "sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.18", + "@esbuild/android-arm64": "0.17.18", + "@esbuild/android-x64": "0.17.18", + "@esbuild/darwin-arm64": "0.17.18", + "@esbuild/darwin-x64": "0.17.18", + "@esbuild/freebsd-arm64": "0.17.18", + "@esbuild/freebsd-x64": "0.17.18", + "@esbuild/linux-arm": "0.17.18", + "@esbuild/linux-arm64": "0.17.18", + "@esbuild/linux-ia32": "0.17.18", + "@esbuild/linux-loong64": "0.17.18", + "@esbuild/linux-mips64el": "0.17.18", + "@esbuild/linux-ppc64": "0.17.18", + "@esbuild/linux-riscv64": "0.17.18", + "@esbuild/linux-s390x": "0.17.18", + "@esbuild/linux-x64": "0.17.18", + "@esbuild/netbsd-x64": "0.17.18", + "@esbuild/openbsd-x64": "0.17.18", + "@esbuild/sunos-x64": "0.17.18", + "@esbuild/win32-arm64": "0.17.18", + "@esbuild/win32-ia32": "0.17.18", + "@esbuild/win32-x64": "0.17.18" + } + }, + "esbuild-wasm": { + "version": "0.17.18", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.17.18.tgz", + "integrity": "sha512-h4m5zVa+KaDuRFIbH9dokMwovvkIjTQJS7/Ry+0Z1paVuS9aIkso2vdA2GmwH9GSvGX6w71WveJ3PfkoLuWaRw==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", + "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.40.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true + }, + "espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "dependencies": { + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-minipass": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.2.tgz", + "integrity": "sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g==", + "dev": true, + "requires": { + "minipass": "^5.0.0" + } + }, + "fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "fuse.js": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", + "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==" + }, + "gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + } + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "globby": { + "version": "13.1.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.4.tgz", + "integrity": "sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "optional": true, + "requires": { + "delegate": "^3.1.2" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + } + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "requires": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "optional": true + }, + "hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "requires": { + "lru-cache": "^7.5.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } + } + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "requires": {} + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "ignore-walk": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", + "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "dev": true, + "requires": { + "minimatch": "^9.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", + "dev": true + }, + "immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.0.0.tgz", + "integrity": "sha512-t0ikzf5qkSFqRl1e6ejKBe+Tk2bsQd8ivEkcisyGXsku2t8NvXZ1Y3RRz5vxrDgOrTBOi13CvGsVoI5wVpd7xg==", + "dev": true + }, + "injection-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.4.0.tgz", + "integrity": "sha512-6jiJt0tCAo9zjHbcwLiPL+IuNe9SQ6a9g0PEzafThW3fOQi0mrmiJGBJvDD6tmhPh8cQHIQtCOrJuBfQME4kPA==", + "dev": true, + "requires": { + "tslib": "^2.0.0" + } + }, + "inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + } + }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" + }, + "dependencies": { + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + } + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.14" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true, + "optional": true, + "peer": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "jackspeak": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.0.tgz", + "integrity": "sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "jake": { + "version": "10.8.6", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.6.tgz", + "integrity": "sha512-G43Ub9IYEFfu72sua6rzooi8V8Gz2lkfk48rW20vEWCGizeaEPlKB1Kh8JIA84yQbiAEfqlPmSpGgCKKxH3rDA==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "dependencies": { + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", + "dev": true + } + } + }, + "jasmine-core": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.0.0.tgz", + "integrity": "sha512-BJLxZlSVyWPN/oyaS1IIvIjChghI9/xWsLAIJqL9J5Fz47CN3JNr8Lmik3S2S7QS2RxclYjvSVSXP7IR35PAmg==", + "dev": true + }, + "jasmine-spec-reporter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", + "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", + "dev": true, + "requires": { + "colors": "1.4.0" + } + }, + "jasminewd2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", + "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", + "dev": true + }, + "jest-worker": { + "version": "27.4.6", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", + "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jiti": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "dev": true + }, + "js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "js-yaml-loader": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/js-yaml-loader/-/js-yaml-loader-1.2.2.tgz", + "integrity": "sha512-H+NeuNrG6uOs/WMjna2SjkaCw13rMWiT/D7l9+9x5n8aq88BDsh2sRmdfxckWPIHtViYHWRG6XiCKYvS1dfyLg==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1", + "loader-utils": "^1.2.3", + "un-eval": "^1.2.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "karma": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", + "integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "peer": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "optional": true, + "peer": true + } + } + }, + "karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "requires": { + "source-map-support": "^0.5.5" + } + }, + "katex": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.0.tgz", + "integrity": "sha512-wPRB4iUPysfH97wTgG5/tRLYxmKVq6Q4jRAWRVOUxXB1dsiv4cvcNjqabHkrOvJHM1Bpk3WrgmllSO1vIvP24w==", + "optional": true, + "requires": { + "commander": "^8.0.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "optional": true + } + } + }, + "khroma": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.0.0.tgz", + "integrity": "sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==", + "optional": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true + }, + "launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "optional": true + }, + "lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "requires": { + "copy-anything": "^2.0.1", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", + "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "dev": true, + "requires": { + "klona": "^2.0.4" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "requires": { + "webpack-sources": "^3.0.0" + } + }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + } + } + }, + "loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "dev": true + }, + "loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "devOptional": true + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "optional": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "log4js": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.0.tgz", + "integrity": "sha512-3v8R7fd45UB6THucSht6wN2/7AZEruQbXdjygPZcxt5TA/msO6si9CN5MefUuKXbYnJHTBnYcx4famwcyQd+sA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "date-format": "^4.0.11", + "debug": "^4.3.4", + "flatted": "^3.2.5", + "rfdc": "^1.3.0", + "streamroller": "^3.1.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", + "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "dependencies": { + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + } + } + }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "peer": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memfs": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.1.tgz", + "integrity": "sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA==", + "dev": true, + "requires": { + "fs-monkey": "^1.0.3" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "mermaid": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.4.3.tgz", + "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==", + "optional": true, + "requires": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.9", + "dayjs": "^1.11.7", + "dompurify": "2.4.3", + "elkjs": "^0.8.2", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.2", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + }, + "dependencies": { + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "optional": true + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "optional": true, + "peer": true + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "dev": true + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dev": true, + "requires": { + "mime-db": "1.51.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mini-css-extract-plugin": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.5.tgz", + "integrity": "sha512-9HaR++0mlgom81s95vvNjxkg52n2b5s//3ZTI1EtzFb98awsLSivs2LMsVqnQ3ay0PVhqWcGNyDaTE961FOcjQ==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "requires": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "ng-packagr": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-16.0.1.tgz", + "integrity": "sha512-MiJvSR+8olzCViwkQ6ihHLFWVNLdsfUNPCxrZqR7u1nOC/dXlWPf//l2IG0KLdVhHNCiM64mNdwaTpgDEBMD3w==", + "dev": true, + "requires": { + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-node-resolve": "^15.0.0", + "ajv": "^8.11.0", + "ansi-colors": "^4.1.3", + "autoprefixer": "^10.4.12", + "browserslist": "^4.21.4", + "cacache": "^17.0.0", + "chokidar": "^3.5.3", + "commander": "^10.0.0", + "convert-source-map": "^2.0.0", + "dependency-graph": "^0.11.0", + "esbuild": "^0.17.0", + "esbuild-wasm": "^0.17.0", + "fast-glob": "^3.2.12", + "find-cache-dir": "^3.3.2", + "injection-js": "^2.4.0", + "jsonc-parser": "^3.2.0", + "less": "^4.1.3", + "ora": "^5.1.0", + "piscina": "^3.2.0", + "postcss": "^8.4.16", + "postcss-url": "^10.1.3", + "rollup": "^3.0.0", + "rxjs": "^7.5.6", + "sass": "^1.55.0" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } + } + }, + "ng-zorro-antd": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/ng-zorro-antd/-/ng-zorro-antd-16.1.0.tgz", + "integrity": "sha512-+KjXoA0+v/liTtVIHswmOAzB9UaGADrO1tL9AOZsTLq5sZM8+DmhtixGRoSMD8HkkhpMFhsgEIxoHlkxtn1SXg==", + "requires": { + "@angular/cdk": "^16.0.0", + "@ant-design/icons-angular": "^16.0.0", + "date-fns": "^2.16.1", + "tslib": "^2.3.0" + } + }, + "ngx-markdown": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-16.0.0.tgz", + "integrity": "sha512-/rlbXi+HBscJCDdwaTWIUrRkvwJicPnuAgeugOCZa0UbZ4VCWV3U0+uB1Zv6krRDF6FXJNXNLTUrMZV7yH8I6A==", + "requires": { + "clipboard": "^2.0.11", + "emoji-toolkit": "^7.0.0", + "katex": "^0.16.0", + "mermaid": "^9.1.2", + "prismjs": "^1.28.0", + "tslib": "^2.3.0" + } + }, + "nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "optional": true, + "requires": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true + }, + "node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "node-gyp-build": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz", + "integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==" + }, + "non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "optional": true + }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, + "normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^3.0.0" + } + }, + "npm-install-checks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz", + "integrity": "sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw==", + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true + }, + "npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "requires": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + } + }, + "npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "requires": { + "ignore-walk": "^6.0.0" + } + }, + "npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "requires": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + } + }, + "npm-registry-fetch": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", + "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "dev": true, + "requires": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + } + }, + "minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + } + } + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + } + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "requires": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "nx": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/nx/-/nx-16.0.2.tgz", + "integrity": "sha512-8Z9Bo1D2VbYjyC/F2ONensKjm10snz1UfkzURZiFA+oXikBPldiH1u67TOTpoCYZfyYQg4l6h6EpOaAvHF6Abg==", + "dev": true, + "requires": { + "@nrwl/tao": "16.0.2", + "@nx/nx-darwin-arm64": "16.0.2", + "@nx/nx-darwin-x64": "16.0.2", + "@nx/nx-linux-arm-gnueabihf": "16.0.2", + "@nx/nx-linux-arm64-gnu": "16.0.2", + "@nx/nx-linux-arm64-musl": "16.0.2", + "@nx/nx-linux-x64-gnu": "16.0.2", + "@nx/nx-linux-x64-musl": "16.0.2", + "@nx/nx-win32-arm64-msvc": "16.0.2", + "@nx/nx-win32-x64-msvc": "16.0.2", + "@parcel/watcher": "2.0.4", + "@yarnpkg/lockfile": "^1.1.0", + "@yarnpkg/parsers": "^3.0.0-rc.18", + "@zkochan/js-yaml": "0.0.6", + "axios": "^1.0.0", + "chalk": "^4.1.0", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.1", + "cliui": "^7.0.2", + "dotenv": "~10.0.0", + "enquirer": "~2.3.6", + "fast-glob": "3.2.7", + "figures": "3.2.0", + "flat": "^5.0.2", + "fs-extra": "^11.1.0", + "glob": "7.1.4", + "ignore": "^5.0.4", + "js-yaml": "4.1.0", + "jsonc-parser": "3.2.0", + "lines-and-columns": "~2.0.3", + "minimatch": "3.0.5", + "npm-run-path": "^4.0.1", + "open": "^8.4.0", + "semver": "7.3.4", + "string-width": "^4.2.3", + "strong-log-transformer": "^2.1.0", + "tar-stream": "~2.2.0", + "tmp": "~0.2.1", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0", + "v8-compile-cache": "2.3.0", + "yargs": "^17.6.2", + "yargs-parser": "21.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fs-extra": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true + }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "dependencies": { + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pacote": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.1.3.tgz", + "integrity": "sha512-aRts8cZqxiJVDitmAh+3z+FxuO3tLNWEmwDRPEpDDiZJaRz06clP4XX112ynMT5uF0QNoMPajBBHnaStUEPJXA==", + "dev": true, + "requires": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^5.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.3.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + } + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "devOptional": true, + "requires": { + "entities": "^4.4.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "devOptional": true + } + } + }, + "parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "requires": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + } + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } + }, + "parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "requires": { + "parse5": "^7.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-scurry": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.1.tgz", + "integrity": "sha512-UgmoiySyjFxP6tscZDgWGEAgsW5ok8W3F5CJDnnH2pozwSTGE6eH7vwTotMwATWA2r5xqdkKdxYPkwlJjAI/3g==", + "dev": true, + "requires": { + "lru-cache": "^9.1.1", + "minipass": "^5.0.0 || ^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", + "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", + "dev": true + } + } + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "piscina": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", + "integrity": "sha512-yn/jMdHRw+q2ZJhFhyqsmANcbF6V2QwmD84c6xRau+QpQOmtrBCoRGdvTfeuFDYXB5W2m6MfLkjkvQa9lUSmIA==", + "dev": true, + "requires": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0", + "nice-napi": "^1.0.2" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true + }, + "postcss": { + "version": "8.4.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", + "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + } + }, + "postcss-loader": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.0.tgz", + "integrity": "sha512-qLAFjvR2BFNz1H930P7mj1iuWJFjGey/nVhimfOAAQ1ZyPpcClAxP8+A55Sl8mBvM+K2a9Pjgdj10KpANWrNfw==", + "dev": true, + "requires": { + "cosmiconfig": "^8.1.3", + "jiti": "^1.18.2", + "klona": "^2.0.6", + "semver": "^7.3.8" + }, + "dependencies": { + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.11" + } + }, + "postcss-scss": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz", + "integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==", + "dev": true, + "requires": {} + }, + "postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-url": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-10.1.3.tgz", + "integrity": "sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==", + "dev": true, + "requires": { + "make-dir": "~3.1.0", + "mime": "~2.5.2", + "minimatch": "~3.0.4", + "xxhashjs": "~0.2.2" + }, + "dependencies": { + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + } + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true + }, + "prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==" + }, + "proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "protractor": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/protractor/-/protractor-7.0.0.tgz", + "integrity": "sha512-UqkFjivi4GcvUQYzqGYNe0mLzfn5jiLmO8w9nMhQoJRLhy2grJonpga2IWhI6yJO30LibWXJJtA4MOIZD2GgZw==", + "dev": true, + "requires": { + "@types/q": "^0.0.32", + "@types/selenium-webdriver": "^3.0.0", + "blocking-proxy": "^1.0.0", + "browserstack": "^1.5.1", + "chalk": "^1.1.3", + "glob": "^7.0.3", + "jasmine": "2.8.0", + "jasminewd2": "^2.1.0", + "q": "1.4.1", + "saucelabs": "^1.5.0", + "selenium-webdriver": "3.6.0", + "source-map-support": "~0.4.0", + "webdriver-js-extender": "2.1.0", + "webdriver-manager": "^12.1.7", + "yargs": "^15.3.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + } + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, + "purgecss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-5.0.0.tgz", + "integrity": "sha512-RAnuxrGuVyLLTr8uMbKaxDRGWMgK5CCYDfRyUNNcaz5P3kGgD2b7ymQGYEyo2ST7Tl/ScwFgf5l3slKMxHSbrw==", + "dev": true, + "requires": { + "commander": "^9.0.0", + "glob": "^8.0.3", + "postcss": "^8.4.4", + "postcss-selector-parser": "^6.0.7" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "optional": true, + "peer": true + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "read-package-json": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.3.tgz", + "integrity": "sha512-4QbpReW4kxFgeBQ0vPAqh2y8sXEB3D4t3jsXbJKIhBiF80KT6XRo45reqwtftju5J6ru1ax06A2Gb/wM1qCOEQ==", + "dev": true, + "requires": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.4.tgz", + "integrity": "sha512-fDboBse/sl1oXSLhIp0FcCJgzW9KmhC/q8ULTKC82zc+DL3TL7FNb8qlt5qqXN53MsKEUSIcb+7DLmEygOE5Yw==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.0", + "minipass": "^5.0.0 || ^6.0.0", + "path-scurry": "^1.7.0" + } + }, + "json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true + }, + "minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "requires": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "dependencies": { + "json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true + } + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-glob": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.2.tgz", + "integrity": "sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==", + "dev": true, + "requires": { + "minimatch": "^5.1.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + } + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true + } + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "requires": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true, + "optional": true, + "peer": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "robust-predicates": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz", + "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==" + }, + "rollup": { + "version": "3.21.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.8.tgz", + "integrity": "sha512-SSFV2T2fWtQ/vvBip85u2Nr0GNKireabH9d7nXswBg+XSH+jbVDSYptRAEbCEsquhs503rpPA9POYAp0/Jhasw==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "requires": { + "tslib": "^2.1.0" + } + }, + "safe-array-concat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sass": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.1.tgz", + "integrity": "sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "sass-loader": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz", + "integrity": "sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==", + "dev": true, + "requires": { + "klona": "^2.0.6", + "neo-async": "^2.6.2" + } + }, + "saucelabs": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", + "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", + "dev": true, + "requires": { + "https-proxy-agent": "^2.2.1" + }, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "schema-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz", + "integrity": "sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", + "optional": true + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "selenium-webdriver": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", + "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "dev": true, + "requires": { + "jszip": "^3.1.3", + "rimraf": "^2.5.4", + "tmp": "0.0.30", + "xml2js": "^0.4.17" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "tmp": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", + "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.1" + } + } + } + }, + "selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "requires": { + "node-forge": "^1" + } + }, + "semver": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", + "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "dev": true, + "requires": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sigstore": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.5.1.tgz", + "integrity": "sha512-FIPThk7S1oeFXn8O8yh7gpyiQb6lYXzMIlOBzXhId/f81VvU587xNCHc4jd2lZ9724UkKUYYTuKSYcjhDSRD/Q==", + "dev": true, + "requires": { + "@sigstore/protobuf-specs": "^0.1.0", + "make-fetch-happen": "^11.0.1", + "tuf-js": "^1.1.3" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + } + }, + "minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + } + } + }, + "sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "requires": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, + "socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + } + }, + "socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ws": "~8.11.0" + } + }, + "socket.io-parser": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.3.tgz", + "integrity": "sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + } + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.4.tgz", + "integrity": "sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==", + "dev": true, + "requires": { + "minipass": "^5.0.0" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" + } + }, + "streamroller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.1.tgz", + "integrity": "sha512-iPhtd9unZ6zKdWgMeYGfSBuqCngyJy1B/GPi/lTpwGpa3bajuX30GjUVd0/Tn/Xhg0mr4DOSENozz9Y06qyonQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "date-format": "^4.0.10", + "debug": "^4.3.4", + "fs-extra": "^10.1.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.padend": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz", + "integrity": "sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "strong-log-transformer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", + "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "minimist": "^1.2.0", + "through": "^2.3.4" + } + }, + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "optional": true + }, + "stylus": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", + "glob": "^7.1.6", + "sax": "~1.2.4", + "source-map": "^0.7.3" + } + }, + "sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true + }, + "tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "dev": true, + "requires": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz", + "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + } + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "terser": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", + "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz", + "integrity": "sha512-WiHL3ElchZMsK27P8uIUh4604IgJyAW47LVXGbEoB21DbQcZ+OuMpGjVYnEUaqcWM6dO8uS2qUbA7LSCWqvsbg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "dependencies": { + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "terser": { + "version": "5.17.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.3.tgz", + "integrity": "sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "optional": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "requires": { + "commander": "2" + } + }, + "topojson-simplify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topojson-simplify/-/topojson-simplify-3.0.3.tgz", + "integrity": "sha512-V+pBjLVzSQ3+hSOxBiV01OVXgFiCmMO8ia3huxKEyIMTC1ApQHBcdXdOqcQ6U2JJJD31TZduwY6KyF15R8sUgg==", + "requires": { + "commander": "2", + "topojson-client": "3" + } + }, + "totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "requires": { + "punycode": "^2.3.1" + } + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "optional": true + }, + "ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + } + } + }, + "tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "requires": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tuf-js": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.6.tgz", + "integrity": "sha512-CXwFVIsXGbVY4vFiWF7TJKWmlKJAT8TWkH4RmiohJRcDJInix++F0dznDmoVbtJNzZ8yLprKUG4YrDIhv3nBMg==", + "dev": true, + "requires": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.0" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + } + }, + "minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + } + }, + "typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + }, + "ua-parser-js": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", + "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "dev": true, + "optional": true, + "peer": true + }, + "un-eval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/un-eval/-/un-eval-1.2.0.tgz", + "integrity": "sha512-Wlj/pum6dQtGTPD/lclDtoVPkSfpjPfy1dwnnKw/sZP5DpBH9fLhBgQfsqNhe5/gS1D+vkZUuB771NRMUPA5CA==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true + }, + "unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "requires": { + "unique-slug": "^4.0.0" + } + }, + "unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "useragent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", + "dev": true, + "requires": { + "lru-cache": "4.1.x", + "tmp": "0.0.x" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "requires": { + "builtins": "^5.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==", + "dev": true, + "requires": { + "esbuild": "^0.17.5", + "fsevents": "~2.3.2", + "postcss": "^8.4.21", + "rollup": "^3.20.2" + } + }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true, + "optional": true, + "peer": true + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "optional": true + }, + "webdriver-js-extender": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", + "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", + "dev": true, + "requires": { + "@types/selenium-webdriver": "^3.0.0", + "selenium-webdriver": "^3.0.1" + } + }, + "webdriver-manager": { + "version": "12.1.9", + "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.9.tgz", + "integrity": "sha512-Yl113uKm8z4m/KMUVWHq1Sjtla2uxEBtx2Ue3AmIlnlPAKloDn/Lvmy6pqWCUersVISpdMeVpAaGbNnvMuT2LQ==", + "dev": true, + "requires": { + "adm-zip": "^0.5.2", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.87.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0", + "xml2js": "^0.4.17" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "webextension-polyfill": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.8.0.tgz", + "integrity": "sha512-a19+DzlT6Kp9/UI+mF9XQopeZ+n2ussjhxHJ4/pmIGge9ijCDz7Gn93mNnjpZAk95T4Tae8iHZ6sSf869txqiQ==", + "dev": true + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "webpack": { + "version": "5.80.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", + "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.13.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-bundle-analyzer": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz", + "integrity": "sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "ws": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", + "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "dev": true, + "requires": {} + } + } + }, + "webpack-dev-middleware": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.0.2.tgz", + "integrity": "sha512-iOddiJzPcQC6lwOIu60vscbGWth8PCRcWRCwoQcTQf9RMoOWBHg5EyzpGdtSmGMrSPd5vHEfFXmVErQEmkRngQ==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.12", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + } + }, + "webpack-dev-server": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz", + "integrity": "sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==", + "dev": true, + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "dependencies": { + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "requires": {} + } + } + }, + "webpack-ext-reloader": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/webpack-ext-reloader/-/webpack-ext-reloader-1.1.9.tgz", + "integrity": "sha512-6AVXGrjcVHKtIQn4yGGghJpiIV2h9F7hNKLsh1oP8m+d6H3QLF3jTNu3vNdKu/8Lab3J/gwb7Bm7tjZLa+DS6g==", + "dev": true, + "requires": { + "@types/webextension-polyfill": "^0.8.2", + "@types/webpack": "^5.28.0", + "@types/webpack-sources": "^3.2.0", + "clean-webpack-plugin": "^4.0.0", + "colors": "^1.4.0", + "cross-env": "^7.0.3", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "useragent": "^2.3.0", + "webextension-polyfill": "^0.8.0", + "webpack-sources": "^3.2.3", + "ws": "^8.4.2" + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "requires": { + "typed-assert": "^1.0.8" + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" + }, + "whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "requires": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "which-typed-array": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "requires": {} + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true + }, + "xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "dev": true, + "requires": { + "cuint": "^0.2.2" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + } + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "zip-a-folder": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/zip-a-folder/-/zip-a-folder-1.1.5.tgz", + "integrity": "sha512-w6I4mvWc6D0Q4pdzCSFbQih/ezYBdjwGZVbWRRFMOYcOdtE9TONZ7YtXCPnHj4XJQmXQxTOWcRGnPYxRn+d0mw==", + "dev": true, + "requires": { + "archiver": "^5.3.1" + } + }, + "zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dev": true, + "requires": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + } + }, + "zone.js": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.0.tgz", + "integrity": "sha512-7m3hNNyswsdoDobCkYNAy5WiUulkMd3+fWaGT9ij6iq3Zr/IwJo4RMCYPSDjT+r7tnPErmY9sZpKhWQ8S5k6XQ==", + "requires": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/desktop/angular/package.json b/desktop/angular/package.json new file mode 100644 index 00000000..4131e18f --- /dev/null +++ b/desktop/angular/package.json @@ -0,0 +1,105 @@ +{ + "name": "portmaster", + "version": "0.8.3", + "scripts": { + "ng": "ng", + "start": "npm install && npm run build-libs:dev && ng serve --proxy-config ./proxy.json", + "build-libs": "NODE_ENV=production ng build --configuration production @safing/ui && NODE_ENV=production ng build --configuration production @safing/portmaster-api", + "build-libs:dev": "ng build --configuration development @safing/ui && ng build --configuration development @safing/portmaster-api", + "serve": "npm run build-libs:dev && ng serve --proxy-config ./proxy.json", + "build:dev": "npm run build-libs:dev && ng build", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e", + "zip-dist": "node pack.js", + "chrome-extension": "NODE_ENV=production ng build --configuration production portmaster-chrome-extension", + "chrome-extension:dev": "ng build --configuration development portmaster-chrome-extension --watch", + "build": "npm run build-libs && NODE_ENV=production ng build --configuration production --base-href /ui/modules/portmaster/", + "build-tauri": "npm run build-libs && NODE_ENV=production ng build --configuration production", + "serve-tauri-builtin": "ng serve tauri-builtin --port 4100", + "serve-app": "ng serve --port 4200 --proxy-config ./proxy.json", + "tauri-dev": "npm install && run-s build-libs:dev && run-p serve-app serve-tauri-builtin" + }, + "private": true, + "dependencies": { + "@angular/animations": "^16.0.1", + "@angular/cdk": "^16.0.1", + "@angular/common": "^16.0.1", + "@angular/compiler": "^16.0.1", + "@angular/core": "^16.0.1", + "@angular/forms": "^16.0.1", + "@angular/localize": "^16.0.1", + "@angular/platform-browser": "^16.0.1", + "@angular/platform-browser-dynamic": "^16.0.1", + "@angular/router": "^16.0.1", + "@fortawesome/angular-fontawesome": "^0.13.0", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@tauri-apps/api": "^2.0.0-beta.3", + "@tauri-apps/plugin-cli": "^2.0.0-beta.1", + "@tauri-apps/plugin-clipboard-manager": "^2.0.0-alpha.4", + "@tauri-apps/plugin-dialog": "^2.0.0-alpha.4", + "@tauri-apps/plugin-notification": "^2.0.0-alpha.4", + "@tauri-apps/plugin-os": "^2.0.0-alpha.5", + "@tauri-apps/plugin-shell": "^2.0.0-alpha.4", + "autoprefixer": "^10.4.14", + "d3": "^7.8.4", + "data-urls": "^5.0.0", + "emoji-toolkit": "^7.0.1", + "fuse.js": "^6.6.2", + "ng-zorro-antd": "^16.1.0", + "ngx-markdown": "^16.0.0", + "postcss": "^8.4.23", + "prismjs": "^1.29.0", + "psl": "^1.9.0", + "rxjs": "~7.8.1", + "topojson-client": "^3.1.0", + "topojson-simplify": "^3.0.3", + "tslib": "^2.5.0", + "whatwg-encoding": "^3.1.1", + "zone.js": "^0.13.0" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "^16.0.0-beta.1", + "@angular-devkit/build-angular": "^16.0.1", + "@angular-eslint/builder": "16.0.1", + "@angular-eslint/eslint-plugin": "16.0.1", + "@angular-eslint/eslint-plugin-template": "16.0.1", + "@angular-eslint/schematics": "16.0.1", + "@angular-eslint/template-parser": "16.0.1", + "@angular/cli": "^16.0.1", + "@angular/compiler-cli": "^16.0.1", + "@fullhuman/postcss-purgecss": "^5.0.0", + "@types/chrome": "^0.0.236", + "@types/d3": "^7.4.0", + "@types/data-urls": "^3.0.4", + "@types/jasmine": "^4.3.1", + "@types/jasminewd2": "~2.0.10", + "@types/node": "^20.1.5", + "@types/psl": "^1.1.0", + "@types/topojson-client": "^3.1.1", + "@types/topojson-simplify": "^3.0.1", + "@types/whatwg-encoding": "^2.0.3", + "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/parser": "^5.59.6", + "eslint": "^8.40.0", + "jasmine-core": "^5.0.0", + "jasmine-spec-reporter": "^7.0.0", + "js-yaml-loader": "^1.2.2", + "ng-packagr": "^16.0.1", + "npm-run-all": "^4.1.5", + "postcss-import": "^15.1.0", + "postcss-loader": "^7.3.0", + "postcss-scss": "^4.0.6", + "protractor": "~7.0.0", + "tailwindcss": "^3.3.2", + "ts-node": "^10.9.1", + "tslint": "~6.1.0", + "typescript": "4.9", + "webpack-bundle-analyzer": "^4.8.0", + "webpack-ext-reloader": "^1.1.9", + "zip-a-folder": "^1.1.5" + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/karma.conf.js b/desktop/angular/projects/portmaster-chrome-extension/karma.conf.js new file mode 100644 index 00000000..eaac9a49 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, '../../coverage/portmaster-chrome-extension'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/app-routing.module.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/app-routing.module.ts new file mode 100644 index 00000000..73a41af9 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/app-routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ExtDomainListComponent } from './domain-list'; +import { IntroComponent } from './welcome/intro.component'; + +const routes: Routes = [ + { path: '', pathMatch: 'full', component: ExtDomainListComponent }, + { path: 'authorize', pathMatch: 'prefix', component: IntroComponent } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.html b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.html new file mode 100644 index 00000000..d1b1eb54 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.html @@ -0,0 +1,3 @@ + + + diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.scss b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.scss new file mode 100644 index 00000000..b25b9d22 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.scss @@ -0,0 +1,3 @@ +:host { + @apply bg-background text-white flex flex-col w-96 h-96; +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.ts new file mode 100644 index 00000000..e8d9a987 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.component.ts @@ -0,0 +1,54 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { MetaAPI, MyProfileResponse, retryPipeline } from '@safing/portmaster-api'; +import { catchError, filter, throwError } from 'rxjs'; + + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], +}) +export class AppComponent implements OnInit { + isAuthorizeView = false; + + constructor( + private metaapi: MetaAPI, + private router: Router, + ) { } + + profile: MyProfileResponse | null = null; + + ngOnInit(): void { + this.router.events + .pipe( + filter(event => event instanceof NavigationEnd) + ) + .subscribe(event => { + if (event instanceof NavigationEnd) { + this.isAuthorizeView = event.url.includes("/authorize") + } + }) + + this.metaapi.myProfile() + .pipe( + catchError(err => { + if (err instanceof HttpErrorResponse && err.status === 403) { + this.router.navigate(['/authorize']) + } + + return throwError(() => err) + }), + retryPipeline() + ) + .subscribe({ + next: profile => { + this.profile = profile; + + console.log(this.profile); + } + }) + } + +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/app.module.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.module.ts new file mode 100644 index 00000000..93c418a3 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/app.module.ts @@ -0,0 +1,39 @@ +import { OverlayModule } from '@angular/cdk/overlay'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { PortmasterAPIModule } from '@safing/portmaster-api'; +import { TabModule } from '@safing/ui'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { ExtDomainListComponent } from './domain-list'; +import { ExtHeaderComponent } from './header'; +import { AuthIntercepter as AuthInterceptor } from './interceptor'; +import { WelcomeModule } from './welcome'; + + +@NgModule({ + declarations: [ + AppComponent, + ExtDomainListComponent, + ExtHeaderComponent, + ], + imports: [ + BrowserModule, + AppRoutingModule, + HttpClientModule, + PortmasterAPIModule.forRoot(), + TabModule, + WelcomeModule, + OverlayModule, + ], + providers: [ + { + provide: HTTP_INTERCEPTORS, + multi: true, + useClass: AuthInterceptor, + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/domain-list.component.html b/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/domain-list.component.html new file mode 100644 index 00000000..44bc3f02 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/domain-list.component.html @@ -0,0 +1,27 @@ +
    +
  • +
    + + + + + + + + + + {{ req.domain }} + +
    + + + {{ req.lastConn.extra_data?.reason?.Msg }} + +
  • +
diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/domain-list.component.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/domain-list.component.ts new file mode 100644 index 00000000..b0e78d45 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/domain-list.component.ts @@ -0,0 +1,129 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { Netquery, NetqueryConnection } from "@safing/portmaster-api"; +import { ListRequests, NotifyRequests } from "../../background/commands"; +import { Request } from '../../background/tab-tracker'; + +interface DomainRequests { + domain: string; + requests: Request[]; + latestIsBlocked: boolean; + lastConn?: NetqueryConnection; +} + +@Component({ + selector: 'ext-domain-list', + templateUrl: './domain-list.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + :host { + @apply flex flex-grow flex-col overflow-auto; + } + ` + ] +}) +export class ExtDomainListComponent implements OnInit { + requests: DomainRequests[] = []; + + constructor( + private netquery: Netquery, + private cdr: ChangeDetectorRef + ) { } + + ngOnInit() { + // setup listening for requests sent from our background script + const self = this; + chrome.runtime.onMessage.addListener((msg: NotifyRequests) => { + if (typeof msg !== 'object') { + console.error('Received invalid message from background script') + + return; + } + + console.log(`DEBUG: received command ${msg.type} from background script`) + + switch (msg.type) { + case 'notifyRequests': + self.updateRequests(msg.requests); + break; + + default: + console.error('Received unknown command from background script') + } + }) + + this.loadRequests(); + } + + updateRequests(req: Request[]) { + let m = new Map(); + + this.requests.forEach(obj => { + obj.requests = []; + m.set(obj.domain, obj); + }); + + req.forEach(r => { + let obj = m.get(r.domain); + if (!obj) { + obj = { + domain: r.domain, + requests: [], + latestIsBlocked: false + } + m.set(r.domain, obj) + } + + obj.requests.push(r); + }) + + this.requests = []; + Array.from(m.keys()).sort() + .map(key => m.get(key)!) + .forEach(obj => { + this.requests.push(obj) + + this.netquery.query({ + query: { + domain: obj.domain, + }, + orderBy: [ + { + field: 'started', + desc: true, + } + ], + page: 0, + pageSize: 1, + }) + .subscribe(result => { + if (!result[0]) { + return; + } + + obj.latestIsBlocked = !result[0].allowed; + obj.lastConn = result[0] as NetqueryConnection; + }) + }) + + this.cdr.detectChanges(); + } + + private loadRequests() { + const cmd: ListRequests = { + type: 'listRequests', + tabId: 'current' + } + + const self = this; + chrome.runtime.sendMessage(cmd, (response: any) => { + if (Array.isArray(response)) { + self.updateRequests(response) + + return; + } + + console.error(response); + }) + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/index.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/index.ts new file mode 100644 index 00000000..c0b4110c --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/domain-list/index.ts @@ -0,0 +1 @@ +export * from './domain-list.component'; diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.html b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.html new file mode 100644 index 00000000..e61fb0e7 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.html @@ -0,0 +1,22 @@ +
+ + + + + + + + + + + + Secure + +
diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.scss b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.scss new file mode 100644 index 00000000..5c958f4e --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.scss @@ -0,0 +1,29 @@ +svg { + transform: scale(0.95); + + path { + top: 0px; + left: 0px; + transform-origin: center center; + } + + .shield-one { + transform: scale(.62); + } + + .shield-two { + animation-delay: -1.2s; + opacity: .6; + transform: scale(.8); + } + + .shield-three { + animation-delay: -2.5s; + opacity: .4; + transform: scale(1); + } + + .shield-ok { + transform: scale(.62); + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.ts new file mode 100644 index 00000000..3712f321 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/header.component.ts @@ -0,0 +1,9 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; + +@Component({ + selector: 'ext-header', + templateUrl: './header.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['./header.component.scss'] +}) +export class ExtHeaderComponent { } diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/header/index.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/index.ts new file mode 100644 index 00000000..be62c26c --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/header/index.ts @@ -0,0 +1 @@ +export * from './header.component'; diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/interceptor.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/interceptor.ts new file mode 100644 index 00000000..a33e1d04 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/interceptor.ts @@ -0,0 +1,45 @@ +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { BehaviorSubject, filter, Observable, switchMap } from "rxjs"; + + +@Injectable() +export class AuthIntercepter implements HttpInterceptor { + /** Used to delay requests until we loaded the access token from the extension storage. */ + private loaded$ = new BehaviorSubject(false); + + /** Holds the access token required to talk to the Portmaster API. */ + private token: string | null = null; + + constructor() { + // make sure we use the new access token once we get one. + chrome.storage.onChanged.addListener(changes => { + this.token = changes['key'].newValue || null; + }) + + // try to read the current access token from the extension storage. + chrome.storage.local.get('key', obj => { + this.token = obj.key || null; + console.log("got token", this.token) + this.loaded$.next(true); + }) + + chrome.runtime.sendMessage({ type: 'listRequests', tabId: 'current' }, (response: any) => { + console.log(response); + }) + } + + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return this.loaded$.pipe( + filter(loaded => loaded), + switchMap(() => { + if (!!this.token) { + req = req.clone({ + headers: req.headers.set("Authorization", "Bearer " + this.token) + }) + } + return next.handle(req) + }) + ) + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/request-interceptor.service.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/request-interceptor.service.ts new file mode 100644 index 00000000..159a5ea5 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/request-interceptor.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs"; + + + +@Injectable({ + providedIn: 'root' +}) +export class RequestInterceptorService { + /** Used to emit when a new URL was requested */ + private onUrlRequested$ = new Subject(); + + /** Used to emit when a URL has likely been blocked by the portmaster */ + private onUrlBlocked$ = new Subject(); + + /** Emits when a new URL was requested */ + get onUrlRequested() { + return this.onUrlRequested$.asObservable(); + } + + /** Emits when a new URL was likely blocked by the portmaster */ + get onUrlBlocked() { + return this.onUrlBlocked$.asObservable(); + } + + constructor() { + this.registerCallbacks() + } + + private registerCallbacks() { + const filter = { + urls: [ + "http://*/*", + "https://*/*", + ] + }; + + chrome.webRequest.onBeforeRequest.addListener(details => this.onUrlRequested$.next(details), filter) + chrome.webRequest.onErrorOccurred.addListener(details => { + if (details.error !== "net::ERR_ADDRESS_UNREACHABLE") { + // we don't care about errors other than UNREACHABLE because that's error caused + // by the portmaster. + return; + } + + this.onUrlBlocked$.next(details); + }, filter) + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/index.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/index.ts new file mode 100644 index 00000000..a695cb02 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/index.ts @@ -0,0 +1,2 @@ +export * from './welcome.module'; + diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/intro.component.html b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/intro.component.html new file mode 100644 index 00000000..017da699 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/intro.component.html @@ -0,0 +1,48 @@ +
+ +

+ + + + + + + + + + + Welcome to the + + Portmaster Browser Extension + + +

+
+
+ + + This extension adds direct support for Portmaster to your Browser. For that, it needs to get access to the + Portmaster on your system. For security reasons, you first need to authorize the Browser Extension to talk to the + Portmaster. + + + + + +

Waiting for Authorization

+ + Please open the Portmaster and approve the authorization request. + +
+ + +
diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/intro.component.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/intro.component.ts new file mode 100644 index 00000000..45d6b3d9 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/intro.component.ts @@ -0,0 +1,44 @@ +import { Component } from "@angular/core"; +import { Router } from "@angular/router"; +import { MetaAPI } from "@safing/portmaster-api"; +import { Subject, takeUntil } from "rxjs"; + +@Component({ + templateUrl: './intro.component.html', + styles: [ + ` + :host { + @apply flex flex-col h-full; + } + ` + ] +}) +export class IntroComponent { + private cancelRequest$ = new Subject(); + + state: 'authorizing' | 'failed' | '' = ''; + + constructor( + private meta: MetaAPI, + private router: Router, + ) { } + + authorizeExtension() { + // cancel any pending request + this.cancelRequest$.next(); + + this.state = 'authorizing'; + this.meta.requestApplicationAccess("Portmaster Browser Extension") + .pipe(takeUntil(this.cancelRequest$)) + .subscribe({ + next: token => { + chrome.storage.local.set(token); + console.log(token); + this.router.navigate(['/']) + }, + error: err => { + this.state = 'failed'; + } + }) + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/welcome.module.ts b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/welcome.module.ts new file mode 100644 index 00000000..a0de7207 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/app/welcome/welcome.module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { OverlayStepperModule } from "@safing/ui"; +import { IntroComponent } from "./intro.component"; + +@NgModule({ + imports: [ + CommonModule, + OverlayStepperModule, + ], + declarations: [ + IntroComponent, + ], + exports: [ + IntroComponent, + ] +}) +export class WelcomeModule { } + diff --git a/assets/.gitkeep b/desktop/angular/projects/portmaster-chrome-extension/src/assets/.gitkeep similarity index 100% rename from assets/.gitkeep rename to desktop/angular/projects/portmaster-chrome-extension/src/assets/.gitkeep diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/assets/icon_128.png b/desktop/angular/projects/portmaster-chrome-extension/src/assets/icon_128.png new file mode 100644 index 00000000..063948f1 Binary files /dev/null and b/desktop/angular/projects/portmaster-chrome-extension/src/assets/icon_128.png differ diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/background.ts b/desktop/angular/projects/portmaster-chrome-extension/src/background.ts new file mode 100644 index 00000000..e6a0986c --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/background.ts @@ -0,0 +1,133 @@ +import { debounceTime, Subject } from "rxjs"; +import { CallRequest, ListRequests, NotifyRequests } from "./background/commands"; +import { Request, TabTracker } from "./background/tab-tracker"; +import { getCurrentTab } from "./background/tab-utils"; + +export class BackgroundService { + /** a lookup map for tab trackers by tab-id */ + private trackers = new Map(); + + /** used to signal the pop-up that new requests arrived */ + private notifyRequests = new Subject(); + + constructor() { + // register a navigation-completed listener. This is fired when the user switches to a new website + // by entering it in the browser address bar. + chrome.webNavigation.onCompleted.addListener((details) => { + console.log("event: webNavigation.onCompleted", details); + }) + + // request event listeners for new requests and errors that occured for them. + // We only care about http and https here. + const filter = { + urls: [ + 'http://*/*', + 'https://*/*' + ] + } + chrome.webRequest.onBeforeRequest.addListener(details => this.handleOnBeforeRequest(details), filter) + chrome.webRequest.onErrorOccurred.addListener(details => this.handleOnErrorOccured(details), filter) + + // make sure we can communicate with the extension popup + chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => this.handleMessage(msg, sender, sendResponse)) + + // set-up signalling of new requests to the pop-up + this.notifyRequests + .pipe(debounceTime(500)) + .subscribe(async () => { + const currentTab = await getCurrentTab(); + if (!!currentTab && !!currentTab.id) { + const msg: NotifyRequests = { + type: 'notifyRequests', + requests: this.mustGetTab({ tabId: currentTab.id }).allRequests() + } + + chrome.runtime.sendMessage(msg) + } + }) + } + + /** Callback for messages sent by the popup */ + private handleMessage(msg: CallRequest, sender: chrome.runtime.MessageSender, sendResponse: (msg: any) => void) { + console.log(`DEBUG: got message from ${sender.origin} (tab=${sender.tab?.id})`) + + if (typeof msg !== 'object') { + console.error(`Received invalid message from popup`, msg) + + return; + } + + let response: Promise; + switch (msg.type) { + case 'listRequests': + response = this.handleListRequests(msg) + break; + + default: + response = Promise.reject("unknown command") + } + + response + .then(res => { + console.log(`DEBUG: sending response for command ${msg.type}`, res) + sendResponse(res); + }) + .catch(err => { + console.error(`Failed to handle command ${msg.type}`, err) + sendResponse({ + type: 'error', + details: err + }); + }) + } + + /** Returns a list of all observed requests based on the filter in msg. */ + private async handleListRequests(msg: ListRequests): Promise { + if (msg.tabId === 'current') { + const currentID = (await getCurrentTab()).id + if (!currentID) { + return []; + } + + msg.tabId = currentID; + } + + const tracker = this.mustGetTab({ tabId: msg.tabId as number }) + + if (!!msg.domain) { + return tracker.forDomain(msg.domain) + } + + return tracker.allRequests() + } + + /** Callback for chrome.webRequest.onBeforeRequest */ + private handleOnBeforeRequest(details: chrome.webRequest.WebRequestDetails) { + this.mustGetTab(details).trackRequest(details) + + this.notifyRequests.next(); + } + + /** Callback for chrome.webRequest.onErrorOccured */ + private handleOnErrorOccured(details: chrome.webRequest.WebResponseErrorDetails) { + this.mustGetTab(details).trackError(details); + + this.notifyRequests.next(); + } + + /** Returns the tab-tracker for tabId. Creates a new tracker if none exists. */ + private mustGetTab({ tabId }: { tabId: number }): TabTracker { + let tracker = this.trackers.get(tabId); + if (!tracker) { + tracker = new TabTracker(tabId) + this.trackers.set(tabId, tracker) + } + + return tracker; + } +} + +/** start the background service once we got successfully installed. */ +chrome.runtime.onInstalled.addListener(() => { + new BackgroundService() +}); diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/background/commands.ts b/desktop/angular/projects/portmaster-chrome-extension/src/background/commands.ts new file mode 100644 index 00000000..6bfdcd88 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/background/commands.ts @@ -0,0 +1,14 @@ +import { Request } from "./tab-tracker"; + +export interface ListRequests { + type: 'listRequests'; + domain?: string; + tabId: number | 'current'; +} + +export interface NotifyRequests { + type: 'notifyRequests', + requests: Request[]; +} + +export type CallRequest = ListRequests; diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/background/tab-tracker.ts b/desktop/angular/projects/portmaster-chrome-extension/src/background/tab-tracker.ts new file mode 100644 index 00000000..f5a0628e --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/background/tab-tracker.ts @@ -0,0 +1,126 @@ +import { deepClone } from "@safing/portmaster-api"; + +export interface Request { + /** The ID assigned by the browser */ + id: string; + + /** The domain this request was for */ + domain: string; + + /** The timestamp in milliseconds since epoch at which the request was initiated */ + time: number; + + /** Whether or not this request errored with net::ERR_ADDRESS_UNREACHABLE */ + isUnreachable: boolean; +} + +/** + * TabTracker tracks requests to domains made by a single browser tab. + */ +export class TabTracker { + /** A list of requests observed for this tab order by time they have been initiated */ + private requests: Request[] = []; + + /** A lookup map for requests to specific domains */ + private byDomain = new Map(); + + /** A lookup map for requests by the chrome request ID */ + private byRequestId = new Map; + + constructor(public readonly tabId: number) { } + + /** Returns an array of all requests observed in this tab. */ + allRequests(): Request[] { + return deepClone(this.requests) + } + + /** Returns a list of requests that have been observed for domain */ + forDomain(domain: string): Request[] { + if (!domain.endsWith(".")) { + domain += "." + } + + return this.byDomain.get(domain) || []; + } + + /** Call to add the details of a web-request to this tab-tracker */ + trackRequest(details: chrome.webRequest.WebRequestDetails) { + // If this is the wrong tab ID ignore the request details + if (details.tabId !== this.tabId) { + console.error(`TabTracker.trackRequest: called with wrong tab ID. Expected ${this.tabId} but got ${details.tabId}`) + + return; + } + + // if the type of the request is for the main_frame the user switched to a new website. + // In that case, we can wipe out all currently stored requests as the user will likely not + // care anymore. + if (details.type === "main_frame") { + this.clearState(); + } + + // get the domain of the request normalized to contain the trailing dot. + let domain = new URL(details.url).host; + if (!domain.endsWith(".")) { + domain += "." + } + + const req: Request = { + id: details.requestId, + domain: domain, + time: details.timeStamp, + isUnreachable: false, // we don't actually know that yet + } + + this.requests.push(req); + this.byRequestId.set(req.id, req) + + // Add the request to the by-domain lookup map + let byDomainRequests = this.byDomain.get(req.domain); + if (!byDomainRequests) { + byDomainRequests = []; + this.byDomain.set(req.domain, byDomainRequests) + } + byDomainRequests.push(req) + + console.log(`DEBUG: observed request ${req.id} to ${req.domain}`) + } + + /** Call to notify the tab-tracker of a request error */ + trackError(errorDetails: chrome.webRequest.WebResponseErrorDetails) { + // we only care about net::ERR_ADDRESS_UNREACHABLE here because that's how the + // Portmaster blocks the request. + + // TODO(ppacher): docs say we must not rely on that value so we should figure out a better + // way to detect if the error is caused by the Portmaster. + if (errorDetails.error !== "net::ERR_ADDRESS_UNREACHABLE") { + return; + } + + // the the previsouly observed request by the request ID. + const req = this.byRequestId.get(errorDetails.requestId) + if (!req) { + console.error("TabTracker.trackError: request has not been observed before") + + return + } + + // make sure the error details actually happend for the observed tab. + if (errorDetails.tabId !== this.tabId) { + console.error(`TabTracker.trackRequest: called with wrong tab ID. Expected ${this.tabId} but got ${errorDetails.tabId}`) + + return; + } + + // mark the request as unreachable. + req.isUnreachable = true; + console.log(`DEBUG: marked request ${req.id} to ${req.domain} as unreachable`) + } + + /** Clears the current state of the tab tracker */ + private clearState() { + this.requests = []; + this.byDomain = new Map(); + this.byRequestId = new Map(); + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/background/tab-utils.ts b/desktop/angular/projects/portmaster-chrome-extension/src/background/tab-utils.ts new file mode 100644 index 00000000..36635ca8 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/background/tab-utils.ts @@ -0,0 +1,9 @@ + +/** Queries and returns the currently active tab */ +export function getCurrentTab(): Promise { + return new Promise((resolve) => { + chrome.tabs.query({ active: true, lastFocusedWindow: true }, ([tab]) => { + resolve(tab); + }) + }) +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/environments/environment.prod.ts b/desktop/angular/projects/portmaster-chrome-extension/src/environments/environment.prod.ts new file mode 100644 index 00000000..ffe8aed7 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: false +}; diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/environments/environment.ts b/desktop/angular/projects/portmaster-chrome-extension/src/environments/environment.ts new file mode 100644 index 00000000..f56ff470 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/environments/environment.ts @@ -0,0 +1,16 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/favicon.ico b/desktop/angular/projects/portmaster-chrome-extension/src/favicon.ico new file mode 100644 index 00000000..997406ad Binary files /dev/null and b/desktop/angular/projects/portmaster-chrome-extension/src/favicon.ico differ diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/index.html b/desktop/angular/projects/portmaster-chrome-extension/src/index.html new file mode 100644 index 00000000..afb08c65 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/index.html @@ -0,0 +1,13 @@ + + + + + PortmasterChromeExtension + + + + + + + + diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/main.ts b/desktop/angular/projects/portmaster-chrome-extension/src/main.ts new file mode 100644 index 00000000..c7b673cf --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/manifest.json b/desktop/angular/projects/portmaster-chrome-extension/src/manifest.json new file mode 100644 index 00000000..db045a05 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Portmaster Browser Extension", + "version": "0.1", + "description": "Browser Extension for even better Portmaster integration", + "manifest_version": 2, + "permissions": [ + "activeTab", + "storage", + "webRequest", + "webNavigation", + "*://*/*" + ], + "browser_action": { + "default_popup": "index.html", + "default_icon": { + "128": "assets/icon_128.png" + } + }, + "background": { + "scripts": ["background.js"], + "persistent": true + } +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/polyfills.ts b/desktop/angular/projects/portmaster-chrome-extension/src/polyfills.ts new file mode 100644 index 00000000..429bb9ef --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/polyfills.ts @@ -0,0 +1,53 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes recent versions of Safari, Chrome (including + * Opera), Edge on the desktop, and iOS and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/styles.scss b/desktop/angular/projects/portmaster-chrome-extension/src/styles.scss new file mode 100644 index 00000000..e41283cd --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/styles.scss @@ -0,0 +1,8 @@ +/* You can add global styles to this file, and also import other style files */ + +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; + + +@import '@angular/cdk/overlay-prebuilt'; diff --git a/desktop/angular/projects/portmaster-chrome-extension/src/test.ts b/desktop/angular/projects/portmaster-chrome-extension/src/test.ts new file mode 100644 index 00000000..51bb0206 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/src/test.ts @@ -0,0 +1,14 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), +); diff --git a/desktop/angular/projects/portmaster-chrome-extension/tsconfig.app.json b/desktop/angular/projects/portmaster-chrome-extension/tsconfig.app.json new file mode 100644 index 00000000..28c28154 --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/tsconfig.app.json @@ -0,0 +1,18 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [ + "chrome" + ] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts", + "src/background.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/desktop/angular/projects/portmaster-chrome-extension/tsconfig.spec.json b/desktop/angular/projects/portmaster-chrome-extension/tsconfig.spec.json new file mode 100644 index 00000000..b66a2f0b --- /dev/null +++ b/desktop/angular/projects/portmaster-chrome-extension/tsconfig.spec.json @@ -0,0 +1,18 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/desktop/angular/projects/safing/portmaster-api/README.md b/desktop/angular/projects/safing/portmaster-api/README.md new file mode 100644 index 00000000..fc4110d2 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/README.md @@ -0,0 +1,24 @@ +# PortmasterApi + +This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.0. + +## Code scaffolding + +Run `ng generate component component-name --project portmaster-api` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project portmaster-api`. +> Note: Don't forget to add `--project portmaster-api` or else it will be added to the default project in your `angular.json` file. + +## Build + +Run `ng build portmaster-api` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Publishing + +After building your library with `ng build portmaster-api`, go to the dist folder `cd dist/portmaster-api` and run `npm publish`. + +## Running unit tests + +Run `ng test portmaster-api` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/desktop/angular/projects/safing/portmaster-api/karma.conf.js b/desktop/angular/projects/safing/portmaster-api/karma.conf.js new file mode 100644 index 00000000..6f9bd935 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, '../../../coverage/safing/portmaster-api'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/desktop/angular/projects/safing/portmaster-api/ng-package.json b/desktop/angular/projects/safing/portmaster-api/ng-package.json new file mode 100644 index 00000000..4ea94f9a --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../dist-lib/safing/portmaster-api", + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/package-lock.json b/desktop/angular/projects/safing/portmaster-api/package-lock.json new file mode 100644 index 00000000..848065cc --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/package-lock.json @@ -0,0 +1,132 @@ +{ + "name": "@safing/portmaster-api", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@safing/portmaster-api", + "version": "0.0.1", + "dependencies": { + "tslib": "^2.3.0" + }, + "devDependencies": { + "@types/jasmine": "^4.0.3" + }, + "peerDependencies": { + "@angular/common": "^14.0.0", + "@angular/core": "^14.0.0" + } + }, + "node_modules/@angular/common": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.0.5.tgz", + "integrity": "sha512-YFRPxx3yRLjk0gPL7tm/97mi8+Pjt3q6zWCjrLkAlDjniDvgmKNWIQ1h6crZQR0Cw7yNqK0QoFXQgTw0GJIWLQ==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "@angular/core": "14.0.5", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/core": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.0.5.tgz", + "integrity": "sha512-4MIfFM2nD+N0/Dk8xKfKvbdS/zYRhQgdnKT6ZIIV7Y/XCfn5QAIa4+vB5BEAZpuzSsZHLVdBQQ0TkaiONLfL2Q==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.11.4" + } + }, + "node_modules/@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "node_modules/rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/zone.js": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.6.tgz", + "integrity": "sha512-umJqFtKyZlPli669gB1gOrRE9hxUUGkZr7mo878z+NEBJZZixJkKeVYfnoLa7g25SseUDc92OZrMKKHySyJrFg==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + } + } + }, + "dependencies": { + "@angular/common": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-14.0.5.tgz", + "integrity": "sha512-YFRPxx3yRLjk0gPL7tm/97mi8+Pjt3q6zWCjrLkAlDjniDvgmKNWIQ1h6crZQR0Cw7yNqK0QoFXQgTw0GJIWLQ==", + "peer": true, + "requires": { + "tslib": "^2.3.0" + } + }, + "@angular/core": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-14.0.5.tgz", + "integrity": "sha512-4MIfFM2nD+N0/Dk8xKfKvbdS/zYRhQgdnKT6ZIIV7Y/XCfn5QAIa4+vB5BEAZpuzSsZHLVdBQQ0TkaiONLfL2Q==", + "peer": true, + "requires": { + "tslib": "^2.3.0" + } + }, + "@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "rxjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.6.tgz", + "integrity": "sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==", + "peer": true, + "requires": { + "tslib": "^2.1.0" + } + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "zone.js": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.6.tgz", + "integrity": "sha512-umJqFtKyZlPli669gB1gOrRE9hxUUGkZr7mo878z+NEBJZZixJkKeVYfnoLa7g25SseUDc92OZrMKKHySyJrFg==", + "peer": true, + "requires": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/package.json b/desktop/angular/projects/safing/portmaster-api/package.json new file mode 100644 index 00000000..98483319 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/package.json @@ -0,0 +1,14 @@ +{ + "name": "@safing/portmaster-api", + "version": "0.0.1", + "peerDependencies": { + "@angular/common": "^14.0.0", + "@angular/core": "^14.0.0" + }, + "dependencies": { + "tslib": "^2.3.0" + }, + "devDependencies": { + "@types/jasmine": "^4.0.3" + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/app-profile.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/app-profile.service.ts new file mode 100644 index 00000000..814b67ff --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/app-profile.service.ts @@ -0,0 +1,262 @@ +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { filter, finalize, map, mergeMap, share, take } from 'rxjs/operators'; +import { + AppProfile, + FlatConfigObject, + LayeredProfile, + TagDescription, + flattenProfileConfig, +} from './app-profile.types'; +import { + PORTMASTER_HTTP_API_ENDPOINT, + PortapiService, +} from './portapi.service'; +import { Process } from './portapi.types'; + +@Injectable({ + providedIn: 'root', +}) +export class AppProfileService { + private watchedProfiles = new Map>(); + + constructor( + private portapi: PortapiService, + private http: HttpClient, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string + ) { } + + /** + * Returns the database key of a profile. + * + * @param source The source of the profile. + * @param id The profile ID. + */ + getKey(source: string, id: string): string; + + /** + * Returns the database key of a profile + * + * @param p The app-profile itself.. + */ + getKey(p: AppProfile): string; + + getKey(idOrSourceOrProfile: string | AppProfile, id?: string): string { + if (typeof idOrSourceOrProfile === 'object') { + return this.getKey(idOrSourceOrProfile.Source, idOrSourceOrProfile.ID); + } + + let key = idOrSourceOrProfile; + + if (!!id) { + key = `core:profiles/${idOrSourceOrProfile}/${id}`; + } + + return key; + } + + /** + * Load an application profile. + * + * @param sourceAndId The full profile ID including source + */ + getAppProfile(sourceAndId: string): Observable; + + /** + * Load an application profile. + * + * @param source The source of the profile + * @param id The ID of the profile + */ + getAppProfile(source: string, id: string): Observable; + + getAppProfile( + sourceOrSourceAndID: string, + id?: string + ): Observable { + let source = sourceOrSourceAndID; + if (id !== undefined) { + source += '/' + id; + } + const key = `core:profiles/${source}`; + + if (this.watchedProfiles.has(key)) { + return this.watchedProfiles.get(key)!.pipe(take(1)); + } + + return this.getAppProfileFromKey(key); + } + + setProfileIcon( + content: string | ArrayBuffer, + mimeType: string + ): Observable<{ filename: string }> { + return this.http.post<{ filename: string }>( + `${this.httpAPI}/v1/profile/icon`, + content, + { + headers: new HttpHeaders({ + 'Content-Type': mimeType, + }), + } + ); + } + + /** + * Loads an application profile by it's database key. + * + * @param key The key of the application profile. + */ + getAppProfileFromKey(key: string): Observable { + return this.portapi.get(key); + } + + /** + * Loads the global-configuration profile. + */ + globalConfig(): Observable { + return this.getAppProfile('special', 'global-config').pipe( + map((profile) => flattenProfileConfig(profile.Config)) + ); + } + + /** Returns all possible process tags. */ + tagDescriptions(): Observable { + return this.http + .get<{ Tags: TagDescription[] }>(`${this.httpAPI}/v1/process/tags`) + .pipe(map((result) => result.Tags)); + } + + /** + * Watches an application profile for changes. + * + * @param source The source of the profile + * @param id The ID of the profile + */ + watchAppProfile(sourceAndId: string): Observable; + /** + * Watches an application profile for changes. + * + * @param source The source of the profile + * @param id The ID of the profile + */ + watchAppProfile(source: string, id: string): Observable; + + watchAppProfile(sourceAndId: string, id?: string): Observable { + let key = ''; + + if (id === undefined) { + key = sourceAndId; + if (!key.startsWith('core:profiles/')) { + key = `core:profiles/${key}`; + } + } else { + key = `core:profiles/${sourceAndId}/${id}`; + } + + if (this.watchedProfiles.has(key)) { + return this.watchedProfiles.get(key)!; + } + + const stream = this.portapi.get(key).pipe( + mergeMap(() => this.portapi.watch(key)), + finalize(() => { + console.log( + 'watchAppProfile: removing cached profile stream for ' + key + ); + this.watchedProfiles.delete(key); + }), + share({ + connector: () => new BehaviorSubject(null), + resetOnRefCountZero: true, + }), + filter((profile) => profile !== null) + ) as Observable; + + this.watchedProfiles.set(key, stream); + + return stream; + } + + /** @deprecated use saveProfile instead */ + saveLocalProfile(profile: AppProfile): Observable { + return this.saveProfile(profile); + } + + /** + * Save an application profile. + * + * @param profile The profile to save + */ + saveProfile(profile: AppProfile): Observable { + profile.LastEdited = Math.floor(new Date().getTime() / 1000); + return this.portapi.update( + `core:profiles/${profile.Source}/${profile.ID}`, + profile + ); + } + + /** + * Watch all application profiles + */ + watchProfiles(): Observable { + return this.portapi.watchAll('core:profiles/'); + } + + watchLayeredProfile(source: string, id: string): Observable; + + /** + * Watches the layered runtime profile for a given application + * profile. + * + * @param profile The app profile + */ + watchLayeredProfile(profile: AppProfile): Observable; + + watchLayeredProfile( + profileOrSource: string | AppProfile, + id?: string + ): Observable { + if (typeof profileOrSource == 'object') { + id = profileOrSource.ID; + profileOrSource = profileOrSource.Source; + } + + const key = `runtime:layeredProfile/${profileOrSource}/${id}`; + return this.portapi.watch(key); + } + + /** + * Loads the layered runtime profile for a given application + * profile. + * + * @param profile The app profile + */ + getLayeredProfile(profile: AppProfile): Observable { + const key = `runtime:layeredProfile/${profile.Source}/${profile.ID}`; + return this.portapi.get(key); + } + + /** + * Delete an application profile. + * + * @param profile The profile to delete + */ + deleteProfile(profile: AppProfile): Observable { + return this.portapi.delete(`core:profiles/${profile.Source}/${profile.ID}`); + } + + getProcessesByProfile(profileOrId: AppProfile | string): Observable { + if (typeof profileOrId === 'object') { + profileOrId = profileOrId.Source + "/" + profileOrId.ID + } + + return this.http.get(`${this.httpAPI}/v1/process/list/by-profile/${profileOrId}`) + } + + getProcessByPid(pid: number): Observable { + return this.http.get(`${this.httpAPI}/v1/process/group-leader/${pid}`) + } +} + diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/app-profile.types.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/app-profile.types.ts new file mode 100644 index 00000000..986d62ff --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/app-profile.types.ts @@ -0,0 +1,215 @@ +import { BaseSetting, OptionValueType, SettingValueType } from './config.types'; +import { SecurityLevel } from './core.types'; +import { Record } from './portapi.types'; + +export interface ConfigMap { + [key: string]: ConfigObject; +} + +export type ConfigObject = OptionValueType | ConfigMap; + +export interface FlatConfigObject { + [key: string]: OptionValueType; +} + + +export interface LayeredProfile extends Record { + // LayerIDs is a list of all profiles that are used + // by this layered profile. Profiles are evaluated in + // order. + LayerIDs: string[]; + + // The current revision counter of the layered profile. + RevisionCounter: number; +} + +export enum FingerprintType { + Tag = 'tag', + Cmdline = 'cmdline', + Env = 'env', + Path = 'path', +} + +export enum FingerpringOperation { + Equal = 'equals', + Prefix = 'prefix', + Regex = 'regex', +} + +export interface Fingerprint { + Type: FingerprintType; + Key: string; + Operation: FingerpringOperation; + Value: string; +} + +export interface TagDescription { + ID: string; + Name: string; + Description: string; +} + +export interface Icon { + Type: 'database' | 'path' | 'api'; + Source: '' | 'user' | 'import' | 'core' | 'ui'; + Value: string; +} + +export interface AppProfile extends Record { + ID: string; + LinkedPath: string; // deprecated + PresentationPath: string; + Fingerprints: Fingerprint[]; + Created: number; + LastEdited: number; + Config?: ConfigMap; + Description: string; + Warning: string; + WarningLastUpdated: string; + Homepage: string; + Icons: Icon[]; + Name: string; + Internal: boolean; + SecurityLevel: SecurityLevel; + Source: 'local'; +} + +// flattenProfileConfig returns a flat version of a nested ConfigMap where each property +// can be used as the database key for the associated setting. +export function flattenProfileConfig( + p?: ConfigMap, + prefix = '' +): FlatConfigObject { + if (p === null || p === undefined) { + return {} + } + + let result: FlatConfigObject = {}; + + Object.keys(p).forEach((key) => { + const childPrefix = prefix === '' ? key : `${prefix}/${key}`; + + const prop = p[key]; + + if (isConfigMap(prop)) { + const flattened = flattenProfileConfig(prop, childPrefix); + result = mergeObjects(result, flattened); + return; + } + + result[childPrefix] = prop; + }); + + return result; +} + +/** + * Returns the current value (or null) of a setting stored in a config + * map by path. + * + * @param obj The ConfigMap object + * @param path The path of the setting separated by foward slashes. + */ +export function getAppSetting( + obj: ConfigMap | null | undefined, + path: string +): T | null { + if (obj === null || obj === undefined) { + return null + } + + const parts = path.split('/'); + + let iter = obj; + for (let idx = 0; idx < parts.length; idx++) { + const propName = parts[idx]; + + if (iter[propName] === undefined) { + return null; + } + + const value = iter[propName]; + if (idx === parts.length - 1) { + return value as T; + } + + if (!isConfigMap(value)) { + return null; + } + + iter = value; + } + return null; +} + +export function getActualValue>( + s: S +): SettingValueType { + if (s.Value !== undefined) { + return s.Value; + } + if (s.GlobalDefault !== undefined) { + return s.GlobalDefault; + } + return s.DefaultValue; +} + +/** + * Sets the value of a settings inside the nested config object. + * + * @param obj THe config object + * @param path The path of the setting + * @param value The new value to set. + */ +export function setAppSetting(obj: ConfigObject, path: string, value: any) { + const parts = path.split('/'); + if (typeof obj !== 'object' || Array.isArray(obj)) { + return; + } + + let iter = obj; + for (let idx = 0; idx < parts.length; idx++) { + const propName = parts[idx]; + + if (idx === parts.length - 1) { + if (value === undefined) { + delete iter[propName]; + } else { + iter[propName] = value; + } + return; + } + + if (iter[propName] === undefined) { + iter[propName] = {}; + } + + iter = iter[propName] as ConfigMap; + } +} + +/** Typeguard to ensure v is a ConfigMap */ +function isConfigMap(v: any): v is ConfigMap { + return typeof v === 'object' && !Array.isArray(v); +} + +/** + * Returns a new flat-config object that contains values from both + * parameters. + * + * @param a The first config object + * @param b The second config object + */ +function mergeObjects( + a: FlatConfigObject, + b: FlatConfigObject +): FlatConfigObject { + var res: FlatConfigObject = {}; + Object.keys(a).forEach((key) => { + res[key] = a[key]; + }); + Object.keys(b).forEach((key) => { + res[key] = b[key]; + }); + return res; +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/config.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/config.service.ts new file mode 100644 index 00000000..58daeb28 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/config.service.ts @@ -0,0 +1,128 @@ +import { Injectable, TrackByFunction } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { distinctUntilChanged, filter, map, share, toArray } from 'rxjs/operators'; +import { BaseSetting, BoolSetting, OptionType, Setting, SettingValueType } from './config.types'; +import { PortapiService } from './portapi.service'; + + +@Injectable({ + providedIn: 'root' +}) +export class ConfigService { + networkRatingEnabled$: Observable; + + /** + * A {@link TrackByFunction} for tracking settings. + */ + static trackBy: TrackByFunction = (_: number, obj: Setting) => obj.Name; + readonly trackBy = ConfigService.trackBy; + + /** configPrefix is the database key prefix for the config db */ + readonly configPrefix = "config:"; + + constructor(private portapi: PortapiService) { + this.networkRatingEnabled$ = this.watch("core/enableNetworkRating") + .pipe( + share({ connector: () => new BehaviorSubject(false) }), + ) + } + + /** + * Loads a configuration setting from the database. + * + * @param key The key of the configuration setting. + */ + get(key: string): Observable { + return this.portapi.get(this.configPrefix + key); + } + + /** + * Returns all configuration settings that match query. Note that in + * contrast to {@link PortAPI} settings values are collected into + * an array before being emitted. This allows simple usage in *ngFor + * and friends. + * + * @param query The query used to search for configuration settings. + */ + query(query: string): Observable { + return this.portapi.query(this.configPrefix + query) + .pipe( + map(setting => setting.data), + toArray() + ); + } + + /** + * Save a setting. + * + * @param s The setting to save. Note that the new value should already be set to {@property Value}. + */ + save(s: Setting): Observable; + + /** + * Save a setting. + * + * @param key The key of the configuration setting + * @param value The new value of the setting. + */ + save(key: string, value: any): Observable; + + // save is overloaded, see above. + save(s: Setting | string, v?: any): Observable { + if (typeof s === 'string') { + return this.portapi.update(this.configPrefix + s, { + Key: s, + Value: v, + }); + } + return this.portapi.update(this.configPrefix + s.Key, s); + } + + /** + * Watch a configuration setting. + * + * @param key The key of the setting to watch. + */ + watch(key: string): Observable> { + return this.portapi.qsub, any>>(this.configPrefix + key) + .pipe( + filter(value => value.key === this.configPrefix + key), // qsub does a query so filter for our key. + map(value => value.data), + map(value => value.Value !== undefined ? value.Value : value.DefaultValue), + distinctUntilChanged(), + ) + } + + /** + * Tests if a value is valid for a given option. + * + * @param spec The option specification (as returned by get()). + * @param value The value that should be tested. + */ + validate(spec: S, value: SettingValueType) { + if (!spec.ValidationRegex) { + return; + } + + const re = new RegExp(spec.ValidationRegex); + + switch (spec.OptType) { + case OptionType.Int: + case OptionType.Bool: + // todo(ppacher): do we validate that? + return + case OptionType.String: + if (!re.test(value as string)) { + throw new Error(`${value} does not match ${spec.ValidationRegex}`) + } + return; + case OptionType.StringArray: + (value as string[]).forEach(v => { + if (!re.test(v as string)) { + throw new Error(`${value} does not match ${spec.ValidationRegex}`) + } + }); + return + } + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/config.types.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/config.types.ts new file mode 100644 index 00000000..99fe5d82 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/config.types.ts @@ -0,0 +1,348 @@ +import { FeatureID } from './features'; +import { Record } from './portapi.types'; +import { deepClone } from './utils'; + +/** + * ExpertiseLevel defines all available expertise levels. + */ +export enum ExpertiseLevel { + User = 'user', + Expert = 'expert', + Developer = 'developer', +} + +export enum ExpertiseLevelNumber { + user = 0, + expert = 1, + developer = 2 +} + +export function getExpertiseLevelNumber(lvl: ExpertiseLevel): ExpertiseLevelNumber { + switch (lvl) { + case ExpertiseLevel.User: + return ExpertiseLevelNumber.user; + case ExpertiseLevel.Expert: + return ExpertiseLevelNumber.expert; + case ExpertiseLevel.Developer: + return ExpertiseLevelNumber.developer + } +} + +/** + * OptionType defines the type of an option as stored in + * the backend. Note that ExternalOptionHint may be used + * to request a different visual representation and edit + * menu on a per-option basis. + */ +export enum OptionType { + String = 1, + StringArray = 2, + Int = 3, + Bool = 4, +} + +/** + * Converts an option type to it's string representation. + * + * @param opt The option type to convert + */ +export function optionTypeName(opt: OptionType): string { + switch (opt) { + case OptionType.String: + return 'string'; + case OptionType.StringArray: + return '[]string'; + case OptionType.Int: + return 'int' + case OptionType.Bool: + return 'bool' + } +} + +/** The actual type an option value can be */ +export type OptionValueType = string | string[] | number | boolean; + +/** Type-guard for string option types */ +export function isStringType(opt: OptionType, vt: OptionValueType): vt is string { + return opt === OptionType.String; +} + +/** Type-guard for string-array option types */ +export function isStringArrayType(opt: OptionType, vt: OptionValueType): vt is string[] { + return opt === OptionType.StringArray; +} + +/** Type-guard for number option types */ +export function isNumberType(opt: OptionType, vt: OptionValueType): vt is number { + return opt === OptionType.Int; +} + +/** Type-guard for boolean option types */ +export function isBooleanType(opt: OptionType, vt: OptionValueType): vt is boolean { + return opt === OptionType.Bool; +} + +/** + * ReleaseLevel defines the available release and maturity + * levels. + */ +export enum ReleaseLevel { + Stable = 0, + Beta = 1, + Experimental = 2, +} + +export function releaseLevelFromName(name: 'stable' | 'beta' | 'experimental'): ReleaseLevel { + switch (name) { + case 'stable': + return ReleaseLevel.Stable; + case 'beta': + return ReleaseLevel.Beta; + case 'experimental': + return ReleaseLevel.Experimental; + } +} + +/** + * releaseLevelName returns a string representation of the + * release level. + * + * @args level The release level to convert. + */ +export function releaseLevelName(level: ReleaseLevel): string { + switch (level) { + case ReleaseLevel.Stable: + return 'stable' + case ReleaseLevel.Beta: + return 'beta' + case ReleaseLevel.Experimental: + return 'experimental' + } +} + +/** + * ExternalOptionHint tells the UI to use a different visual + * representation and edit menu that the options value would + * imply. + */ +export enum ExternalOptionHint { + SecurityLevel = 'security level', + EndpointList = 'endpoint list', + FilterList = 'filter list', + OneOf = 'one-of', + OrderedList = 'ordered' +} + +/** A list of well-known option annotation keys. */ +export enum WellKnown { + DisplayHint = "safing/portbase:ui:display-hint", + Order = "safing/portbase:ui:order", + Unit = "safing/portbase:ui:unit", + Category = "safing/portbase:ui:category", + Subsystem = "safing/portbase:module:subsystem", + Stackable = "safing/portbase:options:stackable", + QuickSetting = "safing/portbase:ui:quick-setting", + Requires = "safing/portbase:config:requires", + RestartPending = "safing/portbase:options:restart-pending", + EndpointListVerdictNames = "safing/portmaster:ui:endpoint-list:verdict-names", + RequiresFeatureID = "safing/portmaster:ui:config:requires-feature", + RequiresUIReload = "safing/portmaster:ui:requires-reload", +} + +/** + * Annotations describes the annoations object of a configuration + * setting. Well-known annotations are stricktly typed. + */ +export interface Annotations { + // Well known option annoations and their + // types. + [WellKnown.DisplayHint]?: ExternalOptionHint; + [WellKnown.Order]?: number; + [WellKnown.Unit]?: string; + [WellKnown.Category]?: string; + [WellKnown.Subsystem]?: string; + [WellKnown.Stackable]?: true; + [WellKnown.QuickSetting]?: QuickSetting | QuickSetting[] | CountrySelectionQuickSetting | CountrySelectionQuickSetting[]; + [WellKnown.Requires]?: ValueRequirement | ValueRequirement[]; + [WellKnown.RequiresFeatureID]?: FeatureID | FeatureID[]; + [WellKnown.RequiresUIReload]?: unknown, + // Any thing else... + [key: string]: any; +} + +export interface PossilbeValue { + /** Name is the name of the value and should be displayed */ + Name: string; + /** Description may hold an additional description of the value */ + Description: string; + /** Value is the actual value expected by the portmaster */ + Value: T; +} + +export interface QuickSetting { + // Name is the name of the quick setting. + Name: string; + // Value is the value that the quick-setting configures. It must match + // the expected value type of the annotated option. + Value: T; + // Action defines the action of the quick setting. + Action: 'replace' | 'merge-top' | 'merge-bottom'; +} + +export interface CountrySelectionQuickSetting extends QuickSetting { + // Filename of the flag to be used. + // In most cases this will be the 2-letter country code, but there are also special flags. + FlagID: string; +} + +export interface ValueRequirement { + // Key is the configuration key of the required setting. + Key: string; + // Value is the required value of the linked setting. + Value: any; +} + +/** + * BaseSetting describes the general shape of a portbase config setting. + */ +export interface BaseSetting extends Record { + // Value is the value of a setting. + Value?: T; + // DefaultValue is the default value of a setting. + DefaultValue: T; + // Description is a short description. + Description?: string; + // ExpertiseLevel defines the required expertise level for + // this setting to show up. + ExpertiseLevel: ExpertiseLevelNumber; + // Help may contain a longer help text for this option. + Help?: string; + // Key is the database key. + Key: string; + // Name is the name of the option. + Name: string; + // OptType is the option's basic type. + OptType: O; + // Annotations holds option specific annotations. + Annotations: Annotations; + // ReleaseLevel defines the release level of the feature + // or settings changed by this option. + ReleaseLevel: ReleaseLevel; + // RequiresRestart may be set to true if the service requires + // a restart after this option has been changed. + RequiresRestart?: boolean; + // ValidateRegex defines the regex used to validate this option. + // The regex is used in Golang but is expected to be valid in + // JavaScript as well. + ValidationRegex?: string; + PossibleValues?: PossilbeValue[]; + + // GlobalDefault holds the global default value and is used in the app settings + // This property is NOT defined inside the portmaster! + GlobalDefault?: T; +} + +export type IntSetting = BaseSetting; +export type StringSetting = BaseSetting; +export type StringArraySetting = BaseSetting; +export type BoolSetting = BaseSetting; + +/** + * Apply a quick setting to a value. + * + * @param current The current value of the setting. + * @param qs The quick setting to apply. + */ +export function applyQuickSetting(current: V | null, qs: QuickSetting): V | null { + if (qs.Action === 'replace' || !qs.Action) { + return deepClone(qs.Value); + } + + if ((!Array.isArray(current) && current !== null) || !Array.isArray(qs.Value)) { + console.warn(`Tried to ${qs.Action} quick-setting on non-array type`); + return current; + } + + const clone = deepClone(current); + let missing: any[] = []; + + qs.Value.forEach(val => { + if (clone.includes(val)) { + return + } + missing.push(val); + }); + + if (qs.Action === 'merge-bottom') { + return clone.concat(missing) as V; + } + + return missing.concat(clone) as V; +} + +/** + * Parses the ValidationRegex of a setting and returns a list + * of supported values. + * + * @param s The setting to extract support values from. + */ +export function parseSupportedValues(s: S): SettingValueType[] { + if (!s.ValidationRegex) { + return []; + } + + const values = s.ValidationRegex.match(/\w+/gmi); + const result: SettingValueType[] = []; + + let converter: (s: string) => any; + + switch (s.OptType) { + case OptionType.Bool: + converter = s => s === 'true'; + break; + case OptionType.Int: + converter = s => +s; + break; + case OptionType.String: + case OptionType.StringArray: + converter = s => s + break + } + + values?.forEach(val => { + result.push(converter(val)) + }); + + return result; +} + +/** + * isDefaultValue checks if value is the settings default value. + * It supports all available settings type and fallsback to use + * JSON encoded string comparision (JS JSON.stringify is stable). + */ +export function isDefaultValue(value: T | undefined | null, defaultValue: T): boolean { + if (value === undefined) { + return true; + } + + const isObject = typeof value === 'object'; + const isDefault = isObject + ? JSON.stringify(value) === JSON.stringify(defaultValue) + : value === defaultValue; + + return isDefault; +} + +/** + * SettingValueType is used to infer the type of a settings from it's default value. + * Use like this: + * + * validate(spec: S, value SettingValueType) { ... } + */ +export type SettingValueType = S extends { DefaultValue: infer T } ? T : any; + +export type Setting = IntSetting + | StringSetting + | StringArraySetting + | BoolSetting; diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/core.types.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/core.types.ts new file mode 100644 index 00000000..5e5e1417 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/core.types.ts @@ -0,0 +1,34 @@ +import { TrackByFunction } from '@angular/core'; + +export enum SecurityLevel { + Off = 0, + Normal = 1, + High = 2, + Extreme = 4, +} + +export enum RiskLevel { + Off = 'off', + Auto = 'auto', + Low = 'low', + Medium = 'medium', + High = 'high' +} + +/** Interface capturing any object that has an ID member. */ +export interface Identifyable { + ID: string | number; +} + +/** A TrackByFunction for all Identifyable objects. */ +export const trackById: TrackByFunction = (_: number, obj: Identifyable) => { + return obj.ID; +} + +export function getEnumKey(enumLike: any, value: string | number): string { + if (typeof value === 'string') { + return value.toLowerCase() + } + + return (enumLike[value] as string).toLowerCase() +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/debug-api.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/debug-api.service.ts new file mode 100644 index 00000000..f0617943 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/debug-api.service.ts @@ -0,0 +1,54 @@ +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PORTMASTER_HTTP_API_ENDPOINT } from './portapi.service'; + +@Injectable({ + providedIn: 'root', +}) +export class DebugAPI { + constructor( + private http: HttpClient, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string, + ) { } + + ping(): Observable { + return this.http.get(`${this.httpAPI}/v1/ping`, { + responseType: 'text' + }) + } + + getStack(): Observable { + return this.http.get(`${this.httpAPI}/v1/debug/stack`, { + responseType: 'text' + }) + } + + getDebugInfo(style = 'github'): Observable { + return this.http.get(`${this.httpAPI}/v1/debug/info`, { + params: { + style, + }, + responseType: 'text', + }) + } + + getCoreDebugInfo(style = 'github'): Observable { + return this.http.get(`${this.httpAPI}/v1/debug/core`, { + params: { + style, + }, + responseType: 'text', + }) + } + + getProfileDebugInfo(source: string, id: string, style = 'github'): Observable { + return this.http.get(`${this.httpAPI}/v1/debug/network`, { + params: { + profile: `${source}/${id}`, + style, + }, + responseType: 'text', + }) + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/features.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/features.ts new file mode 100644 index 00000000..658f1c1b --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/features.ts @@ -0,0 +1,8 @@ +export enum FeatureID { + None = "", + SPN = "spn", + PrioritySupport = "support", + History = "history", + Bandwidth = "bw-vis", + VPNCompat = "vpn-compat", +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/meta-api.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/meta-api.service.ts new file mode 100644 index 00000000..009848f4 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/meta-api.service.ts @@ -0,0 +1,106 @@ +import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http'; +import { Inject, Injectable, Optional } from '@angular/core'; +import { Observable, of, throwError } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; +import { PORTMASTER_HTTP_API_ENDPOINT } from './portapi.service'; + +export interface MetaEndpointParameter { + Method: string; + Field: string; + Value: string; + Description: string; +} + +export interface MetaEndpoint { + Path: string; + MimeType: string; + Read: number; + Write: number; + Name: string; + Description: string; + Parameters: MetaEndpointParameter[]; +} + +export interface AuthPermission { + Read: number; + Write: number; + ReadRole: string; + WriteRole: string; +} + +export interface MyProfileResponse { + profile: string; + source: string; + name: string; +} + +export interface AuthKeyResponse { + key: string; + validUntil: string; +} + +@Injectable() +export class MetaAPI { + constructor( + private http: HttpClient, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) @Optional() private httpEndpoint: string = 'http://localhost:817/api', + ) { } + + listEndpoints(): Observable { + return this.http.get(`${this.httpEndpoint}/v1/endpoints`) + } + + permissions(): Observable { + return this.http.get(`${this.httpEndpoint}/v1/auth/permissions`) + } + + myProfile(): Observable { + return this.http.get(`${this.httpEndpoint}/v1/app/profile`) + } + + requestApplicationAccess(appName: string, read: 'user' | 'admin' = 'user', write: 'user' | 'admin' = 'user'): Observable { + let params = new HttpParams() + .set("app-name", appName) + .set("read", read) + .set("write", write) + + return this.http.get(`${this.httpEndpoint}/v1/app/auth`, { params }) + } + + login(bearer: string): Observable; + login(username: string, password: string): Observable; + login(usernameOrBearer: string, password?: string): Observable { + let login: Observable; + + if (!!password) { + login = this.http.get(`${this.httpEndpoint}/v1/auth/basic`, { + headers: { + 'Authorization': `Basic ${btoa(usernameOrBearer + ":" + password)}` + } + }) + } else { + login = this.http.get(`${this.httpEndpoint}/v1/auth/bearer`, { + headers: { + 'Authorization': `Bearer ${usernameOrBearer}` + } + }) + } + + return login.pipe( + map(() => true), + catchError(err => { + if (err instanceof HttpErrorResponse) { + if (err.status === 401) { + return of(false); + } + } + + return throwError(() => err) + }) + ) + } + + logout(): Observable { + return this.http.get(`${this.httpEndpoint}/v1/auth/reset`); + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/module.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/module.ts new file mode 100644 index 00000000..0ed13363 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/module.ts @@ -0,0 +1,55 @@ +import { ModuleWithProviders, NgModule } from "@angular/core"; +import { AppProfileService } from "./app-profile.service"; +import { ConfigService } from "./config.service"; +import { DebugAPI } from "./debug-api.service"; +import { MetaAPI } from "./meta-api.service"; +import { Netquery } from "./netquery.service"; +import { PortapiService, PORTMASTER_HTTP_API_ENDPOINT, PORTMASTER_WS_API_ENDPOINT } from "./portapi.service"; +import { SPNService } from "./spn.service"; +import { WebsocketService } from "./websocket.service"; + +export interface ModuleConfig { + httpAPI?: string; + websocketAPI?: string; +} + +@NgModule({}) +export class PortmasterAPIModule { + + /** + * Configures a module with additional providers. + * + * @param cfg The module configuration defining the Portmaster HTTP and Websocket API endpoints. + */ + static forRoot(cfg: ModuleConfig = {}): ModuleWithProviders { + if (cfg.httpAPI === undefined) { + cfg.httpAPI = `http://${window.location.host}/api`; + } + if (cfg.websocketAPI === undefined) { + cfg.websocketAPI = `ws://${window.location.host}/api/database/v1`; + } + + return { + ngModule: PortmasterAPIModule, + providers: [ + PortapiService, + WebsocketService, + MetaAPI, + ConfigService, + AppProfileService, + DebugAPI, + Netquery, + SPNService, + { + provide: PORTMASTER_HTTP_API_ENDPOINT, + useValue: cfg.httpAPI, + }, + { + provide: PORTMASTER_WS_API_ENDPOINT, + useValue: cfg.websocketAPI + } + ] + } + } + +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/netquery.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/netquery.service.ts new file mode 100644 index 00000000..c0b1ec88 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/netquery.service.ts @@ -0,0 +1,543 @@ +import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http"; +import { Inject, Injectable } from "@angular/core"; +import { Observable, forkJoin, of } from "rxjs"; +import { catchError, map, mergeMap } from "rxjs/operators"; +import { AppProfileService } from "./app-profile.service"; +import { AppProfile } from "./app-profile.types"; +import { DNSContext, IPScope, Reason, TLSContext, TunnelContext, Verdict } from "./network.types"; +import { PORTMASTER_HTTP_API_ENDPOINT, PortapiService } from "./portapi.service"; +import { Container } from "postcss"; + +export interface FieldSelect { + field: string; +} + +export interface FieldAsSelect { + $field: { + field: string; + as: string; + } +} + +export interface Count { + $count: { + field: string; + distinct?: boolean; + as?: string; + } +} + +export interface Sum { + $sum: { + condition: Condition; + as: string; + distinct?: boolean; + } | { + field: string; + as: string; + distinct?: boolean; + } +} + +export interface Min { + $min: { + condition: Condition; + as: string; + distinct?: boolean; + } | { + field: string; + as: string; + distinct?: boolean; + } +} + +export interface Distinct { + $distinct: string; +} + +export type Select = FieldSelect | FieldAsSelect | Count | Distinct | Sum | Min; + +export interface Equal { + $eq: any; +} + +export interface NotEqual { + $ne: any; +} + +export interface Like { + $like: string; +} + +export interface In { + $in: any[]; +} + +export interface NotIn { + $notin: string[]; +} + +export interface Greater { + $gt: number; +} + +export interface GreaterOrEqual { + $ge: number; +} + +export interface Less { + $lt: number; +} + +export interface LessOrEqual { + $le: number; +} + +export type Matcher = Equal | NotEqual | Like | In | NotIn | Greater | GreaterOrEqual | Less | LessOrEqual; + +export interface OrderBy { + field: string; + desc?: boolean; +} + +export interface Condition { + [key: string]: string | Matcher | (string | Matcher)[]; +} + +export interface TextSearch { + fields: string[]; + value: string; +} + +export enum Database { + Live = "main", + History = "history" +} + +export interface Query { + select?: string | Select | (Select | string)[]; + query?: Condition; + orderBy?: string | OrderBy | (OrderBy | string)[]; + textSearch?: TextSearch; + groupBy?: string[]; + pageSize?: number; + page?: number; + databases?: Database[]; +} + +export interface NetqueryConnection { + id: string; + allowed: boolean | null; + profile: string; + path: string; + type: 'dns' | 'ip'; + external: boolean; + ip_version: number; + ip_protocol: number; + local_ip: string; + local_port: number; + remote_ip: string; + remote_port: number; + domain: string; + country: string; + asn: number; + as_owner: string; + latitude: number; + longitude: number; + scope: IPScope; + verdict: Verdict; + started: string; + ended: string; + tunneled: boolean; + encrypted: boolean; + internal: boolean; + direction: 'inbound' | 'outbound'; + profile_revision: number; + exit_node?: string; + extra_data?: { + pid?: number; + processCreatedAt?: number; + cname?: string[]; + blockedByLists?: string[]; + blockedEntities?: string[]; + reason?: Reason; + tunnel?: TunnelContext; + dns?: DNSContext; + tls?: TLSContext; + }; + + profile_name: string; + active: boolean; + bytes_received: number; + bytes_sent: number; +} + +export interface ChartResult { + timestamp: number; + value: number; + countBlocked: number; +} + +export interface QueryResult extends Partial { + [key: string]: any; +} + +export interface Identities { + exit_node: string; + count: number; +} + +export interface IProfileStats { + ID: string; + Name: string; + + size: number; + empty: boolean; + identities: Identities[]; + countAllowed: number; + countUnpermitted: number; + countAliveConnections: number; + bytes_sent: number; + bytes_received: number; +} + +type BatchResponse = { + [key in keyof T]: QueryResult[] +} + +interface BatchRequest { + [key: string]: Query +} + +interface BandwidthBaseResult { + timestamp: number; + incoming: number; + outgoing: number; +} + +export type ConnKeys = keyof NetqueryConnection + +export type BandwidthChartResult = { + [key in K]: NetqueryConnection[K]; +} & BandwidthBaseResult + +export type ProfileBandwidthChartResult = BandwidthChartResult<'profile'>; + +export type ConnectionBandwidthChartResult = BandwidthChartResult<'id'>; + +@Injectable({ providedIn: 'root' }) +export class Netquery { + constructor( + private http: HttpClient, + private profileService: AppProfileService, + private portapi: PortapiService, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string, + ) { } + + query(query: Query, origin: string): Observable { + return this.http.post<{ results: QueryResult[] }>(`${this.httpAPI}/v1/netquery/query`, query, { + params: new HttpParams().set("origin", origin) + }) + .pipe(map(res => res.results || [])); + } + + batch(queries: T): Observable> { + return this.http.post>(`${this.httpAPI}/v1/netquery/query/batch`, queries) + } + + cleanProfileHistory(profileIDs: string | string[]): Observable> { + return this.http.post(`${this.httpAPI}/v1/netquery/history/clear`, + { + profileIDs: Array.isArray(profileIDs) ? profileIDs : [profileIDs] + }, + { + observe: 'response', + responseType: 'text', + reportProgress: false, + } + ) + } + + profileBandwidthChart(profile?: string[], interval?: number): Observable<{ [profile: string]: ProfileBandwidthChartResult[] }> { + const cond: Condition = {} + if (!!profile) { + cond['profile'] = profile + } + + return this.bandwidthChart(cond, ['profile'], interval) + .pipe( + map(results => { + const obj: { + [connId: string]: ProfileBandwidthChartResult[] + } = {}; + + results?.forEach(row => { + const arr = obj[row.profile] || [] + arr.push(row) + obj[row.profile] = arr + }) + + return obj + }) + ) + } + + bandwidthChart(query: Condition, groupBy?: K[], interval?: number): Observable[]> { + return this.http.post<{ results: BandwidthChartResult[] }>(`${this.httpAPI}/v1/netquery/charts/bandwidth`, { + interval, + groupBy, + query, + }) + .pipe( + map(response => response.results), + ) + } + + connectionBandwidthChart(connIds: string[], interval?: number): Observable<{ [connId: string]: ConnectionBandwidthChartResult[] }> { + const cond: Condition = {} + if (!!connIds) { + cond['id'] = connIds + } + + return this.bandwidthChart(cond, ['id'], interval) + .pipe( + map(results => { + const obj: { + [connId: string]: ConnectionBandwidthChartResult[] + } = {}; + + results?.forEach(row => { + const arr = obj[row.id] || [] + arr.push(row) + obj[row.id] = arr + }) + + return obj + }) + ) + } + + activeConnectionChart(cond: Condition, textSearch?: TextSearch): Observable { + return this.http.post<{ results: ChartResult[] }>(`${this.httpAPI}/v1/netquery/charts/connection-active`, { + query: cond, + textSearch, + }) + .pipe(map(res => { + const now = new Date(); + + let data: ChartResult[] = []; + + let lastPoint: ChartResult | null = { + timestamp: Math.floor(now.getTime() / 1000 - 600), + value: 0, + countBlocked: 0, + }; + res.results?.forEach(point => { + if (!!lastPoint && lastPoint.timestamp < (point.timestamp - 10)) { + for (let i = lastPoint.timestamp; i < point.timestamp; i += 10) { + data.push({ + timestamp: i, + value: 0, + countBlocked: 0, + }) + } + } + data.push(point); + lastPoint = point; + }) + + const lastPointTs = Math.round(now.getTime() / 1000); + if (!!lastPoint && lastPoint.timestamp < (lastPointTs - 20)) { + for (let i = lastPoint.timestamp; i < lastPointTs; i += 20) { + data.push({ + timestamp: i, + value: 0, + countBlocked: 0 + }) + } + } + + return data; + })); + } + + getActiveProfileIDs(): Observable { + return this.query({ + select: [ + 'profile', + ], + groupBy: [ + 'profile', + ], + }, 'get-active-profile-ids').pipe( + map(result => { + return result.map(res => res.profile!); + }) + ) + } + + getActiveProfiles(): Observable { + return this.getActiveProfileIDs() + .pipe( + mergeMap(profiles => forkJoin(profiles.map(pid => this.profileService.getAppProfile(pid)))) + ) + } + + getProfileStats(query?: Condition): Observable { + let profileCache = new Map(); + + return this.batch({ + verdicts: { + select: [ + 'profile', + 'verdict', + { $count: { field: '*', as: 'totalCount' } }, + ], + groupBy: [ + 'profile', + 'verdict', + ], + query: query, + }, + + conns: { + select: [ + 'profile', + { $count: { field: '*', as: 'totalCount' } }, + { $count: { field: 'ended', as: 'countEnded' } }, + { $sum: { field: 'bytes_sent', as: 'bytes_sent' } }, + { $sum: { field: 'bytes_received', as: 'bytes_received' } }, + ], + groupBy: [ + 'profile', + ], + query: query, + }, + + identities: { + select: [ + 'profile', + 'exit_node', + { $count: { field: '*', as: 'totalCount' } } + ], + groupBy: [ + 'profile', + 'exit_node', + ], + query: { + ...query, + exit_node: { + $ne: "", + }, + }, + } + }).pipe( + map(result => { + let statsMap = new Map(); + + const getOrCreate = (id: string) => { + let stats = statsMap.get(id) || { + ID: id, + Name: 'Deleted', + countAliveConnections: 0, + countAllowed: 0, + countUnpermitted: 0, + empty: true, + identities: [], + size: 0, + bytes_received: 0, + bytes_sent: 0 + }; + + statsMap.set(id, stats); + return stats; + } + result.verdicts?.forEach(res => { + const stats = getOrCreate(res.profile!); + + switch (res.verdict) { + case Verdict.Accept: + case Verdict.RerouteToNs: + case Verdict.RerouteToTunnel: + case Verdict.Undeterminable: + stats.size += res.totalCount + stats.countAllowed += res.totalCount; + break; + + case Verdict.Block: + case Verdict.Drop: + case Verdict.Failed: + case Verdict.Undecided: + stats.size += res.totalCount + stats.countUnpermitted += res.totalCount; + break; + } + + stats.empty = stats.size == 0; + }) + + result.conns?.forEach(res => { + const stats = getOrCreate(res.profile!); + + stats.countAliveConnections = res.totalCount - res.countEnded; + stats.bytes_received += res.bytes_received!; + stats.bytes_sent += res.bytes_sent!; + }) + + result.identities?.forEach(res => { + const stats = getOrCreate(res.profile!); + + let ident = stats.identities.find(value => value.exit_node === res.exit_node) + if (!ident) { + ident = { + count: 0, + exit_node: res.exit_node!, + } + stats.identities.push(ident); + } + + ident.count += res.totalCount; + }) + + return Array.from(statsMap.values()) + }), + mergeMap(stats => { + return forkJoin(stats.map(p => { + if (profileCache.has(p.ID)) { + return of(profileCache.get(p.ID)!); + } + return this.profileService.getAppProfile(p.ID) + .pipe(catchError(err => { + return of(null) + })) + })) + .pipe( + map((profiles: (AppProfile | null)[]) => { + profileCache = new Map(); + + let lm = new Map(); + stats.forEach(stat => lm.set(stat.ID, stat)); + + profiles + .forEach(p => { + if (!p) { + return + } + + profileCache.set(`${p.Source}/${p.ID}`, p) + + let stat = lm.get(`${p.Source}/${p.ID}`) + if (!stat) { + return; + } + + stat.Name = p.Name + }) + + return Array.from(lm.values()) + }) + ) + }) + ) + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/network.types.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/network.types.ts new file mode 100644 index 00000000..6cdef998 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/network.types.ts @@ -0,0 +1,314 @@ +import { Record } from './portapi.types'; + +export enum Verdict { + Undecided = 0, + Undeterminable = 1, + Accept = 2, + Block = 3, + Drop = 4, + RerouteToNs = 5, + RerouteToTunnel = 6, + Failed = 7 +} + +export enum IPProtocol { + ICMP = 1, + IGMP = 2, + TCP = 6, + UDP = 17, + ICMPv6 = 58, + UDPLite = 136, + RAW = 255, // TODO(ppacher): what is RAW used for? +} + +export enum IPVersion { + V4 = 4, + V6 = 6, +} + +export enum IPScope { + Invalid = -1, + Undefined = 0, + HostLocal = 1, + LinkLocal = 2, + SiteLocal = 3, + Global = 4, + LocalMulticast = 5, + GlobalMulitcast = 6 +} + +let globalScopes = new Set([IPScope.GlobalMulitcast, IPScope.Global]) +let localScopes = new Set([IPScope.SiteLocal, IPScope.LinkLocal, IPScope.LocalMulticast]) + +// IsGlobalScope returns true if scope represents a globally +// routed destination. +export function IsGlobalScope(scope: IPScope): scope is IPScope.GlobalMulitcast | IPScope.Global { + return globalScopes.has(scope); +} + +// IsLocalScope returns true if scope represents a locally +// routed destination. +export function IsLANScope(scope: IPScope): scope is IPScope.SiteLocal | IPScope.LinkLocal | IPScope.LocalMulticast { + return localScopes.has(scope); +} + +// IsLocalhost returns true if scope represents localhost. +export function IsLocalhost(scope: IPScope): scope is IPScope.HostLocal { + return scope === IPScope.HostLocal; +} + +const deniedVerdicts = new Set([ + Verdict.Drop, + Verdict.Block, +]) +// IsDenied returns true if the verdict v represents a +// deny or block decision. +export function IsDenied(v: Verdict): boolean { + return deniedVerdicts.has(v); +} + +export interface CountryInfo { + Code: string; + Name: string; + Center: GeoCoordinates; + Continent: ContinentInfo; +} + +export interface ContinentInfo { + Code: string; + Region: string; + Name: string; +} + +export interface GeoCoordinates { + AccuracyRadius: number; + Latitude: number; + Longitude: number; +} + +export const UnknownLocation: GeoCoordinates = { + AccuracyRadius: 0, + Latitude: 0, + Longitude: 0 +} + +export interface IntelEntity { + // Protocol is the IP protocol used to connect/communicate + // the the described entity. + Protocol: IPProtocol; + // Port is the remote port number used. + Port: number; + // Domain is the domain name of the entity. This may either + // be the domain name used in the DNS request or the + // named returned from reverse PTR lookup. + Domain: string; + // CNAME is a list of CNAMEs that have been used + // to resolve this entity. + CNAME: string[] | null; + // IP is the IP address of the entity. + IP: string; + // IPScope holds the classification of the IP address. + IPScope: IPScope; + // Country holds the country of residence of the IP address. + Country: string; + // ASN holds the number of the autonoumous system that operates + // the IP. + ASN: number; + // ASOrg holds the AS owner name. + ASOrg: string; + // Coordinates contains the geographic coordinates of the entity. + Coordinates: GeoCoordinates | null; + // BlockedByLists holds a list of filter list IDs that + // would have blocked the entity. + BlockedByLists: string[] | null; + // BlockedEntities holds a list of entities that have been + // blocked by filter lists. Those entities can be ASNs, domains, + // CNAMEs, IPs or Countries. + BlockedEntities: string[] | null; + // ListOccurences maps the blocked entity (see BlockedEntities) + // to a list of filter-list IDs that contains it. + ListOccurences: { [key: string]: string[] } | null; +} + +export enum ScopeIdentifier { + IncomingHost = "IH", + IncomingLAN = "IL", + IncomingInternet = "II", + IncomingInvalid = "IX", + PeerHost = "PH", + PeerLAN = "PL", + PeerInternet = "PI", + PeerInvalid = "PX" +} + +export const ScopeTranslation: { [key: string]: string } = { + [ScopeIdentifier.IncomingHost]: "Device-Local Incoming", + [ScopeIdentifier.IncomingLAN]: "LAN Incoming", + [ScopeIdentifier.IncomingInternet]: "Internet Incoming", + [ScopeIdentifier.PeerHost]: "Device-Local Outgoing", + [ScopeIdentifier.PeerLAN]: "LAN Peer-to-Peer", + [ScopeIdentifier.PeerInternet]: "Internet Peer-to-Peer", + [ScopeIdentifier.IncomingInvalid]: "N/A", + [ScopeIdentifier.PeerInvalid]: "N/A", +} + +export interface ProcessContext { + BinaryPath: string; + ProcessName: string; + ProfileName: string; + PID: number; + Profile: string; + Source: string +} + +// Reason justifies the decision on a connection +// verdict. +export interface Reason { + // Msg holds a human readable message of the reason. + Msg: string; + // OptionKey, if available, holds the key of the + // configuration option that caused the verdict. + OptionKey: string; + // Profile holds the profile the option setting has + // been configured in. + Profile: string; + // Context may holds additional data about the reason. + Context: any; +} + +export enum ConnectionType { + Undefined = 0, + IPConnection = 1, + DNSRequest = 2 +} + +export function IsDNSRequest(t: ConnectionType): t is ConnectionType.DNSRequest { + return t === ConnectionType.DNSRequest; +} + +export function IsIPConnection(t: ConnectionType): t is ConnectionType.IPConnection { + return t === ConnectionType.IPConnection; +} + +export interface DNSContext { + Domain: string; + ServedFromCache: boolean; + RequestingNew: boolean; + IsBackup: boolean; + Filtered: boolean; + FilteredEntries: string[], // RR + Question: 'A' | 'AAAA' | 'MX' | 'TXT' | 'SOA' | 'SRV' | 'PTR' | 'NS' | string; + RCode: 'NOERROR' | 'SERVFAIL' | 'NXDOMAIN' | 'REFUSED' | string; + Modified: string; + Expires: string; +} + +export interface TunnelContext { + Path: TunnelNode[]; + PathCost: number; + RoutingAlg: 'default'; +} + +export interface GeoIPInfo { + IP: string; + Country: string; + ASN: number; + ASOwner: string; +} + +export interface TunnelNode { + ID: string; + Name: string; + IPv4?: GeoIPInfo; + IPv6?: GeoIPInfo; + +} + +export interface CertInfo { + Subject: string; + Issuer: string; + AlternateNames: string[]; + NotBefore: dateType; + NotAfter: dateType; +} + +export interface TLSContext { + Version: string; + VersionRaw: number; + SNI: string; + Chain: CertInfo[][]; +} + +export interface Connection extends Record { + // ID is a unique ID for the connection. + ID: string; + // Type defines the connection type. + Type: ConnectionType; + // TLS may holds additional data for the TLS + // session. + TLS: TLSContext | null; + // DNSContext holds additional data about the DNS request for + // this connection. + DNSContext: DNSContext | null; + // TunnelContext holds additional data about the SPN tunnel used for + // the connection. + TunnelContext: TunnelContext | null; + // Scope defines the scope of the connection. It's an somewhat + // weired field that may contain a ScopeIdentifier or a string. + // In case of a string it may eventually be interpreted as a + // domain name. + Scope: ScopeIdentifier | string; + // IPVersion is the version of the IP protocol used. + IPVersion: IPVersion; + // Inbound is true if the connection is incoming to + // hte local system. + Inbound: boolean; + // IPProtocol is the protocol used by the connection. + IPProtocol: IPProtocol; + // LocalIP is the local IP address that is involved into + // the connection. + LocalIP: string; + // LocalIPScope holds the classification of the local IP + // address; + LocalIPScope: IPScope; + // LocalPort is the local port that is involved into the + // connection. + LocalPort: number; + // Entity describes the remote entity that is part of the + // connection. + Entity: IntelEntity; + // Verdict defines the final verdict. + Verdict: Verdict; + // Reason is the reason justifying the verdict of the connection. + Reason: Reason; + // Started holds the number of seconds in UNIX epoch time at which + // the connection was initiated. + Started: number; + // End dholds the number of seconds in UNIX epoch time at which + // the connection was considered terminated. + Ended: number; + // Tunneled is set to true if the connection was tunneled through the + // SPN. + Tunneled: boolean; + // VerdictPermanent is set to true if the connection was marked and + // handed back to the operating system. + VerdictPermanent: boolean; + // Inspecting is set to true if the connection is being inspected. + Inspecting: boolean; + // Encrypted is set to true if the connection is estimated as being + // encrypted. Interpreting this field must be done with care! + Encrypted: boolean; + // Internal is set to true if this connection is done by the Portmaster + // or any associated helper processes/binaries itself. + Internal: boolean; + // ProcessContext holds additional information about the process + // that initated the connection. + ProcessContext: ProcessContext; + // ProfileRevisionCounter is used to track changes to the process + // profile. + ProfileRevisionCounter: number; +} + +export interface ReasonContext { + [key: string]: any; +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/portapi.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/portapi.service.ts new file mode 100644 index 00000000..4f243ecd --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/portapi.service.ts @@ -0,0 +1,1011 @@ +import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http'; +import { + Inject, + Injectable, + InjectionToken, + isDevMode, + NgZone, +} from '@angular/core'; +import { BehaviorSubject, Observable, Observer, of } from 'rxjs'; +import { + concatMap, + delay, + filter, + map, + retryWhen, + takeWhile, + tap, +} from 'rxjs/operators'; +import { WebSocketSubject } from 'rxjs/webSocket'; +import { + DataReply, + deserializeMessage, + DoneReply, + ImportResult, + InspectedActiveRequest, + isCancellable, + isDataReply, + ProfileImportResult, + Record, + ReplyMessage, + Requestable, + RequestMessage, + RequestType, + RetryableOpts, + retryPipeline, + serializeMessage, + WatchOpts, +} from './portapi.types'; +import { WebsocketService } from './websocket.service'; + +export const PORTMASTER_WS_API_ENDPOINT = new InjectionToken( + 'PortmasterWebsocketEndpoint' +); +export const PORTMASTER_HTTP_API_ENDPOINT = new InjectionToken( + 'PortmasterHttpApiEndpoint' +); + +export const RECONNECT_INTERVAL = 2000; + +let uniqueRequestId = 0; + +interface PendingMethod { + observer: Observer; + request: RequestMessage; +} + +@Injectable() +export class PortapiService { + /** The actual websocket connection, auto-(re)connects on subscription */ + private ws$: WebSocketSubject | null; + + /** used to emit changes to our "connection state" */ + private connectedSubject = new BehaviorSubject(false); + + /** A map to multiplex websocket messages to the actual observer/initator */ + private _streams$ = new Map>>(); + + /** Map to keep track of "still-to-send" requests when we are currently disconnected */ + private _pendingCalls$ = new Map(); + + /** Whether or not we are currently connected. */ + get connected$() { + return this.connectedSubject.asObservable(); + } + + /** @private DEBUGGING ONLY - keeps track of current requests and supports injecting messages */ + readonly activeRequests = new BehaviorSubject<{ + [key: string]: InspectedActiveRequest; + }>({}); + + constructor( + private websocketFactory: WebsocketService, + private ngZone: NgZone, + private http: HttpClient, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpEndpoint: string, + @Inject(PORTMASTER_WS_API_ENDPOINT) private wsEndpoint: string + ) { + // create a new websocket connection that will auto-connect + // on the first subscription and will automatically reconnect + // with consecutive subscribers. + this.ws$ = this.createWebsocket(); + + // no need to keep a reference to the subscription as we're not going + // to unsubscribe ... + this.ws$ + .pipe( + retryWhen((errors) => + errors.pipe( + // use concatMap to keep the errors in order and make sure + // they don't execute in parallel. + concatMap((e, i) => + of(e).pipe( + // We need to forward the error to all streams here because + // due to the retry feature the subscriber below won't see + // any error at all. + tap(() => { + this._streams$.forEach((observer) => observer.error(e)); + this._streams$.clear(); + }), + delay(1000) + ) + ) + ) + ) + ) + .subscribe( + (msg) => { + const observer = this._streams$.get(msg.id); + if (!observer) { + // it's expected that we receive done messages from time to time here + // as portmaster sends a "done" message after we "cancel" a subscription + // and we already remove the observer from _streams$ if the subscription + // is unsubscribed. So just hide that warning message for "done" + if (msg.type !== 'done') { + console.warn( + `Received message for unknown request id ${msg.id} (type=${msg.type})`, + msg + ); + } + return; + } + + // forward the message to the actual stream. + observer.next(msg as ReplyMessage); + }, + console.error, + () => { + // This should actually never happen but if, make sure + // we handle it ... + this._streams$.forEach((observer) => observer.complete()); + this._streams$.clear(); + } + ); + } + + /** Triggers a restart of the portmaster service */ + restartPortmaster(): Observable { + return this.http.post(`${this.httpEndpoint}/v1/core/restart`, undefined, { + observe: 'response', + responseType: 'arraybuffer', + }); + } + + /** Triggers a shutdown of the portmaster service */ + shutdownPortmaster(): Observable { + return this.http.post(`${this.httpEndpoint}/v1/core/shutdown`, undefined, { + observe: 'response', + responseType: 'arraybuffer', + }); + } + + /** Force the portmaster to check for updates */ + checkForUpdates(): Observable { + return this.http.post(`${this.httpEndpoint}/v1/updates/check`, undefined, { + observe: 'response', + responseType: 'arraybuffer', + reportProgress: false, + }); + } + + /** Force a reload of the UI assets */ + reloadUI(): Observable { + return this.http.post(`${this.httpEndpoint}/v1/ui/reload`, undefined, { + observe: 'response', + responseType: 'arraybuffer', + }); + } + + /** Clear DNS cache */ + clearDNSCache(): Observable { + return this.http.post(`${this.httpEndpoint}/v1/dns/clear`, undefined, { + observe: 'response', + responseType: 'arraybuffer', + }); + } + + /** Reset the broadcast notifications state */ + resetBroadcastState(): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/broadcasts/reset-state`, + undefined, + { observe: 'response', responseType: 'arraybuffer' } + ); + } + + /** Re-initialize the SPN */ + reinitSPN(): Observable { + return this.http.post(`${this.httpEndpoint}/v1/spn/reinit`, undefined, { + observe: 'response', + responseType: 'arraybuffer', + }); + } + + /** Cleans up the history database by applying history retention settings */ + cleanupHistory(): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/netquery/history/cleanup`, + undefined, + { observe: 'response', responseType: 'arraybuffer' } + ); + } + + /** Requests a resource from the portmaster as application/json and automatically parses the response body*/ + getResource(resource: string): Observable; + + /** Requests a resource from the portmaster as text */ + getResource(resource: string, type: string): Observable>; + + getResource( + resource: string, + type?: string + ): Observable | any> { + if (type !== undefined) { + return this.http.get(`${this.httpEndpoint}/v1/updates/get/${resource}`, { + headers: new HttpHeaders({ Accept: type }), + observe: 'response', + responseType: 'text', + }); + } + + return this.http.get( + `${this.httpEndpoint}/v1/updates/get/${resource}`, + { + headers: new HttpHeaders({ Accept: 'application/json' }), + responseType: 'json', + } + ); + } + + /** Export one or more settings, either from global settings or a specific profile */ + exportSettings( + keys: string[], + from: 'global' | string = 'global' + ): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/sync/settings/export`, + { + from, + keys, + }, + { + headers: new HttpHeaders({ Accept: 'text/yaml' }), + responseType: 'text', + observe: 'body', + } + ); + } + + /** Validate a settings import for a given target */ + validateSettingsImport( + blob: string | Blob, + target: string | 'global' = 'global', + mimeType: string = 'text/yaml' + ): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/sync/settings/import`, + { + target, + rawExport: blob.toString(), + rawMime: mimeType, + validateOnly: true, + } + ); + } + + /** Import settings into a given target */ + importSettings( + blob: string | Blob, + target: string | 'global' = 'global', + mimeType: string = 'text/yaml', + reset = false, + allowUnknown = false + ): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/sync/settings/import`, + { + target, + rawExport: blob.toString(), + rawMime: mimeType, + validateOnly: false, + reset, + allowUnknown, + } + ); + } + + /** Import a profile */ + importProfile( + blob: string | Blob, + mimeType: string = 'text/yaml', + reset = false, + allowUnknown = false, + allowReplaceProfiles = false + ): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/sync/profile/import`, + { + rawExport: blob.toString(), + rawMime: mimeType, + validateOnly: false, + reset, + allowUnknown, + allowReplaceProfiles, + } + ); + } + + /** Import a profile */ + validateProfileImport( + blob: string | Blob, + mimeType: string = 'text/yaml' + ): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/sync/profile/import`, + { + rawExport: blob.toString(), + rawMime: mimeType, + validateOnly: true, + } + ); + } + + /** Export one or more settings, either from global settings or a specific profile */ + exportProfile(id: string): Observable { + return this.http.post( + `${this.httpEndpoint}/v1/sync/profile/export`, + { + id, + }, + { + headers: new HttpHeaders({ Accept: 'text/yaml' }), + responseType: 'text', + observe: 'body', + } + ); + } + + /** Merge multiple profiles into one primary profile. */ + mergeProfiles( + name: string, + primary: string, + secondaries: string[] + ): Observable { + return this.http + .post<{ new: string }>(`${this.httpEndpoint}/v1/profile/merge`, { + name: name, + to: primary, + from: secondaries, + }) + .pipe(map((response) => response.new)); + } + + /** + * Injects an event into a module to trigger certain backend + * behavior. + * + * @deprecated - Use the HTTP API instead. + * + * @param module The name of the module to inject + * @param kind The event kind to inject + */ + bridgeAPI(call: string, method: string): Observable { + return this.create(`api:${call}`, { + Method: method, + }).pipe(map(() => { })); + } + + /** + * Flushes all pending method calls that have been collected + * while we were not connected to the portmaster API. + */ + private _flushPendingMethods() { + const count = this._pendingCalls$.size; + try { + this._pendingCalls$.forEach((req, key) => { + // It's fine if we throw an error here! + this.ws$!.next(req.request); + this._streams$.set(req.request.id, req.observer); + this._pendingCalls$.delete(key); + }); + } catch (err) { + // we failed to send the pending calls because the + // websocket connection just broke. + console.error( + `Failed to flush pending calls, ${this._pendingCalls$.size} left: `, + err + ); + } + + console.log(`Successfully flushed all (${count}) pending calles`); + } + + /** + * Allows to inspect currently active requests. + */ + inspectActiveRequests(): { [key: string]: InspectedActiveRequest } { + return this.activeRequests.getValue(); + } + + /** + * Loads a database entry. The returned observable completes + * after the entry has been loaded. + * + * @param key The database key of the entry to load. + */ + get(key: string): Observable { + return this.request('get', { key }).pipe(map((res) => res.data)); + } + + /** + * Searches for multiple database entries at once. Each entry + * is streams via the returned observable. The observable is + * closed after the last entry has been published. + * + * @param query The query used to search the database. + */ + query(query: string): Observable> { + return this.request('query', { query }); + } + + /** + * Subscribes for updates on entries of the selected query. + * + * @param query The query use to subscribe. + */ + sub( + query: string, + opts: RetryableOpts = {} + ): Observable> { + return this.request('sub', { query }).pipe(retryPipeline(opts)); + } + + /** + * Subscribes for updates on entries of the selected query and + * ensures entries are stream once upon subscription. + * + * @param query The query use to subscribe. + * @todo(ppacher): check what a ok/done message mean here. + */ + qsub( + query: string, + opts?: RetryableOpts + ): Observable>; + qsub( + query: string, + opts: RetryableOpts, + _: { forwardDone: true } + ): Observable | DoneReply>; + qsub( + query: string, + opts: RetryableOpts = {}, + { forwardDone }: { forwardDone?: true } = {} + ): Observable> { + return this.request('qsub', { query }, { forwardDone }).pipe( + retryPipeline(opts) + ); + } + + /** + * Creates a new database entry. + * + * @warn create operations do not validate the type of data + * to be overwritten (for keys that does already exist). + * Use {@function insert} for more validation. + * + * @param key The database key for the entry. + * @param data The actual data for the entry. + */ + create(key: string, data: any): Observable { + data = this.stripMeta(data); + return this.request('create', { key, data }).pipe(map(() => { })); + } + + /** + * Updates an existing entry. + * + * @param key The database key for the entry + * @param data The actual, updated entry data. + */ + update(key: string, data: any): Observable { + data = this.stripMeta(data); + return this.request('update', { key, data }).pipe(map(() => { })); + } + + /** + * Creates a new database entry. + * + * @param key The database key for the entry. + * @param data The actual data for the entry. + * @todo(ppacher): check what's different to create(). + */ + insert(key: string, data: any): Observable { + data = this.stripMeta(data); + return this.request('insert', { key, data }).pipe(map(() => { })); + } + + /** + * Deletes an existing database entry. + * + * @param key The key of the database entry to delete. + */ + delete(key: string): Observable { + return this.request('delete', { key }).pipe(map(() => { })); + } + + /** + * Watch a database key for modifications. If the + * websocket connection is lost or an error is returned + * watch will automatically retry after retryDelay + * milliseconds. It stops retrying to watch key once + * maxRetries is exceeded. The returned observable completes + * when the watched key is deleted. + * + * @param key The database key to watch + * @param opts.retryDelay Number of milliseconds to wait + * between retrying the request. Defaults to 1000 + * @param opts.maxRetries Maximum number of tries before + * giving up. Defaults to Infinity + * @param opts.ingoreNew Whether or not `new` notifications + * will be ignored. Defaults to false + * @param opts.ignoreDelete Whether or not "delete" notification + * will be ignored (and replaced by null) + * @param forwardDone: Whether or not the "done" message should be forwarded + */ + watch(key: string, opts?: WatchOpts): Observable; + watch( + key: string, + opts?: WatchOpts & { ignoreDelete: true } + ): Observable; + watch( + key: string, + opts: WatchOpts, + _: { forwardDone: true } + ): Observable; + watch( + key: string, + opts: WatchOpts & { ignoreDelete: true }, + _: { forwardDone: true } + ): Observable; + watch( + key: string, + opts: WatchOpts = {}, + { forwardDone }: { forwardDone?: boolean } = {} + ): Observable { + return this.qsub(key, opts, { forwardDone } as any).pipe( + filter((reply) => reply.type !== 'done' || forwardDone === true), + filter((reply) => reply.type === 'done' || reply.key === key), + takeWhile((reply) => opts.ignoreDelete || reply.type !== 'del'), + filter((reply) => { + return !opts.ingoreNew || reply.type !== 'new'; + }), + map((reply) => { + if (reply.type === 'del') { + return null; + } + + if (reply.type === 'done') { + return reply; + } + return reply.data; + }) + ); + } + + watchAll( + query: string, + opts?: RetryableOpts + ): Observable { + return new Observable((observer) => { + let values: T[] = []; + let keys: string[] = []; + let doneReceived = false; + + const sub = this.request( + 'qsub', + { query }, + { forwardDone: true } + ).subscribe({ + next: (value) => { + if ((value as any).type === 'done') { + doneReceived = true; + observer.next(values); + return; + } + + if (!doneReceived) { + values.push(value.data); + keys.push(value.key); + return; + } + + const idx = keys.findIndex((k) => k === value.key); + switch (value.type) { + case 'new': + if (idx < 0) { + values.push(value.data); + keys.push(value.key); + } else { + /* + const existing = values[idx]._meta!; + const existingTs = existing.Modified || existing.Created; + const newTs = (value.data as Record)?._meta?.Modified || (value.data as Record)?._meta?.Created || 0; + + console.log(`Comparing ${newTs} against ${existingTs}`); + + if (newTs > existingTs) { + console.log(`New record is ${newTs - existingTs} seconds newer`); + values[idx] = value.data; + } else { + return; + } + */ + values[idx] = value.data; + } + break; + case 'del': + if (idx >= 0) { + keys.splice(idx, 1); + values.splice(idx, 1); + } + break; + case 'upd': + if (idx >= 0) { + values[idx] = value.data; + } + break; + } + + observer.next(values); + }, + error: (err) => { + observer.error(err); + }, + complete: () => { + observer.complete(); + }, + }); + + return () => { + sub.unsubscribe(); + }; + }).pipe(retryPipeline(opts)); + } + + /** + * Close the current websocket connection. A new subscription + * will _NOT_ trigger a reconnect. + */ + close() { + if (!this.ws$) { + return; + } + + this.ws$.complete(); + this.ws$ = null; + } + + request( + method: M, + attrs: Partial>, + { forwardDone }: { forwardDone?: boolean } = {} + ): Observable> { + return new Observable((observer) => { + const id = `${++uniqueRequestId}`; + if (!this.ws$) { + observer.error('No websocket connection'); + return; + } + + let shouldCancel = isCancellable(method); + let unsub: () => RequestMessage | null = () => { + if (shouldCancel) { + return { + id: id, + type: 'cancel', + }; + } + + return null; + }; + + const request: any = { + ...attrs, + id: id, + type: method, + }; + + let inspected: InspectedActiveRequest = { + type: method, + messagesReceived: 0, + observer: observer, + payload: request, + lastData: null, + lastKey: '', + }; + + if (isDevMode()) { + this.activeRequests.next({ + ...this.inspectActiveRequests(), + [id]: inspected, + }); + } + + let stream$: Observable> = this.multiplex( + request, + unsub + ); + if (isDevMode()) { + // in development mode we log all replys for the different + // methods. This also includes updates to subscriptions. + stream$ = stream$.pipe( + tap( + (msg) => { }, + //msg => console.log(`[portapi] reply for ${method} ${id}: `, msg), + (err) => console.error(`[portapi] error in ${method} ${id}: `, err) + ) + ); + } + + const subscription = stream$?.subscribe({ + next: (data) => { + inspected.messagesReceived++; + + // in all cases, an `error` message type + // terminates the data flow. + if (data.type === 'error') { + console.error(data.message, inspected); + shouldCancel = false; + + observer.error(data.message); + return; + } + + if ( + method === 'create' || + method === 'update' || + method === 'insert' || + method === 'delete' + ) { + // for data-manipulating methods success + // ends the stream. + if (data.type === 'success') { + observer.next(); + observer.complete(); + return; + } + } + + if (method === 'query' || method === 'sub' || method === 'qsub') { + if (data.type === 'warning') { + console.warn(data.message); + return; + } + + // query based methods send `done` once all + // results are sent at least once. + if (data.type === 'done') { + if (method === 'query') { + // done ends the query but does not end sub or qsub + shouldCancel = false; + observer.complete(); + return; + } + + if (!!forwardDone) { + // A done message in qsub does not actually represent + // a DataReply but we still want to forward that. + observer.next(data as any); + } + return; + } + } + + if (!isDataReply(data)) { + console.error( + `Received unexpected message type ${data.type} in a ${method} operation` + ); + return; + } + + inspected.lastData = data.data; + inspected.lastKey = data.key; + + observer.next(data); + + // for a `get` method the first `ok` message + // also marks the end of the stream. + if (method === 'get' && data.type === 'ok') { + shouldCancel = false; + observer.complete(); + } + }, + error: (err) => { + console.error(err, attrs); + observer.error(err); + }, + complete: () => { + observer.complete(); + }, + }); + + if (isDevMode()) { + // make sure we remove the "active" request when the subscription + // goes down + subscription.add(() => { + const active = this.inspectActiveRequests(); + delete active[request.id]; + this.activeRequests.next(active); + }); + } + + return () => { + subscription.unsubscribe(); + }; + }); + } + + private multiplex( + req: RequestMessage, + cancel: (() => RequestMessage | null) | null + ): Observable { + return new Observable((observer) => { + if (this.connectedSubject.getValue()) { + // Try to directly send the request to the backend + this._streams$.set(req.id, observer); + this.ws$!.next(req); + } else { + // in case of an error we just add the request as + // "pending" and wait for the connection to be + // established. + console.warn( + `Failed to send request ${req.id}:${req.type}, marking as pending ...` + ); + this._pendingCalls$.set(req.id, { + request: req, + observer: observer, + }); + } + + return () => { + // Try to cancel the request but ingore + // any errors here. + try { + if (cancel !== null) { + const cancelMsg = cancel(); + if (!!cancelMsg) { + this.ws$!.next(cancelMsg); + } + } + } catch (err) { } + + this._pendingCalls$.delete(req.id); + this._streams$.delete(req.id); + }; + }); + } + + /** + * Inject a message into a PortAPI stream. + * + * @param id The request ID to inject msg into. + * @param msg The message to inject. + */ + _injectMessage(id: string, msg: DataReply) { + // we are using runTask here so change-detection is + // triggered as needed + this.ngZone.runTask(() => { + const req = this.activeRequests.getValue()[id]; + if (!req) { + return; + } + + req.observer.next(msg as DataReply); + }); + } + + /** + * Injects a 'ok' type message + * + * @param id The ID of the request to inject into + * @param data The data blob to inject + * @param key [optional] The key of the entry to inject + */ + _injectData(id: string, data: any, key: string = '') { + this._injectMessage(id, { type: 'ok', data: data, key, id: id }); + } + + /** + * Patches the last message received on id by deeply merging + * data and re-injects that message. + * + * @param id The ID of the request + * @param data The patch to apply and reinject + */ + _patchLast(id: string, data: any) { + const req = this.activeRequests.getValue()[id]; + if (!req || !req.lastData) { + return; + } + + const newPayload = mergeDeep({}, req.lastData, data); + this._injectData(id, newPayload, req.lastKey); + } + + private stripMeta(obj: T): T { + let copy = { + ...obj, + _meta: undefined, + }; + return copy; + } + + /** + * Creates a new websocket subject and configures appropriate serializer + * and deserializer functions for PortAPI. + * + * @private + */ + private createWebsocket(): WebSocketSubject { + return this.websocketFactory.createConnection< + ReplyMessage | RequestMessage + >({ + url: this.wsEndpoint, + serializer: (msg) => { + try { + return serializeMessage(msg); + } catch (err) { + console.error('serialize message', err); + return { + type: 'error', + }; + } + }, + // deserializeMessage also supports RequestMessage so cast as any + deserializer: ((msg: any) => { + try { + const res = deserializeMessage(msg); + return res; + } catch (err) { + console.error('deserialize message', err); + return { + type: 'error', + }; + } + }), + binaryType: 'arraybuffer', + openObserver: { + next: () => { + console.log('[portapi] connection to portmaster established'); + this.connectedSubject.next(true); + this._flushPendingMethods(); + }, + }, + closeObserver: { + next: () => { + console.log('[portapi] connection to portmaster closed'); + this.connectedSubject.next(false); + }, + }, + closingObserver: { + next: () => { + console.log('[portapi] connection to portmaster closing'); + }, + }, + }); + } +} + +// Counts the number of "truthy" datafields in obj. +function countTruthyDataFields(obj: { [key: string]: any }): number { + let count = 0; + Object.keys(obj).forEach((key) => { + let value = obj[key]; + if (!!value) { + count++; + } + }); + return count; +} + +function isObject(item: any): item is Object { + return item && typeof item === 'object' && !Array.isArray(item); +} + +export function mergeDeep(target: any, ...sources: any): any { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/portapi.types.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/portapi.types.ts new file mode 100644 index 00000000..349c7b9f --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/portapi.types.ts @@ -0,0 +1,453 @@ +import { iif, MonoTypeOperatorFunction, of, Subscriber, throwError } from 'rxjs'; +import { concatMap, delay, retryWhen } from 'rxjs/operators'; + +/** +* ReplyType contains all possible message types of a reply. +*/ +export type ReplyType = 'ok' + | 'upd' + | 'new' + | 'del' + | 'success' + | 'error' + | 'warning' + | 'done'; + +/** +* RequestType contains all possible message types of a request. +*/ +export type RequestType = 'get' + | 'query' + | 'sub' + | 'qsub' + | 'create' + | 'update' + | 'insert' + | 'delete' + | 'cancel'; + +// RecordMeta describes the meta-data object that is part of +// every API resource. +export interface RecordMeta { + // Created hold a unix-epoch timestamp when the record has been + // created. + Created: number; + // Deleted hold a unix-epoch timestamp when the record has been + // deleted. + Deleted: number; + // Expires hold a unix-epoch timestamp when the record has been + // expires. + Expires: number; + // Modified hold a unix-epoch timestamp when the record has been + // modified last. + Modified: number; + // Key holds the database record key. + Key: string; +} + +export interface Process extends Record { + Name: string; + UserID: number; + UserName: string; + UserHome: string; + Pid: number; + Pgid: number; + CreatedAt: number; + ParentPid: number; + ParentCreatedAt: number; + Path: string; + ExecName: string; + Cwd: string; + CmdLine: string; + FirstArg: string; + Env: { + [key: string]: string + } | null; + Tags: { + Key: string; + Value: string; + }[] | null; + MatchingPath: string; + PrimaryProfileID: string; + FirstSeen: number; + LastSeen: number; + Error: string; + ExecHashes: { + [key: string]: string + } | null; +} + +// Record describes the base record structure of all API resources. +export interface Record { + _meta?: RecordMeta; +} + +/** +* All possible MessageType that are available in PortAPI. +*/ +export type MessageType = RequestType | ReplyType; + +/** +* BaseMessage describes the base message type that is exchanged +* via PortAPI. +*/ +export interface BaseMessage { + // ID of the request. Used to correlated (multiplex) requests and + // responses across a single websocket connection. + id: string; + // Type is the request/response message type. + type: M; +} + +/** +* DoneReply marks the end of a PortAPI stream. +*/ +export interface DoneReply extends BaseMessage<'done'> { } + +/** +* DataReply is either sent once as a result on a `get` request or +* is sent multiple times in the course of a PortAPI stream. +*/ +export interface DataReply extends BaseMessage<'ok' | 'upd' | 'new' | 'del'> { + // Key is the database key including the database prefix. + key: string; + // Data is the actual data of the entry. + data: T; +} + +/** + * Returns true if d is a DataReply message type. + * + * @param d The reply message to check + */ +export function isDataReply(d: ReplyMessage): d is DataReply { + return d.type === 'ok' + || d.type === 'upd' + || d.type === 'new' + || d.type === 'del'; + //|| d.type === 'done'; // done is actually not correct +} + +/** +* SuccessReply is used to mark an operation as successfully. It does not carry any +* data. Think of it as a "201 No Content" in HTTP. +*/ +export interface SuccessReply extends BaseMessage<'success'> { } + +/** +* ErrorReply describes an error that happened while processing a +* request. Note that an `error` type message may be sent for single +* and response-stream requests. In case of a stream the `error` type +* message marks the end of the stream. See WarningReply for a simple +* warning message that can be transmitted via PortAPI. +*/ +export interface ErrorReply extends BaseMessage<'error'> { + // Message is the error message from the backend. + message: string; +} + +/** +* WarningReply contains a warning message that describes an error +* condition encountered when processing a single entitiy of a +* response stream. In contrast to `error` type messages, a `warning` +* can only occure during data streams and does not end the stream. +*/ +export interface WarningReply extends BaseMessage<'warning'> { + // Message describes the warning/error condition the backend + // encountered. + message: string; +} + +/** +* QueryRequest defines the payload for `query`, `sub` and `qsub` message +* types. The result of a query request is always a stream of responses. +* See ErrorReply, WarningReply and DoneReply for more information. +*/ +export interface QueryRequest extends BaseMessage<'query' | 'sub' | 'qsub'> { + // Query is the query for the database. + query: string; +} + +/** +* KeyRequests defines the payload for a `get` or `delete` request. Those +* message type only carry the key of the database entry to delete. Note that +* `delete` can only return a `success` or `error` type message while `get` will +* receive a `ok` or `error` type message. +*/ +export interface KeyRequest extends BaseMessage<'delete' | 'get'> { + // Key is the database entry key. + key: string; +} + + +/** +* DataRequest is used during create, insert or update operations. +* TODO(ppacher): check what's the difference between create and insert, +* both seem to error when trying to create a new entry. +*/ +export interface DataRequest extends BaseMessage<'update' | 'create' | 'insert'> { + // Key is the database entry key. + key: string; + // Data is the data to store. + data: T; +} + +/** + * CancelRequest can be sent on stream operations to early-abort the request. + */ +export interface CancelRequest extends BaseMessage<'cancel'> { } + +/** +* ReplyMessage is a union of all reply message types. +*/ +export type ReplyMessage = DataReply + | DoneReply + | SuccessReply + | WarningReply + | ErrorReply; + +/** +* RequestMessage is a union of all request message types. +*/ +export type RequestMessage = QueryRequest + | KeyRequest + | DataRequest + | CancelRequest; + +/** +* Requestable can be used to accept only properties that match +* the request message type M. +*/ +export type Requestable = RequestMessage & { type: M }; + +/** + * Returns true if m is a cancellable message type. + * + * @param m The message type to check. + */ +export function isCancellable(m: MessageType): boolean { + switch (m) { + case 'qsub': + case 'sub': + return true; + default: + return false; + } +} + +/** + * Reflects a currently in-flight PortAPI request. Used to + * intercept and mangle with responses. + */ +export interface InspectedActiveRequest { + // The type of request. + type: RequestType; + // The actual request payload. + // @todo(ppacher): typings + payload: any; + // The request observer. Use to inject data + // or complete/error the subscriber. Use with + // care! + observer: Subscriber>; + // Counter for the number of messages received + // for this request. + messagesReceived: number; + // The last data received on the request + lastData: any; + // The last key received on the request + lastKey: string; +} + +export interface RetryableOpts { + // A delay in milliseconds before retrying an operation. + retryDelay?: number; + // The maximum number of retries. + maxRetries?: number; +} + +export interface ProfileImportResult extends ImportResult { + replacesProfiles: string[]; +} + +export interface ImportResult { + restartRequired: boolean; + replacesExisting: boolean; + containsUnknown: boolean; +} + +/** + * Returns a RxJS operator function that implements a retry pipeline + * with a configurable retry delay and an optional maximum retry count. + * If maxRetries is reached the last error captured is thrown. + * + * @param opts Configuration options for the retryPipeline. + * see {@type RetryableOpts} for more information. + */ +export function retryPipeline({ retryDelay, maxRetries }: RetryableOpts = {}): MonoTypeOperatorFunction { + return retryWhen(errors => errors.pipe( + // use concatMap to keep the errors in order and make sure + // they don't execute in parallel. + concatMap((e, i) => + iif( + // conditional observable seletion, throwError if i > maxRetries + // or a retryDelay otherwise + () => i > (maxRetries || Infinity), + throwError(() => e), + of(e).pipe(delay(retryDelay || 1000)) + ) + ) + )) +} + +export interface WatchOpts extends RetryableOpts { + // Whether or not `new` updates should be filtered + // or let through. See {@method PortAPI.watch} for + // more information. + ingoreNew?: boolean; + + ignoreDelete?: boolean; +} + + +/** +* Serializes a request or reply message into it's wire format. +* +* @param msg The request or reply messsage to serialize +*/ +export function serializeMessage(msg: RequestMessage | ReplyMessage): any { + if (msg === undefined) { + return undefined; + } + + let blob = `${msg.id}|${msg.type}`; + + switch (msg.type) { + case 'done': // reply + case 'success': // reply + case 'cancel': // request + break; + + case 'error': // reply + case 'warning': // reply + blob += `|${msg.message}` + break; + + case 'ok': // reply + case 'upd': // reply + case 'new': // reply + case 'insert': // request + case 'update': // request + case 'create': // request + blob += `|${msg.key}|J${JSON.stringify(msg.data)}` + break; + + + case 'del': // reply + case 'get': // request + case 'delete': // request + blob += `|${msg.key}` + break; + + case 'query': // request + case 'sub': // request + case 'qsub': // request + blob += `|query ${msg.query}` + break; + + default: + // We need (msg as any) here because typescript knows that we covered + // all possible values above and that .type can never be something else. + // Still, we want to guard against unexpected portmaster message + // types. + console.error(`Unknown message type ${(msg as any).type}`); + } + + return blob; +} + +/** +* Deserializes (loads) a PortAPI message from a WebSocket message event. +* +* @param event The WebSocket MessageEvent to parse. +*/ +export function deserializeMessage(event: MessageEvent): RequestMessage | ReplyMessage { + let data: string; + + if (typeof event.data !== 'string') { + data = new TextDecoder("utf-8").decode(event.data) + } else { + data = event.data; + } + + const parts = data.split("|"); + + if (parts.length < 2) { + throw new Error(`invalid number of message parts, expected 3-4 but got ${parts.length}`); + } + + const id = parts[0]; + const type = parts[1] as MessageType; + + var msg: Partial = { + id, + type, + } + + if (parts.length > 4) { + parts[3] = parts.slice(3).join('|') + } + + switch (msg.type) { + case 'done': // reply + case 'success': // reply + case 'cancel': // request + break; + + case 'error': // reply + case 'warning': // reply + msg.message = parts[2]; + break; + + case 'ok': // reply + case 'upd': // reply + case 'new': // reply + case 'insert': // request + case 'update': // request + case 'create': // request + msg.key = parts[2]; + try { + if (parts[3][0] === 'J') { + msg.data = JSON.parse(parts[3].slice(1)); + } else { + msg.data = parts[3]; + } + } catch (e) { + console.log(e, data) + } + break; + + case 'del': // reply + case 'get': // request + case 'delete': // request + msg.key = parts[2]; + break; + + case 'query': // request + case 'sub': // request + case 'qsub': // request + msg.query = parts[2]; + if (msg.query.startsWith("query ")) { + msg.query = msg.query.slice(6); + } + break; + + default: + // We need (msg as any) here because typescript knows that we covered + // all possible values above and that .type can never be something else. + // Still, we want to guard against unexpected portmaster message + // types. + console.error(`Unknown message type ${(msg as any).type}`); + } + + return msg as (ReplyMessage | RequestMessage); // it's not partitial anymore +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/spn.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/spn.service.ts new file mode 100644 index 00000000..fc0a6047 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/spn.service.ts @@ -0,0 +1,171 @@ +import { HttpClient, HttpParams, HttpResponse } from "@angular/common/http"; +import { Inject, Injectable } from "@angular/core"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { filter, map, share, switchMap } from "rxjs/operators"; +import { FeatureID } from "./features"; +import { PORTMASTER_HTTP_API_ENDPOINT, PortapiService } from './portapi.service'; +import { Feature, Pin, SPNStatus, UserProfile } from "./spn.types"; + +@Injectable({ providedIn: 'root' }) +export class SPNService { + + /** Emits the SPN status whenever it changes */ + status$: Observable; + + profile$ = this.watchProfile() + .pipe( + share({ connector: () => new BehaviorSubject(undefined) }), + filter(val => val !== undefined) + ) as Observable; + + private pins$: Observable; + + constructor( + private portapi: PortapiService, + private http: HttpClient, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string, + ) { + this.status$ = this.portapi.watch('runtime:spn/status', { ignoreDelete: true }) + .pipe( + share({ connector: () => new BehaviorSubject(null) }), + filter(val => val !== null), + ) + + this.pins$ = this.status$ + .pipe( + switchMap(status => { + if (status.Status !== "disabled") { + return this.portapi.watchAll("map:main/", { retryDelay: 50000 }) + } + + return of([] as Pin[]); + }), + share({ connector: () => new BehaviorSubject(undefined) }), + filter(val => val !== undefined) + ) as Observable; + } + + /** + * Watches all pins of the "main" SPN map. + */ + watchPins(): Observable { + return this.pins$; + } + + /** + * Encodes a unicode string to base64. + * See https://developer.mozilla.org/en-US/docs/Web/API/btoa + * and https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings + */ + b64EncodeUnicode(str: string): string { + return window.btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { + return String.fromCharCode(parseInt(p1, 16)) + })) + } + + /** + * Logs into the SPN user account + */ + login({ username, password }: { username: string, password: string }): Observable> { + return this.http.post(`${this.httpAPI}/v1/spn/account/login`, undefined, { + headers: { + Authorization: `Basic ${this.b64EncodeUnicode(username + ':' + password)}` + }, + responseType: 'text', + observe: 'response' + }); + } + + /** + * Log out of the SPN user account + * + * @param purge Whether or not the portmaster should keep user/device information for the next login + */ + logout(purge = false): Observable> { + let params = new HttpParams(); + if (!!purge) { + params = params.set("purge", "true") + } + return this.http.delete(`${this.httpAPI}/v1/spn/account/logout`, { + params, + responseType: 'text', + observe: 'response' + }) + } + + watchEnabledFeatures(): Observable<(Feature & { enabled: boolean })[]> { + return this.profile$ + .pipe( + switchMap(profile => { + return this.loadFeaturePackages() + .pipe( + map(features => { + return features.map(feature => { + // console.log(feature, profile?.current_plan?.feature_ids) + return { + ...feature, + enabled: feature.RequiredFeatureID === FeatureID.None || profile?.current_plan?.feature_ids?.includes(feature.RequiredFeatureID) || false, + } + }) + }) + ) + }) + ); + } + + /** Returns a list of all feature packages */ + loadFeaturePackages(): Observable { + return this.http.get<{ Features: Feature[] }>(`${this.httpAPI}/v1/account/features`) + .pipe( + map(response => response.Features.map(feature => { + return { + ...feature, + IconURL: `${this.httpAPI}/v1/account/features/${feature.ID}/icon`, + } + })) + ); + } + + /** + * Returns the current SPN user profile. + * + * @param refresh Whether or not the user profile should be refreshed from the ticket agent + * @returns + */ + userProfile(refresh = false): Observable { + let params = new HttpParams(); + if (!!refresh) { + params = params.set("refresh", true) + } + return this.http.get(`${this.httpAPI}/v1/spn/account/user/profile`, { + params + }); + } + + /** + * Watches the user profile. It will emit null if there is no profile available yet. + */ + watchProfile(): Observable { + let hasSent = false; + return this.portapi.watch('core:spn/account/user', { ignoreDelete: true }, { forwardDone: true }) + .pipe( + filter(result => { + if ('type' in result && result.type === 'done') { + if (hasSent) { + return false; + } + } + + return true + }), + map(result => { + hasSent = true; + if ('type' in result) { + return null; + } + + return result; + }) + ); + } +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/spn.types.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/spn.types.ts new file mode 100644 index 00000000..b2e7caaf --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/spn.types.ts @@ -0,0 +1,104 @@ +import { FeatureID } from './features'; +import { CountryInfo, GeoCoordinates, IntelEntity } from './network.types'; +import { Record } from './portapi.types'; + +export interface SPNStatus extends Record { + Status: 'failed' | 'disabled' | 'connecting' | 'connected'; + HomeHubID: string; + HomeHubName: string; + ConnectedIP: string; + ConnectedTransport: string; + ConnectedCountry: CountryInfo | null; + ConnectedSince: string | null; +} + +export interface Pin extends Record { + ID: string; + Name: string; + FirstSeen: string; + EntityV4?: IntelEntity | null; + EntityV6?: IntelEntity | null; + States: string[]; + SessionActive: boolean; + HopDistance: number; + ConnectedTo: { + [key: string]: Lane, + }; + Route: string[] | null; + VerifiedOwner: string; +} + +export interface Lane { + HubID: string; + Capacity: number; + Latency: number; +} + +export function getPinCoords(p: Pin): GeoCoordinates | null { + if (p.EntityV4 && p.EntityV4.Coordinates) { + return p.EntityV4.Coordinates; + } + return p.EntityV6?.Coordinates || null; +} + +export interface Device { + name: string; + id: string; +} + +export interface Subscription { + ends_at: string; + state: 'manual' | 'active' | 'cancelled'; + next_billing_date: string; + payment_provider: string; +} + +export interface Plan { + name: string; + amount: number; + months: number; + renewable: boolean; + feature_ids: FeatureID[]; +} + +export interface View { + Message: string; + ShowAccountData: boolean; + ShowAccountButton: boolean; + ShowLoginButton: boolean; + ShowRefreshButton: boolean; + ShowLogoutButton: boolean; +} + +export interface UserProfile extends Record { + username: string; + state: string; + balance: number; + device: Device | null; + subscription: Subscription | null; + current_plan: Plan | null; + next_plan: Plan | null; + view: View | null; + LastNotifiedOfEnd?: string; + LoggedInAt?: string; +} + +export interface Package { + Name: string; + HexColor: string; +} + +export interface Feature { + ID: string; + Name: string; + ConfigKey: string; + ConfigScope: string; + RequiredFeatureID: FeatureID; + InPackage: Package | null; + Comment: string; + Beta?: boolean; + ComingSoon?: boolean; + + // does not come from the PM API but is set by SPNService + IconURL: string; +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/utils.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/utils.ts new file mode 100644 index 00000000..80b97573 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/utils.ts @@ -0,0 +1,13 @@ + +export function deepClone(o?: T | null): T { + if (o === null || o === undefined) { + return null as any as T; + } + + let _out: T = (Array.isArray(o) ? [] : {}) as T; + for (let _key in (o as T)) { + let v = o[_key]; + _out[_key] = (typeof v === "object") ? deepClone(v) : v; + } + return _out as T; +} diff --git a/desktop/angular/projects/safing/portmaster-api/src/lib/websocket.service.ts b/desktop/angular/projects/safing/portmaster-api/src/lib/websocket.service.ts new file mode 100644 index 00000000..c42efa8d --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/lib/websocket.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { webSocket, WebSocketSubject, WebSocketSubjectConfig } from 'rxjs/webSocket'; + +@Injectable() +export class WebsocketService { + constructor() { } + + /** + * createConnection creates a new websocket connection using opts. + * + * @param opts Options for the websocket connection. + */ + createConnection(opts: WebSocketSubjectConfig): WebSocketSubject { + return webSocket(opts); + } +} + diff --git a/desktop/angular/projects/safing/portmaster-api/src/public-api.ts b/desktop/angular/projects/safing/portmaster-api/src/public-api.ts new file mode 100644 index 00000000..9097761e --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/public-api.ts @@ -0,0 +1,22 @@ +/* + * Public API Surface of portmaster-api + */ + +export * from './lib/app-profile.service'; +export * from './lib/app-profile.types'; +export * from './lib/config.service'; +export * from './lib/config.types'; +export * from './lib/core.types'; +export * from './lib/debug-api.service'; +export * from './lib/features'; +export * from './lib/meta-api.service'; +export * from './lib/module'; +export * from './lib/netquery.service'; +export * from './lib/network.types'; +export * from './lib/portapi.service'; +export * from './lib/portapi.types'; +export * from './lib/spn.service'; +export * from './lib/spn.types'; +export * from './lib/utils'; +export * from './lib/websocket.service'; + diff --git a/desktop/angular/projects/safing/portmaster-api/src/test.ts b/desktop/angular/projects/safing/portmaster-api/src/test.ts new file mode 100644 index 00000000..43808367 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/src/test.ts @@ -0,0 +1,15 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), +); diff --git a/desktop/angular/projects/safing/portmaster-api/tsconfig.lib.json b/desktop/angular/projects/safing/portmaster-api/tsconfig.lib.json new file mode 100644 index 00000000..c9f14589 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/tsconfig.lib.json @@ -0,0 +1,16 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/lib", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "src/test.ts", + "testing/**/*", + "**/*.spec.ts" + ] +} diff --git a/desktop/angular/projects/safing/portmaster-api/tsconfig.lib.prod.json b/desktop/angular/projects/safing/portmaster-api/tsconfig.lib.prod.json new file mode 100644 index 00000000..71b135f6 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/tsconfig.lib.prod.json @@ -0,0 +1,7 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, +} diff --git a/desktop/angular/projects/safing/portmaster-api/tsconfig.spec.json b/desktop/angular/projects/safing/portmaster-api/tsconfig.spec.json new file mode 100644 index 00000000..258250d2 --- /dev/null +++ b/desktop/angular/projects/safing/portmaster-api/tsconfig.spec.json @@ -0,0 +1,18 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "files": [ + "testing/**/*.ts" + ], + "include": [ + "testing/**/*.ts", + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/desktop/angular/projects/safing/ui/.eslintrc.json b/desktop/angular/projects/safing/ui/.eslintrc.json new file mode 100644 index 00000000..91e1f496 --- /dev/null +++ b/desktop/angular/projects/safing/ui/.eslintrc.json @@ -0,0 +1,44 @@ +{ + "extends": "../../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "projects/safing/ui/tsconfig.lib.json", + "projects/safing/ui/tsconfig.spec.json" + ], + "createDefaultProgram": true + }, + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "sfng", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "sfng", + "style": "kebab-case" + } + ] + } + }, + { + "files": [ + "*.html" + ], + "rules": {} + } + ] +} diff --git a/desktop/angular/projects/safing/ui/README.md b/desktop/angular/projects/safing/ui/README.md new file mode 100644 index 00000000..cf11e371 --- /dev/null +++ b/desktop/angular/projects/safing/ui/README.md @@ -0,0 +1,24 @@ +# Ui + +This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0. + +## Code scaffolding + +Run `ng generate component component-name --project ui` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project ui`. +> Note: Don't forget to add `--project ui` or else it will be added to the default project in your `angular.json` file. + +## Build + +Run `ng build ui` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Publishing + +After building your library with `ng build ui`, go to the dist folder `cd dist/ui` and run `npm publish`. + +## Running unit tests + +Run `ng test ui` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/desktop/angular/projects/safing/ui/karma.conf.js b/desktop/angular/projects/safing/ui/karma.conf.js new file mode 100644 index 00000000..8975477b --- /dev/null +++ b/desktop/angular/projects/safing/ui/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, '../../../coverage/safing/ui'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/desktop/angular/projects/safing/ui/ng-package.json b/desktop/angular/projects/safing/ui/ng-package.json new file mode 100644 index 00000000..4a890c44 --- /dev/null +++ b/desktop/angular/projects/safing/ui/ng-package.json @@ -0,0 +1,11 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../dist-lib/safing/ui", + "lib": { + "entryFile": "src/public-api.ts" + }, + "assets": [ + "theming.scss", + "**/_*.scss" + ] +} diff --git a/desktop/angular/projects/safing/ui/package.json b/desktop/angular/projects/safing/ui/package.json new file mode 100644 index 00000000..52fa541a --- /dev/null +++ b/desktop/angular/projects/safing/ui/package.json @@ -0,0 +1,17 @@ +{ + "name": "@safing/ui", + "version": "0.0.1", + "peerDependencies": { + "@angular/common": "~12.2.0", + "@angular/core": "~12.2.0", + "@angular/cdk": "~12.2.0" + }, + "dependencies": { + "tslib": "^2.3.0" + }, + "exports": { + "./theming": { + "sass": "./theming.scss" + } + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/accordion/accordion-group.html b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion-group.html new file mode 100644 index 00000000..6dbc7430 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion-group.html @@ -0,0 +1 @@ + diff --git a/desktop/angular/projects/safing/ui/src/lib/accordion/accordion-group.ts b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion-group.ts new file mode 100644 index 00000000..3c152842 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion-group.ts @@ -0,0 +1,116 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, Component, Input, OnDestroy, TemplateRef } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { SfngAccordionComponent } from './accordion'; + +@Component({ + selector: 'sfng-accordion-group', + templateUrl: './accordion-group.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngAccordionGroupComponent implements OnDestroy { + /** @private Currently registered accordion components */ + accordions: SfngAccordionComponent[] = []; + + /** + * A template-ref to render as the header for each accordion-component. + * Receives the accordion data as an $implicit context. + */ + @Input() + set headerTemplate(v: TemplateRef | null) { + this._headerTemplate = v; + + if (!!this.accordions.length) { + this.accordions.forEach(a => { + a.headerTemplate = v; + a.cdr.markForCheck(); + }) + } + } + get headerTemplate() { return this._headerTemplate } + private _headerTemplate: TemplateRef | null = null; + + /** Whether or not one or more components can be expanded. */ + @Input() + set singleMode(v: any) { + this._singleMode = coerceBooleanProperty(v); + } + get singleMode() { return this._singleMode } + private _singleMode = false; + + /** Whether or not the accordion is disabled and does not allow expanding */ + @Input() + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v); + if (this._disabled) { + this.accordions.forEach(a => a.active = false); + } + } + get disabled(): boolean { return this._disabled; } + private _disabled = false; + + /** A list of subscriptions to the activeChange output of the registered accordion-components */ + private subscriptions: Subscription[] = []; + + /** + * Registeres an accordion component to be handled together with this + * accordion group. + * + * @param a The accordion component to register + */ + register(a: SfngAccordionComponent) { + this.accordions.push(a); + + // Tell the accordion-component about the default header-template. + if (!a.headerTemplate) { + a.headerTemplate = this.headerTemplate; + } + + // Subscribe to the activeChange output of the registered + // accordion and call toggle() for each event emitted. + this.subscriptions.push(a.activeChange.subscribe(() => { + if (this.disabled) { + return; + } + + this.toggle(a); + })) + } + + /** + * Unregisters a accordion component + * + * @param a The accordion component to unregister + */ + unregister(a: SfngAccordionComponent) { + const index = this.accordions.indexOf(a); + if (index === -1) return; + + const subscription = this.subscriptions[index]; + + subscription.unsubscribe(); + this.accordions = this.accordions.splice(index, 1); + this.subscriptions = this.subscriptions.splice(index, 1); + } + + ngOnDestroy() { + this.subscriptions.forEach(s => s.unsubscribe()); + this.subscriptions = []; + this.accordions = []; + } + + /** + * Expand an accordion component and collaps all others if + * single-mode is selected. + * + * @param a The accordion component to toggle. + */ + private toggle(a: SfngAccordionComponent) { + if (!a.active && this._singleMode) { + this.accordions?.forEach(a => a.active = false); + } + + a.active = !a.active; + } + +} diff --git a/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.html b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.html new file mode 100644 index 00000000..4d47b842 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.html @@ -0,0 +1,10 @@ +
+ + +
+ +
+ + + +
diff --git a/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.module.ts b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.module.ts new file mode 100644 index 00000000..7de494f9 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SfngAccordionComponent } from "./accordion"; +import { SfngAccordionGroupComponent } from "./accordion-group"; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + SfngAccordionGroupComponent, + SfngAccordionComponent, + ], + exports: [ + SfngAccordionGroupComponent, + SfngAccordionComponent, + ] +}) +export class SfngAccordionModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.ts b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.ts new file mode 100644 index 00000000..1c3f6ec5 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/accordion/accordion.ts @@ -0,0 +1,88 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnDestroy, OnInit, Optional, Output, TemplateRef, TrackByFunction } from '@angular/core'; +import { fadeInAnimation, fadeOutAnimation } from '../animations'; +import { SfngAccordionGroupComponent } from './accordion-group'; + +@Component({ + selector: 'sfng-accordion', + templateUrl: './accordion.html', + exportAs: 'sfngAccordion', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInAnimation, + fadeOutAnimation + ] +}) +export class SfngAccordionComponent implements OnInit, OnDestroy { + /** @deprecated in favor of [data] */ + @Input() + title: string = ''; + + /** A reference to the component provided via the template context */ + component = this; + + /** + * The data the accordion component is used for. This is passed as an $implicit context + * to the header template. + */ + @Input() + data: T | undefined = undefined; + + @Input() + trackBy: TrackByFunction = (_, c) => c + + /** Whether or not the accordion component starts active. */ + @Input() + set active(v: any) { + this._active = coerceBooleanProperty(v); + } + get active() { + return this._active; + } + private _active: boolean = false; + + /** Emits whenever the active value changes. Supports two-way bindings. */ + @Output() + activeChange = new EventEmitter(); + + /** + * The header-template to render for this component. If null, the default template from + * the parent accordion-group will be used. + */ + @Input() + headerTemplate: TemplateRef | null = null; + + @HostBinding('class.active') + /** @private Whether or not the accordion should have the 'active' class */ + get activeClass(): string { + return this.active; + } + + ngOnInit(): void { + // register at our parent group-component (if any). + this.group?.register(this); + } + + ngOnDestroy(): void { + this.group?.unregister(this); + } + + /** + * Toggle the active-state of the accordion-component. + * + * @param event The mouse event. + */ + toggle(event?: Event) { + if (!!this.group && this.group.disabled) { + return; + } + + event?.preventDefault(); + this.activeChange.emit(!this.active); + } + + constructor( + public cdr: ChangeDetectorRef, + @Optional() public group: SfngAccordionGroupComponent, + ) { } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/accordion/index.ts b/desktop/angular/projects/safing/ui/src/lib/accordion/index.ts new file mode 100644 index 00000000..c06e6707 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/accordion/index.ts @@ -0,0 +1,4 @@ +export { SfngAccordionComponent } from './accordion'; +export { SfngAccordionGroupComponent } from './accordion-group'; +export { SfngAccordionModule } from './accordion.module'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/animations/index.ts b/desktop/angular/projects/safing/ui/src/lib/animations/index.ts new file mode 100644 index 00000000..e1613052 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/animations/index.ts @@ -0,0 +1,88 @@ +import { animate, query, stagger, style, transition, trigger } from '@angular/animations'; + +export const fadeInAnimation = trigger( + 'fadeIn', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateY(-5px)' }), + animate('120ms cubic-bezier(0, 0, 0.2, 1)', + style({ opacity: 1, transform: 'translateY(0px)' })) + ] + ), + ] +); + +export const fadeOutAnimation = trigger( + 'fadeOut', + [ + transition( + ':leave', + [ + style({ opacity: 1, transform: 'translateY(0px)' }), + animate('120ms cubic-bezier(0, 0, 0.2, 1)', + style({ opacity: 0, transform: 'translateY(-5px)' })) + ] + ), + ] +); + +export const fadeInListAnimation = trigger( + 'fadeInList', + [ + transition(':enter, * => 0, * => -1', []), + transition(':increment', [ + query(':enter', [ + style({ opacity: 0 }), + stagger(5, [ + animate('300ms ease-out', style({ opacity: 1 })), + ]), + ], { optional: true }) + ]), + ] +) + +export const moveInOutAnimation = trigger( + 'moveInOut', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateX(100%)' }), + animate('.2s ease-in', + style({ opacity: 1, transform: 'translateX(0%)' })) + ] + ), + transition( + ':leave', + [ + style({ opacity: 1 }), + animate('.2s ease-out', + style({ opacity: 0, transform: 'translateX(100%)' })) + ] + ) + ] +) + +export const moveInOutListAnimation = trigger( + 'moveInOutList', + [ + transition(':enter, * => 0, * => -1', []), + transition(':increment', [ + query(':enter', [ + style({ opacity: 0, transform: 'translateX(100%)' }), + stagger(50, [ + animate('200ms ease-out', style({ opacity: 1, transform: 'translateX(0%)' })), + ]), + ], { optional: true }) + ]), + transition(':decrement', [ + query(':leave', [ + stagger(-50, [ + animate('200ms ease-out', style({ opacity: 0, transform: 'translateX(100%)' })), + ]), + ], { optional: true }) + ]), + ] +) diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/_confirm.dialog.scss b/desktop/angular/projects/safing/ui/src/lib/dialog/_confirm.dialog.scss new file mode 100644 index 00000000..a0d459a8 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/_confirm.dialog.scss @@ -0,0 +1,95 @@ +.sfng-confirm-dialog { + display: flex; + flex-direction: column; + align-items: flex-start; + + caption { + @apply text-sm; + opacity: .6; + font-size: .6rem; + } + + h1 { + font-size: 0.85rem; + font-weight: 500; + margin-bottom: 1rem; + } + + .message, + h1 { + flex-shrink: 0; + text-overflow: ellipsis; + word-break: normal; + } + + .message { + font-size: 0.75rem; + flex-grow: 1; + opacity: .6; + max-width: 300px; + } + + .message~input { + margin-top: 0.5rem; + font-size: 95%; + } + + .close-icon { + position: absolute; + top: 1rem; + right: 1rem; + opacity: .7; + cursor: pointer; + + &:hover { + opacity: 1; + } + } + + input[type="text"] { + @apply text-primary; + @apply bg-gray-500 border-gray-400 bg-opacity-75 border-opacity-75; + + &::placeholder { + @apply text-tertiary; + } + } + + .actions { + margin-top: 1rem; + width: 100%; + display: flex; + justify-content: flex-end; + align-items: center; + + button.action-button { + &:not(:last-child) { + margin-right: 0.5rem; + } + + &:not(.outline) { + @apply bg-blue; + } + + &.danger { + @apply bg-red-300; + } + + &.outline { + @apply outline-none; + @apply border; + @apply border-gray-400; + } + } + + &>span { + display: flex; + align-items: center; + + label { + margin-left: .5rem; + user-select: none; + } + } + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/_dialog.scss b/desktop/angular/projects/safing/ui/src/lib/dialog/_dialog.scss new file mode 100644 index 00000000..22300126 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/_dialog.scss @@ -0,0 +1,28 @@ +sfng-dialog-container { + .container { + display: block; + box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.75); + @apply p-6; + @apply bg-gray-300; + @apply rounded; + min-width: 20rem; + width: fit-content; + position: relative; + } + + #drag-handle { + display: block; + height: 6px; + background-color: white; + opacity: .4; + border-radius: 3px; + position: absolute; + bottom: calc(0.5rem - 2px); + width: 30%; + left: calc(50% - 15%); + + &:hover { + opacity: .8; + } + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/confirm.dialog.html b/desktop/angular/projects/safing/ui/src/lib/dialog/confirm.dialog.html new file mode 100644 index 00000000..0bbcf275 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/confirm.dialog.html @@ -0,0 +1,22 @@ +
+ {{config.caption}} + + + + + +

{{config.header}}

+ + {{ config.message }} + + + +
+ +
+
diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/confirm.dialog.ts b/desktop/angular/projects/safing/ui/src/lib/dialog/confirm.dialog.ts new file mode 100644 index 00000000..c3c1f888 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/confirm.dialog.ts @@ -0,0 +1,40 @@ +import { ChangeDetectionStrategy, Component, Inject, InjectionToken } from '@angular/core'; +import { SfngDialogRef, SFNG_DIALOG_REF } from './dialog.ref'; + +export interface ConfirmDialogButton { + text: string; + id: string; + class?: 'danger' | 'outline'; +} + +export interface ConfirmDialogConfig { + buttons?: ConfirmDialogButton[]; + canCancel?: boolean; + header?: string; + message?: string; + caption?: string; + inputType?: 'text' | 'password'; + inputModel?: string; + inputPlaceholder?: string; +} + +export const CONFIRM_DIALOG_CONFIG = new InjectionToken('ConfirmDialogConfig'); + +@Component({ + templateUrl: './confirm.dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngConfirmDialogComponent { + constructor( + @Inject(SFNG_DIALOG_REF) private dialogRef: SfngDialogRef, + @Inject(CONFIRM_DIALOG_CONFIG) public config: ConfirmDialogConfig, + ) { + if (config.inputType !== undefined && config.inputModel === undefined) { + config.inputModel = ''; + } + } + + select(action?: string) { + this.dialogRef.close(action || null); + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.animations.ts b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.animations.ts new file mode 100644 index 00000000..14e0fe29 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.animations.ts @@ -0,0 +1,19 @@ +import { animate, state, style, transition, trigger } from "@angular/animations"; + +export const dialogAnimation = trigger( + 'dialogContainer', + [ + state('void, exit', style({ opacity: 0, transform: 'scale(0.7)' })), + state('enter', style({ transform: 'none', opacity: 1 })), + transition( + '* => enter', + animate('120ms cubic-bezier(0, 0, 0.2, 1)', + style({ opacity: 1, transform: 'translateY(0px)' })) + ), + transition( + '* => void, * => exit', + animate('120ms cubic-bezier(0, 0, 0.2, 1)', + style({ opacity: 0, transform: 'scale(0.7)' })) + ), + ] +); diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.container.ts b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.container.ts new file mode 100644 index 00000000..d3565f47 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.container.ts @@ -0,0 +1,76 @@ +import { AnimationEvent } from '@angular/animations'; +import { CdkDrag } from '@angular/cdk/drag-drop'; +import { CdkPortalOutlet, ComponentPortal, Portal, TemplatePortal } from '@angular/cdk/portal'; +import { ChangeDetectorRef, Component, ComponentRef, EmbeddedViewRef, HostBinding, HostListener, InjectionToken, Input, ViewChild } from '@angular/core'; +import { Subject } from 'rxjs'; +import { dialogAnimation } from './dialog.animations'; + +export const SFNG_DIALOG_PORTAL = new InjectionToken>('SfngDialogPortal'); + +export type SfngDialogState = 'opening' | 'open' | 'closing' | 'closed'; + +@Component({ + selector: 'sfng-dialog-container', + template: ` +
+
+ +
+ `, + animations: [dialogAnimation] +}) +export class SfngDialogContainerComponent { + onStateChange = new Subject(); + + ref: ComponentRef | EmbeddedViewRef | null = null; + + constructor( + private cdr: ChangeDetectorRef, + ) { } + + @HostBinding('@dialogContainer') + state = 'enter'; + + @ViewChild(CdkPortalOutlet, { static: true }) + _portalOutlet: CdkPortalOutlet | null = null; + + @ViewChild(CdkDrag, { static: true }) + drag!: CdkDrag; + + attachComponentPortal(portal: ComponentPortal): ComponentRef { + this.ref = this._portalOutlet!.attachComponentPortal(portal) + return this.ref; + } + + attachTemplatePortal(portal: TemplatePortal): EmbeddedViewRef { + this.ref = this._portalOutlet!.attachTemplatePortal(portal); + return this.ref; + } + + @Input() + dragable: boolean = false; + + @HostListener('@dialogContainer.start', ['$event']) + onAnimationStart({ toState }: AnimationEvent) { + if (toState === 'enter') { + this.onStateChange.next('opening'); + } else if (toState === 'exit') { + this.onStateChange.next('closing'); + } + } + + @HostListener('@dialogContainer.done', ['$event']) + onAnimationEnd({ toState }: AnimationEvent) { + if (toState === 'enter') { + this.onStateChange.next('open'); + } else if (toState === 'exit') { + this.onStateChange.next('closed'); + } + } + + /** Starts the exit animation */ + _startExit() { + this.state = 'exit'; + this.cdr.markForCheck(); + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.module.ts b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.module.ts new file mode 100644 index 00000000..d47195b9 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.module.ts @@ -0,0 +1,23 @@ +import { DragDropModule } from "@angular/cdk/drag-drop"; +import { OverlayModule } from "@angular/cdk/overlay"; +import { PortalModule } from "@angular/cdk/portal"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { SfngConfirmDialogComponent } from "./confirm.dialog"; +import { SfngDialogContainerComponent } from "./dialog.container"; + +@NgModule({ + imports: [ + CommonModule, + OverlayModule, + PortalModule, + DragDropModule, + FormsModule, + ], + declarations: [ + SfngDialogContainerComponent, + SfngConfirmDialogComponent, + ] +}) +export class SfngDialogModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.ref.ts b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.ref.ts new file mode 100644 index 00000000..145c60ca --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.ref.ts @@ -0,0 +1,62 @@ +import { OverlayRef } from "@angular/cdk/overlay"; +import { InjectionToken } from "@angular/core"; +import { Observable, PartialObserver, Subject } from "rxjs"; +import { filter, take } from "rxjs/operators"; +import { SfngDialogContainerComponent, SfngDialogState } from "./dialog.container"; + +export const SFNG_DIALOG_REF = new InjectionToken>('SfngDialogRef'); + +export class SfngDialogRef { + constructor( + private _overlayRef: OverlayRef, + private container: SfngDialogContainerComponent, + public readonly data: D, + ) { + this.container.onStateChange + .pipe( + filter(state => state === 'closed'), + take(1) + ) + .subscribe(() => { + this._overlayRef.detach(); + this._overlayRef.dispose(); + this.onClose.next(this.value); + this.onClose.complete(); + }); + } + + get onStateChange(): Observable { + return this.container.onStateChange; + } + + + /** + * @returns The overlayref that holds the dialog container. + */ + overlay() { return this._overlayRef } + + /** + * @returns the instance attached to the dialog container + */ + contentRef() { return this.container.ref! } + + /** Value holds the value passed on close() */ + private value: R | null = null; + + /** + * Emits the result of the dialog and closes the overlay. + */ + onClose = new Subject() + + /** onAction only emits if close() is called with action. */ + onAction(action: T, observer: PartialObserver | ((value: T) => void)): this { + (this.onClose.pipe(filter(val => val === action)) as Observable) + .subscribe(observer as any); // typescript does not select the correct type overload here. + return this; + } + + close(result?: R) { + this.value = result || null; + this.container._startExit(); + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.service.ts b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.service.ts new file mode 100644 index 00000000..e7b80ffc --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/dialog.service.ts @@ -0,0 +1,154 @@ +import { Overlay, OverlayConfig, OverlayPositionBuilder, PositionStrategy } from '@angular/cdk/overlay'; +import { ComponentPortal, ComponentType, TemplatePortal } from '@angular/cdk/portal'; +import { EmbeddedViewRef, Injectable, Injector } from '@angular/core'; +import { filter, take, takeUntil } from 'rxjs/operators'; +import { ConfirmDialogConfig, CONFIRM_DIALOG_CONFIG, SfngConfirmDialogComponent } from './confirm.dialog'; +import { SfngDialogContainerComponent } from './dialog.container'; +import { SfngDialogModule } from './dialog.module'; +import { SfngDialogRef, SFNG_DIALOG_REF } from './dialog.ref'; + +export interface BaseDialogConfig { + /** whether or not the dialog should close on outside-clicks and ESC */ + autoclose?: boolean; + + /** whether or not a backdrop should be visible */ + backdrop?: boolean | 'light'; + + /** whether or not the dialog should be dragable */ + dragable?: boolean; + + /** + * optional position strategy for the overlay. if omitted, the + * overlay will be centered on the screen + */ + positionStrategy?: PositionStrategy; + + /** + * Optional data for the dialog that is available either via the + * SfngDialogRef for ComponentPortals as an $implicit context value + * for TemplatePortals. + * + * Note, for template portals, data is only set as an $implicit context + * value if it is not yet set in the portal! + */ + data?: any; +} + +export interface ComponentPortalConfig { + injector?: Injector; +} + +@Injectable({ providedIn: SfngDialogModule }) +export class SfngDialogService { + + constructor( + private injector: Injector, + private overlay: Overlay, + ) { } + + position(): OverlayPositionBuilder { + return this.overlay.position(); + } + + create(template: TemplatePortal, opts?: BaseDialogConfig): SfngDialogRef>; + create(target: ComponentType, opts?: BaseDialogConfig & ComponentPortalConfig): SfngDialogRef; + create(target: ComponentType | TemplatePortal, opts: BaseDialogConfig & ComponentPortalConfig = {}): SfngDialogRef { + let position: PositionStrategy = opts?.positionStrategy || this.overlay + .position() + .global() + .centerVertically() + .centerHorizontally(); + + let hasBackdrop = true; + let backdropClass = 'dialog-screen-backdrop'; + if (opts.backdrop !== undefined) { + if (opts.backdrop === false) { + hasBackdrop = false; + } else if (opts.backdrop === 'light') { + backdropClass = 'dialog-screen-backdrop-light'; + } + } + + const cfg = new OverlayConfig({ + scrollStrategy: this.overlay.scrollStrategies.noop(), + positionStrategy: position, + hasBackdrop: hasBackdrop, + backdropClass: backdropClass, + }); + const overlayref = this.overlay.create(cfg); + + // create our dialog container and attach it to the + // overlay. + const containerPortal = new ComponentPortal>( + SfngDialogContainerComponent, + undefined, + this.injector, + ) + const containerRef = containerPortal.attach(overlayref); + + if (!!opts.dragable) { + containerRef.instance.dragable = true; + } + + // create the dialog ref + const dialogRef = new SfngDialogRef(overlayref, containerRef.instance, opts.data); + + // prepare the content portal and attach it to the container + let result: any; + if (target instanceof TemplatePortal) { + let r = containerRef.instance.attachTemplatePortal(target) + + if (!!r.context && typeof r.context === 'object' && !('$implicit' in r.context)) { + r.context = { + $implicit: opts.data, + ...r.context, + } + } + + result = r + } else { + const contentPortal = new ComponentPortal(target, null, Injector.create({ + providers: [ + { + provide: SFNG_DIALOG_REF, + useValue: dialogRef, + } + ], + parent: opts?.injector || this.injector, + })); + result = containerRef.instance.attachComponentPortal(contentPortal); + } + // update the container position now that we have some content. + overlayref.updatePosition(); + + if (!!opts?.autoclose) { + overlayref.outsidePointerEvents() + .pipe(take(1)) + .subscribe(() => dialogRef.close()); + overlayref.keydownEvents() + .pipe( + takeUntil(overlayref.detachments()), + filter(event => event.key === 'Escape') + ) + .subscribe(() => { + dialogRef.close(); + }) + } + return dialogRef; + } + + confirm(opts: ConfirmDialogConfig): SfngDialogRef { + return this.create(SfngConfirmDialogComponent, { + autoclose: opts.canCancel, + injector: Injector.create({ + providers: [ + { + provide: CONFIRM_DIALOG_CONFIG, + useValue: opts, + }, + ], + parent: this.injector, + }) + }) + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/dialog/index.ts b/desktop/angular/projects/safing/ui/src/lib/dialog/index.ts new file mode 100644 index 00000000..538cb300 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dialog/index.ts @@ -0,0 +1,5 @@ +export { ConfirmDialogConfig } from './confirm.dialog'; +export * from './dialog.module'; +export * from './dialog.ref'; +export * from './dialog.service'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.html b/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.html new file mode 100644 index 00000000..33232ea0 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.html @@ -0,0 +1,27 @@ +
+ +
+ + + +
+ {{ label }} + + + + +
+
+ + +
+ +
+
diff --git a/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.module.ts b/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.module.ts new file mode 100644 index 00000000..1bfb6846 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.module.ts @@ -0,0 +1,18 @@ +import { OverlayModule } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SfngDropdownComponent } from "./dropdown"; + +@NgModule({ + imports: [ + CommonModule, + OverlayModule, + ], + declarations: [ + SfngDropdownComponent, + ], + exports: [ + SfngDropdownComponent, + ] +}) +export class SfngDropDownModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.ts b/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.ts new file mode 100644 index 00000000..3b50a8f5 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dropdown/dropdown.ts @@ -0,0 +1,216 @@ +import { coerceBooleanProperty, coerceCssPixelValue, coerceNumberProperty } from "@angular/cdk/coercion"; +import { CdkOverlayOrigin, ConnectedPosition, ScrollStrategy, ScrollStrategyOptions } from "@angular/cdk/overlay"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output, Renderer2, TemplateRef, ViewChild } from "@angular/core"; +import { fadeInAnimation, fadeOutAnimation } from '../animations'; + +@Component({ + selector: 'sfng-dropdown', + exportAs: 'sfngDropdown', + templateUrl: './dropdown.html', + styles: [ + ` + :host { + display: block; + } + ` + ], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [fadeInAnimation, fadeOutAnimation], +}) +export class SfngDropdownComponent implements OnInit { + /** The trigger origin used to open the drop-down */ + @ViewChild('trigger', { read: CdkOverlayOrigin }) + trigger: CdkOverlayOrigin | null = null; + + /** + * The button/drop-down label. Only when not using + * {@Link SfngDropdown.externalTrigger} + */ + @Input() + label: string = ''; + + /** The trigger template to use when {@Link SfngDropdown.externalTrigger} */ + @Input() + triggerTemplate: TemplateRef | null = null; + + /** Set to true to provide an external dropdown trigger template using {@Link SfngDropdown.triggerTemplate} */ + @Input() + set externalTrigger(v: any) { + this._externalTrigger = coerceBooleanProperty(v) + } + get externalTrigger() { + return this._externalTrigger; + } + private _externalTrigger = false; + + /** A list of classes to apply to the overlay element */ + @Input() + overlayClass: string = ''; + + /** Whether or not the drop-down is disabled. */ + @Input() + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v) + } + get disabled() { + return this._disabled; + } + private _disabled = false; + + /** The Y-offset of the drop-down overlay */ + @Input() + set offsetY(v: any) { + this._offsetY = coerceNumberProperty(v); + } + get offsetY() { return this._offsetY } + private _offsetY = 4; + + /** The X-offset of the drop-down overlay */ + @Input() + set offsetX(v: any) { + this._offsetX = coerceNumberProperty(v); + } + get offsetX() { return this._offsetX } + private _offsetX = 0; + + /** The scrollStrategy of the drop-down */ + @Input() + scrollStrategy!: ScrollStrategy; + + /** Whether or not the pop-over is currently shown. Do not modify this directly */ + isOpen = false; + + /** The minimum width of the drop-down */ + @Input() + set minWidth(val: any) { + this._minWidth = coerceCssPixelValue(val) + } + get minWidth() { return this._minWidth } + private _minWidth: string | number = 0; + + /** The maximum width of the drop-down */ + @Input() + set maxWidth(val: any) { + this._maxWidth = coerceCssPixelValue(val) + } + get maxWidth() { return this._maxWidth } + private _maxWidth: string | number | null = null; + + /** The minimum height of the drop-down */ + @Input() + set minHeight(val: any) { + this._minHeight = coerceCssPixelValue(val) + } + get minHeight() { return this._minHeight } + private _minHeight: string | number | null = null; + + /** The maximum width of the drop-down */ + @Input() + set maxHeight(val: any) { + this._maxHeight = coerceCssPixelValue(val) + } + get maxHeight() { return this._maxHeight } + private _maxHeight: string | number | null = null; + + /** Emits whenever the drop-down is opened */ + @Output() + opened = new EventEmitter(); + + /** Emits whenever the drop-down is closed. */ + @Output() + closed = new EventEmitter(); + + @Input() + positions: ConnectedPosition[] = [ + { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top', + }, + { + originX: 'end', + originY: 'top', + overlayX: 'end', + overlayY: 'bottom', + }, + { + originX: 'end', + originY: 'bottom', + overlayX: 'start', + overlayY: 'bottom', + }, + ] + + constructor( + public readonly elementRef: ElementRef, + private changeDetectorRef: ChangeDetectorRef, + private renderer: Renderer2, + private scrollOptions: ScrollStrategyOptions, + ) { + } + + ngOnInit() { + this.scrollStrategy = this.scrollStrategy || this.scrollOptions.close(); + } + + onOutsideClick(event: MouseEvent) { + if (!!this.trigger) { + const triggerEl = this.trigger.elementRef.nativeElement; + + let node = event.target; + while (!!node) { + if (node === triggerEl) { + return; + } + node = this.renderer.parentNode(node); + } + } + + this.close(); + } + + onOverlayClosed() { + this.closed.next(); + } + + close() { + if (!this.isOpen) { + return; + } + + this.isOpen = false; + this.changeDetectorRef.markForCheck(); + } + + toggle(t: CdkOverlayOrigin | null = this.trigger) { + if (this.isOpen) { + this.close(); + + return; + } + + this.show(t); + } + + show(t: CdkOverlayOrigin | null = this.trigger) { + if (t === null) { + return; + } + + if (this.isOpen || this._disabled) { + return; + } + + if (!!t) { + this.trigger = t; + const rect = (this.trigger.elementRef.nativeElement as HTMLElement).getBoundingClientRect() + + this.minWidth = rect ? rect.width : this.trigger.elementRef.nativeElement.offsetWidth; + + } + this.isOpen = true; + this.opened.next(); + this.changeDetectorRef.markForCheck(); + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/dropdown/index.ts b/desktop/angular/projects/safing/ui/src/lib/dropdown/index.ts new file mode 100644 index 00000000..ba7a9834 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/dropdown/index.ts @@ -0,0 +1,3 @@ +export * from './dropdown'; +export * from './dropdown.module'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/index.ts b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/index.ts new file mode 100644 index 00000000..8c797446 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/index.ts @@ -0,0 +1,5 @@ +export * from './overlay-stepper'; +export * from './overlay-stepper.module'; +export * from './refs'; +export * from './step'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper-container.html b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper-container.html new file mode 100644 index 00000000..5da1fb3e --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper-container.html @@ -0,0 +1,22 @@ + + + + +
+ +
+ + + + + + +
+ + +
+
diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper-container.ts b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper-container.ts new file mode 100644 index 00000000..18492f21 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper-container.ts @@ -0,0 +1,261 @@ +import { animate, style, transition, trigger } from "@angular/animations"; +import { CdkPortalOutlet, ComponentPortal, ComponentType } from "@angular/cdk/portal"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, Inject, InjectionToken, Injector, isDevMode, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { Subject } from "rxjs"; +import { SfngDialogRef, SFNG_DIALOG_REF } from "../dialog"; +import { StepperControl, StepRef, STEP_REF } from "./refs"; +import { Step, StepperConfig } from "./step"; +import { StepOutletComponent, STEP_ANIMATION_DIRECTION, STEP_PORTAL } from "./step-outlet"; + +/** + * STEP_CONFIG is used to inject the StepperConfig into the OverlayStepperContainer. + */ +export const STEP_CONFIG = new InjectionToken('StepperConfig'); + +@Component({ + templateUrl: './overlay-stepper-container.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + :host { + position: relative; + display: flex; + flex-direction: column; + width: 600px; + } + ` + ], + animations: [ + trigger( + 'moveInOut', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateX({{ in }})' }), + animate('.2s cubic-bezier(0.4, 0, 0.2, 1)', + style({ opacity: 1, transform: 'translateX(0%)' })) + ], + { params: { in: '100%' } } // default parameters + ), + transition( + ':leave', + [ + style({ opacity: 1 }), + animate('.2s cubic-bezier(0.4, 0, 0.2, 1)', + style({ opacity: 0, transform: 'translateX({{ out }})' })) + ], + { params: { out: '-100%' } } // default parameters + ) + ] + )] +}) +export class OverlayStepperContainerComponent implements OnInit, OnDestroy, StepperControl { + /** Used to keep cache the stepRef instances. See documentation for {@class StepRef} */ + private stepRefCache = new Map(); + + /** Used to emit when the stepper finished. This is always folled by emitting on onClose$ */ + private onFinish$ = new Subject(); + + /** Emits when the stepper finished - also see {@link OverlayStepperContainerComponent.onClose}*/ + get onFinish() { + return this.onFinish$.asObservable(); + } + + /** + * Emits when the stepper is closed. + * If the stepper if finished then onFinish will emit first + */ + get onClose() { + return this.dialogRef.onClose; + } + + /** The index of the currently displayed step */ + currentStepIndex = -1; + + /** The component instance of the current step */ + currentStep: Step | null = null; + + /** A reference to the portalOutlet used to render our steps */ + @ViewChild(CdkPortalOutlet, { static: true }) + portalOutlet!: CdkPortalOutlet; + + /** Whether or not the user can go back */ + canGoBack = false; + + /** Whether or not the user can abort and close the stepper */ + canAbort = false; + + /** Whether the current step is the last step */ + get isLast() { + return this.currentStepIndex + 1 >= this.config.steps.length; + } + + constructor( + @Inject(STEP_CONFIG) public readonly config: StepperConfig, + @Inject(SFNG_DIALOG_REF) public readonly dialogRef: SfngDialogRef, + private injector: Injector, + private cdr: ChangeDetectorRef + ) { } + + /** + * Moves forward to the next step or closes the stepper + * when moving beyond the last one. + */ + next(): Promise { + if (this.isLast) { + this.onFinish$.next(); + this.close(); + + return Promise.resolve(); + } + + return this.attachStep(this.currentStepIndex + 1, true) + } + + /** + * Moves back to the previous step. This does not take canGoBack + * into account. + */ + goBack(): Promise { + return this.attachStep(this.currentStepIndex - 1, false) + } + + + /** Closes the stepper - this does not run the onFinish hooks of the steps */ + async close(): Promise { + this.dialogRef.close(); + } + + ngOnInit(): void { + this.next(); + } + + ngOnDestroy(): void { + this.onFinish$.complete(); + } + + /** + * Attaches a new step component in the current outlet. It detaches any previous + * step and calls onBeforeBack and onBeforeNext respectively. + * + * @param index The index of the new step to attach. + * @param forward Whether or not the new step is attached by going "forward" or "backward" + * @returns + */ + private async attachStep(index: number, forward = true) { + if (index >= this.config.steps.length) { + if (isDevMode()) { + throw new Error(`Cannot attach step at ${index}: index out of range`) + } + return; + } + + // call onBeforeNext or onBeforeBack of the current step + if (this.currentStep) { + if (forward) { + if (!!this.currentStep.onBeforeNext) { + try { + await this.currentStep.onBeforeNext(); + } catch (err) { + console.error(`Failed to move to next step`, err) + // TODO(ppacher): display error + + return; + } + } + } else { + if (!!this.currentStep.onBeforeBack) { + try { + await this.currentStep.onBeforeBack() + } catch (err) { + console.error(`Step onBeforeBack callback failed`, err) + } + } + } + + // detach the current step component. + this.portalOutlet.detach(); + } + + const stepType = this.config.steps[index]; + const contentPortal = this.createStepContentPortal(stepType, index) + const outletPortal = this.createStepOutletPortal(contentPortal, forward ? 'right' : 'left') + + // attach the new step (which is wrapped in a StepOutletComponent). + const ref = this.portalOutlet.attachComponentPortal(outletPortal); + + // We need to wait for the step to be actually attached in the outlet + // to get access to the actual step component instance. + ref.instance.portalOutlet!.attached + .subscribe((stepRef: ComponentRef) => { + this.currentStep = stepRef.instance; + this.currentStepIndex = index; + + if (typeof this.config.canAbort === 'function') { + this.canAbort = this.config.canAbort(this.currentStepIndex, this.currentStep); + } + + // make sure we trigger a change-detection cycle now + // markForCheck() is not enough here as we need a CD to run + // immediately for the Step.buttonTemplate to be accounted for correctly. + this.cdr.detectChanges(); + }) + } + + /** + * Creates a new component portal for a step and provides access to the {@class StepRef} + * using dependency injection. + * + * @param stepType The component type of the step for which a new portal should be created. + * @param index The index of the current step. Used to create/cache the {@class StepRef} + */ + private createStepContentPortal(stepType: ComponentType, index: number): ComponentPortal { + let stepRef = this.stepRefCache.get(index); + if (stepRef === undefined) { + stepRef = new StepRef(index, this) + this.stepRefCache.set(index, stepRef); + } + + const injector = Injector.create({ + providers: [ + { + provide: STEP_REF, + useValue: stepRef, + } + ], + parent: this.config.injector || this.injector, + }) + + return new ComponentPortal(stepType, undefined, injector); + } + + /** + * Creates a new component portal for a step outlet component that will attach another content + * portal and wrap the attachment in a "move in" animation for a given direction. + * + * @param contentPortal The portal of the actual content that should be attached in the outlet + * @param dir The direction for the animation of the step outlet. + */ + private createStepOutletPortal(contentPortal: ComponentPortal, dir: 'left' | 'right'): ComponentPortal { + const injector = Injector.create({ + providers: [ + { + provide: STEP_PORTAL, + useValue: contentPortal, + }, + { + provide: STEP_ANIMATION_DIRECTION, + useValue: dir, + }, + ], + parent: this.injector, + }) + + return new ComponentPortal( + StepOutletComponent, + undefined, + injector, + ) + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper.module.ts b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper.module.ts new file mode 100644 index 00000000..6bf5fa63 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper.module.ts @@ -0,0 +1,21 @@ +import { OverlayModule } from "@angular/cdk/overlay"; +import { PortalModule } from "@angular/cdk/portal"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SfngDialogModule } from "../dialog"; +import { OverlayStepperContainerComponent } from "./overlay-stepper-container"; +import { StepOutletComponent } from "./step-outlet"; + +@NgModule({ + imports: [ + CommonModule, + PortalModule, + OverlayModule, + SfngDialogModule, + ], + declarations: [ + OverlayStepperContainerComponent, + StepOutletComponent, + ] +}) +export class OverlayStepperModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper.ts b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper.ts new file mode 100644 index 00000000..4795777a --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/overlay-stepper.ts @@ -0,0 +1,57 @@ +import { ComponentRef, Injectable, Injector } from "@angular/core"; +import { SfngDialogService } from "../dialog"; +import { OverlayStepperContainerComponent, STEP_CONFIG } from "./overlay-stepper-container"; +import { OverlayStepperModule } from "./overlay-stepper.module"; +import { StepperRef } from "./refs"; +import { StepperConfig } from "./step"; + +@Injectable({ providedIn: OverlayStepperModule }) +export class OverlayStepper { + constructor( + private injector: Injector, + private dialog: SfngDialogService, + ) { } + + /** + * Creates a new overlay stepper given it's configuration and returns + * a reference to the stepper that can be used to wait for or control + * the stepper from outside. + * + * @param config The configuration for the overlay stepper. + */ + create(config: StepperConfig): StepperRef { + // create a new injector for our OverlayStepperContainer + // that holds a reference to the StepperConfig. + const injector = this.createInjector(config); + + const dialogRef = this.dialog.create(OverlayStepperContainerComponent, { + injector: injector, + autoclose: false, + backdrop: 'light', + dragable: false, + }) + + const containerComponentRef = dialogRef.contentRef() as ComponentRef; + + return new StepperRef(containerComponentRef.instance); + } + + /** + * Creates a new dependency injector that provides access to the + * stepper configuration using the STEP_CONFIG injection token. + * + * @param config The stepper configuration to provide using DI + * @returns + */ + private createInjector(config: StepperConfig): Injector { + return Injector.create({ + providers: [ + { + provide: STEP_CONFIG, + useValue: config, + }, + ], + parent: this.injector, + }) + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/refs.ts b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/refs.ts new file mode 100644 index 00000000..c5ce4433 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/refs.ts @@ -0,0 +1,143 @@ +import { InjectionToken } from "@angular/core"; +import { Observable } from "rxjs"; +import { take } from "rxjs/operators"; +import { OverlayStepperContainerComponent } from "./overlay-stepper-container"; + +/** + * STEP_REF is the injection token that is used to provide a reference to the + * Stepper to each step. + */ +export const STEP_REF = new InjectionToken>('StepRef') + +export interface StepperControl { + /** + * Next should move the stepper forward to the next + * step or close the stepper if no more steps are + * available. + * If the stepper is closed this way all onFinish hooks + * registered at {@link StepRef} are executed. + */ + next(): Promise; + + /** + * goBack should move the stepper back to the previous + * step. This is a no-op if there's no previous step to + * display. + */ + goBack(): Promise; + + /** + * close closes the stepper but does not run any onFinish hooks + * of {@link StepRef}. + */ + close(): Promise; +} + +/** + * StepRef is a reference to the overlay stepper and can be used to control, abort + * or otherwise interact with the stepper. + * + * It is made available to individual steps using the STEP_REF injection token. + * Each step in the OverlayStepper receives it's own StepRef instance and will receive + * a reference to the same instance in case the user goes back and re-opens a step + * again. + * + * Steps should therefore store any configuration data that is needed to restore + * the previous view in the StepRef using it's save() and load() methods. + */ +export class StepRef implements StepperControl { + private onFinishHooks: (() => PromiseLike | void)[] = []; + private data: T | null = null; + + constructor( + private currentStepIndex: number, + private stepContainerRef: OverlayStepperContainerComponent, + ) { + this.stepContainerRef.onFinish + .pipe(take(1)) + .subscribe(() => this.runOnFinishHooks) + } + + next(): Promise { + return this.stepContainerRef.next(); + } + + goBack(): Promise { + return this.stepContainerRef.goBack(); + } + + close(): Promise { + return this.stepContainerRef.close(); + } + + /** + * Save saves data of the current step in the stepper session. + * This data is saved in case the user decides to "go back" to + * to a previous step so the old view can be restored. + * + * @param data The data to save in the stepper session. + */ + save(data: T): void { + this.data = data; + } + + /** + * Load returns the data previously stored using save(). The + * StepperRef automatically makes sure the correct data is returned + * for the current step. + */ + load(): T | null { + return this.data; + } + + /** + * registerOnFinish registers fn to be called when the last step + * completes and the stepper is going to finish. + */ + registerOnFinish(fn: () => PromiseLike | void) { + this.onFinishHooks.push(fn); + } + + /** + * Executes all onFinishHooks in the order they have been defined + * and waits for each hook to complete. + */ + private async runOnFinishHooks() { + for (let i = 0; i < this.onFinishHooks.length; i++) { + let res = this.onFinishHooks[i](); + if (typeof res === 'object' && 'then' in res) { + // res is a PromiseLike so wait for it + try { + await res; + } catch (err) { + console.error(`Failed to execute on-finish hook of step ${this.currentStepIndex}: `, err) + } + } + } + } +} + + +export class StepperRef implements StepperControl { + constructor(private stepContainerRef: OverlayStepperContainerComponent) { } + + next(): Promise { + return this.stepContainerRef.next(); + } + + goBack(): Promise { + return this.stepContainerRef.goBack(); + } + + close(): Promise { + return this.stepContainerRef.close(); + } + + get onFinish(): Observable { + return this.stepContainerRef.onFinish; + } + + get onClose(): Observable { + return this.stepContainerRef.onClose; + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/step-outlet.ts b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/step-outlet.ts new file mode 100644 index 00000000..75bfab61 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/step-outlet.ts @@ -0,0 +1,90 @@ +import { animate, style, transition, trigger } from "@angular/animations"; +import { CdkPortalOutlet, ComponentPortal } from "@angular/cdk/portal"; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, Inject, InjectionToken, ViewChild } from "@angular/core"; +import { Step } from "./step"; + +export const STEP_PORTAL = new InjectionToken>('STEP_PORTAL') +export const STEP_ANIMATION_DIRECTION = new InjectionToken<'left' | 'right'>('STEP_ANIMATION_DIRECTION'); + +/** + * A simple wrapper component around CdkPortalOutlet to add nice + * move animations. + */ +@Component({ + template: ` +
+ +
+ `, + styles: [ + ` + :host{ + display: flex; + flex-direction: column; + overflow: hidden; + } + ` + ], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger( + 'moveInOut', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateX({{ in }})' }), + animate('.2s ease-in', + style({ opacity: 1, transform: 'translateX(0%)' })) + ], + { params: { in: '100%' } } // default parameters + ), + transition( + ':leave', + [ + style({ opacity: 1 }), + animate('.2s ease-out', + style({ opacity: 0, transform: 'translateX({{ out }})' })) + ], + { params: { out: '-100%' } } // default parameters + ) + ] + )] +}) +export class StepOutletComponent implements AfterViewInit { + /** @private - Whether or not the animation should run. */ + _appAnimate = false; + + /** The actual step instance that has been attached. */ + stepInstance: ComponentRef | null = null; + + /** @private - used in animation interpolation for translateX */ + get in() { + return this._animateDirection == 'left' ? '-100%' : '100%' + } + + /** @private - used in animation interpolation for traslateX */ + get out() { + return this._animateDirection == 'left' ? '100%' : '-100%' + } + + /** The portal outlet in our view used to attach the step */ + @ViewChild(CdkPortalOutlet, { static: true }) + portalOutlet!: CdkPortalOutlet; + + constructor( + @Inject(STEP_PORTAL) public portal: ComponentPortal, + @Inject(STEP_ANIMATION_DIRECTION) public _animateDirection: 'left' | 'right', + private cdr: ChangeDetectorRef + ) { } + + ngAfterViewInit(): void { + this.portalOutlet?.attached + .subscribe(ref => { + this.stepInstance = ref as ComponentRef; + + this._appAnimate = true; + this.cdr.detectChanges(); + }) + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/step.ts b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/step.ts new file mode 100644 index 00000000..1611ff15 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/overlay-stepper/step.ts @@ -0,0 +1,64 @@ +import { Injector, TemplateRef, Type } from "@angular/core"; +import { Observable } from "rxjs"; + +export interface Step { + /** + * validChange should emit true or false when the current step + * is valid and the "next" button should be visible. + */ + validChange: Observable; + + /** + * onBeforeBack, if it exists, is called when the user + * clicks the "Go Back" button but before the current step + * is unloaded. + * + * The OverlayStepper will wait for the callback to resolve or + * reject but will not abort going back! + */ + onBeforeBack?: () => Promise; + + /** + * onBeforeNext, if it exists, is called when the user + * clicks the "Next" button but before the current step + * is unloaded. + * + * The OverlayStepper willw ait for the callback to resolve + * or reject. If it rejects the current step will not be unloaded + * and the rejected error will be displayed to the user. + */ + onBeforeNext?: () => Promise; + + /** + * nextButtonLabel can overwrite the label for the "Next" button. + */ + nextButtonLabel?: string; + + /** + * buttonTemplate may hold a tempalte ref that is rendered instead + * of the default button row with a "Go Back" and a "Next" button. + * Note that if set, the step component must make sure to handle + * navigation itself. See {@class StepRef} for more information on how + * to control the stepper. + */ + buttonTemplate?: TemplateRef; +} + +export interface StepperConfig { + /** + * canAbort can be set to a function that is called + * for each step to determine if the stepper is abortable. + */ + canAbort?: (idx: number, step: Step) => boolean; + + /** steps holds the list of steps to execute */ + steps: Array> + + /** + * injector, if set, defines the parent injector used to + * create dedicated instances of the step types. + */ + injector?: Injector; +} + + diff --git a/desktop/angular/projects/safing/ui/src/lib/pagination/_pagination.scss b/desktop/angular/projects/safing/ui/src/lib/pagination/_pagination.scss new file mode 100644 index 00000000..46b8bdaf --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/pagination/_pagination.scss @@ -0,0 +1,22 @@ +sfng-pagination { + .pagination { + @apply my-2 w-full flex justify-between; + + button { + @apply text-xxs px-2 flex items-center justify-start; + + &.page { + @apply bg-cards-secondary; + @apply opacity-50; + + &:hover { + @apply opacity-100; + } + } + + &.active-page { + @apply text-blue font-medium opacity-100; + } + } + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/pagination/dynamic-items-paginator.ts b/desktop/angular/projects/safing/ui/src/lib/pagination/dynamic-items-paginator.ts new file mode 100644 index 00000000..b3f8a833 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/pagination/dynamic-items-paginator.ts @@ -0,0 +1,64 @@ + +import { BehaviorSubject, Observable, Subscription } from "rxjs"; +import { Pagination, clipPage } from "./pagination"; + +export interface Datasource { + // view should emit all items in the given page using the specified page number. + view(page: number, pageSize: number): Observable; +} + +export class DynamicItemsPaginator implements Pagination { + private _total = 0; + private _pageNumber$ = new BehaviorSubject(1); + private _pageItems$ = new BehaviorSubject([]); + private _pageLoading$ = new BehaviorSubject(false); + private _pageSubscription = Subscription.EMPTY; + + /** Returns the number of total pages. */ + get total() { return this._total; } + + /** Emits the current page number */ + get pageNumber$() { return this._pageNumber$.asObservable() } + + /** Emits all items of the current page */ + get pageItems$() { return this._pageItems$.asObservable() } + + /** Emits whether or not we're loading the next page */ + get pageLoading$() { return this._pageLoading$.asObservable() } + + constructor( + private source: Datasource, + public readonly pageSize = 25, + ) { } + + reset(newTotal: number) { + this._total = Math.ceil(newTotal / this.pageSize); + this.openPage(1); + } + + /** Clear resets the current total and emits an empty item set. */ + clear() { + this._total = 0; + this._pageItems$.next([]); + this._pageNumber$.next(1); + this._pageSubscription.unsubscribe(); + } + + openPage(pageNumber: number): void { + pageNumber = clipPage(pageNumber, this.total); + this._pageLoading$.next(true); + + this._pageSubscription.unsubscribe() + this._pageSubscription = this.source.view(pageNumber, this.pageSize) + .subscribe({ + next: results => { + this._pageLoading$.next(false); + this._pageItems$.next(results); + this._pageNumber$.next(pageNumber); + } + }); + } + + nextPage(): void { this.openPage(this._pageNumber$.getValue() + 1) } + prevPage(): void { this.openPage(this._pageNumber$.getValue() - 1) } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/pagination/index.ts b/desktop/angular/projects/safing/ui/src/lib/pagination/index.ts new file mode 100644 index 00000000..fb2f898c --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/pagination/index.ts @@ -0,0 +1,5 @@ +export * from './dynamic-items-paginator'; +export * from './pagination'; +export * from './pagination.module'; +export * from './snapshot-paginator'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.html b/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.html new file mode 100644 index 00000000..dec63df8 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.module.ts b/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.module.ts new file mode 100644 index 00000000..508454ca --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SfngPaginationContentDirective } from "."; +import { SfngPaginationWrapperComponent } from "./pagination"; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + SfngPaginationContentDirective, + SfngPaginationWrapperComponent, + ], + exports: [ + SfngPaginationContentDirective, + SfngPaginationWrapperComponent, + ], +}) +export class SfngPaginationModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.ts b/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.ts new file mode 100644 index 00000000..f3241cc9 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/pagination/pagination.ts @@ -0,0 +1,132 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Directive, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges, TemplateRef } from "@angular/core"; +import { Observable, Subscription } from "rxjs"; + +export interface Pagination { + /** + * Total should return the total number of pages + */ + total: number; + + /** + * pageNumber$ should emit the currently displayed page + */ + pageNumber$: Observable; + + /** + * pageItems$ should emit all items of the current page + */ + pageItems$: Observable; + + /** + * nextPage should progress to the next page. If there are no more + * pages than nextPage() should be a no-op. + */ + nextPage(): void; + + /** + * prevPage should move back the the previous page. If there is no + * previous page, prevPage should be a no-op. + */ + prevPage(): void; + + /** + * openPage opens the page @pageNumber. If pageNumber is greater than + * the total amount of pages it is clipped to the lastPage. If it is + * less than 1, it is clipped to 1. + */ + openPage(pageNumber: number): void +} + + + +@Directive({ + selector: '[sfngPageContent]' +}) +export class SfngPaginationContentDirective { + constructor(public readonly templateRef: TemplateRef) { } +} + +export interface PageChangeEvent { + totalPages: number; + currentPage: number; +} + +@Component({ + selector: 'sfng-pagination', + templateUrl: './pagination.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngPaginationWrapperComponent implements OnChanges, OnDestroy { + private _sub: Subscription = Subscription.EMPTY; + + @Input() + source: Pagination | null = null; + + @Output() + pageChange = new EventEmitter(); + + @ContentChild(SfngPaginationContentDirective) + content: SfngPaginationContentDirective | null = null; + + currentPageIdx: number = 0; + pageNumbers: number[] = []; + + ngOnChanges(changes: SimpleChanges) { + if ('source' in changes) { + this.subscribeToSource(changes.source.currentValue); + } + } + + ngOnDestroy() { + this._sub.unsubscribe(); + } + + private subscribeToSource(source: Pagination) { + // Unsubscribe from the previous pagination, if any + this._sub.unsubscribe(); + + this._sub = new Subscription(); + + this._sub.add( + source.pageNumber$ + .subscribe(current => { + this.currentPageIdx = current; + this.pageNumbers = generatePageNumbers(current - 1, source.total); + this.cdr.markForCheck(); + + this.pageChange.next({ + totalPages: source.total, + currentPage: current, + }) + }) + ) + } + + constructor(private cdr: ChangeDetectorRef) { } +} + +/** + * Generates an array of page numbers that should be displayed in paginations. + * + * @param current The current page number + * @param countPages The total number of pages + * @returns An array of page numbers to display + */ +export function generatePageNumbers(current: number, countPages: number): number[] { + let delta = 2; + let leftRange = current - delta; + let rightRange = current + delta + 1; + + return Array.from({ length: countPages }, (v, k) => k + 1) + .filter(i => i === 1 || i === countPages || (i >= leftRange && i < rightRange)); +} + +export function clipPage(pageNumber: number, total: number): number { + if (pageNumber < 1) { + return 1; + } + if (pageNumber > total) { + return total; + } + return pageNumber; +} diff --git a/desktop/angular/projects/safing/ui/src/lib/pagination/snapshot-paginator.ts b/desktop/angular/projects/safing/ui/src/lib/pagination/snapshot-paginator.ts new file mode 100644 index 00000000..7f014254 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/pagination/snapshot-paginator.ts @@ -0,0 +1,64 @@ +import { BehaviorSubject, Observable } from "rxjs"; +import { debounceTime, map } from "rxjs/operators"; +import { clipPage, Pagination } from "./pagination"; + +export class SnapshotPaginator implements Pagination { + private _itemSnapshot: T[] = []; + private _activePageItems = new BehaviorSubject([]); + private _totalPages = 1; + private _updatePending = false; + + constructor( + public items$: Observable, + public readonly pageSize: number, + ) { + items$ + .pipe(debounceTime(100)) + .subscribe(data => { + this._itemSnapshot = data; + this.openPage(this._currentPage.getValue()); + }); + + this._currentPage + .subscribe(page => { + this._updatePending = false; + const start = this.pageSize * (page - 1); + const end = this.pageSize * page; + this._totalPages = Math.ceil(this._itemSnapshot.length / this.pageSize) || 1; + this._activePageItems.next(this._itemSnapshot.slice(start, end)); + }) + } + + private _currentPage = new BehaviorSubject(0); + + get updatePending() { + return this._updatePending; + } + get pageNumber$(): Observable { + return this._activePageItems.pipe(map(() => this._currentPage.getValue())); + } + get pageNumber(): number { + return this._currentPage.getValue(); + } + get total(): number { + return this._totalPages + } + get pageItems$(): Observable { + return this._activePageItems.asObservable(); + } + get pageItems(): T[] { + return this._activePageItems.getValue(); + } + get snapshot(): T[] { return this._itemSnapshot }; + + reload(): void { this.openPage(this._currentPage.getValue()) } + + nextPage(): void { this.openPage(this._currentPage.getValue() + 1) } + + prevPage(): void { this.openPage(this._currentPage.getValue() - 1) } + + openPage(pageNumber: number): void { + pageNumber = clipPage(pageNumber, this.total); + this._currentPage.next(pageNumber); + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/select/_select.scss b/desktop/angular/projects/safing/ui/src/lib/select/_select.scss new file mode 100644 index 00000000..0d8cb345 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/select/_select.scss @@ -0,0 +1,73 @@ +.sfng-select { + @apply cursor-pointer relative p-0 flex whitespace-nowrap w-full items-center outline-none self-center overflow-hidden; + @apply hover:bg-gray-400; + @apply bg-gray-300 border border-gray-300 transition ease-in-out duration-200; + + &.disabled { + @apply cursor-not-allowed opacity-75 hover:bg-gray-400; + } + + min-width: 6rem; + max-width: 12rem; + + &.active { + @apply bg-gray-400; + + div.arrow svg { + @apply transform -rotate-90; + } + } + + & > span { + @apply flex-grow text-ellipsis inline-block overflow-hidden; + @apply px-2; + } + + div.arrow { + @apply flex flex-row items-center justify-center bg-gray-200 rounded-r-sm; + @apply w-5 h-7; + + svg { + @apply w-4 m-0 p-0 rotate-90 transform transition ease-in-out duration-100; + + g { + @apply text-white; + stroke: currentColor; + } + } + } +} + +.sfng-select-dropdown { + ul { + max-height: 12rem; + @apply relative py-1 overflow-auto; + + li { + @apply py-2; + @apply flex flex-row items-center justify-start gap-1 transition duration-200 ease-in-out cursor-pointer hover:bg-gray-300; + } + + li:not(.disabled) { + @apply hover:bg-gray-300; + } + + li.disabled { + @apply cursor-not-allowed; + } + } +} + +.sfng-select-dropdown.sfng-select-inline { + ul { + max-height: unset; + } +} + +sfng-select-item { + @apply text-xxs w-full font-medium gap-3 text-primary flex flex-row items-center justify-start; + + &.disabled { + @apply opacity-75 cursor-not-allowed; + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/select/index.ts b/desktop/angular/projects/safing/ui/src/lib/select/index.ts new file mode 100644 index 00000000..1342a276 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/select/index.ts @@ -0,0 +1,4 @@ +export * from './item'; +export * from './select'; +export * from './select.module'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/select/item.ts b/desktop/angular/projects/safing/ui/src/lib/select/item.ts new file mode 100644 index 00000000..b2eb5696 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/select/item.ts @@ -0,0 +1,64 @@ +import { ListKeyManagerOption } from '@angular/cdk/a11y'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Component, Directive, HostBinding, Input, Optional, TemplateRef } from '@angular/core'; + +export interface SelectOption extends ListKeyManagerOption { + value: any; + selected: boolean; + + data?: T; + label?: string; + description?: string; + templateRef?: TemplateRef; + disabled?: boolean; +} + +@Component({ + selector: 'sfng-select-item', + template: ``, +}) +export class SfngSelectItemComponent implements ListKeyManagerOption { + @HostBinding('class.disabled') + get disabled() { + return this.sfngSelectValue?.disabled || false; + } + + getLabel() { + return this.sfngSelectValue?.label || ''; + } + + constructor(@Optional() private sfngSelectValue: SfngSelectValueDirective) { } +} + +@Directive({ + selector: '[sfngSelectValue]', +}) +export class SfngSelectValueDirective implements SelectOption { + @Input('sfngSelectValue') + value: any; + + @Input('sfngSelectValueLabel') + label?: string; + + @Input('sfngSelectValueData') + data?: T; + + @Input('sfngSelectValueDescription') + description = ''; + + @Input('sfngSelectValueDisabled') + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v) + } + get disabled() { return this._disabled } + private _disabled = false; + + getLabel() { + return this.label || ('' + this.value); + } + + /** Whether or not the item is currently selected */ + selected = false; + + constructor(public templateRef: TemplateRef) { } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/select/select.html b/desktop/angular/projects/safing/ui/src/lib/select/select.html new file mode 100644 index 00000000..bccf19af --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/select/select.html @@ -0,0 +1,88 @@ + + + + + + + +
    +
  • + + + + + + + +
  • + + +
  • + + + Add {{ searchText }} + + +
  • +
+
+ + + + + + + +
+ +
+
+ + + + {{ data.label || data.value }} + diff --git a/desktop/angular/projects/safing/ui/src/lib/select/select.module.ts b/desktop/angular/projects/safing/ui/src/lib/select/select.module.ts new file mode 100644 index 00000000..d33fce4d --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/select/select.module.ts @@ -0,0 +1,31 @@ +import { CdkScrollableModule } from "@angular/cdk/scrolling"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { SfngDropDownModule } from "../dropdown"; +import { SfngTooltipModule } from "../tooltip"; +import { SfngSelectItemComponent, SfngSelectValueDirective } from "./item"; +import { SfngSelectComponent, SfngSelectRenderedItemDirective } from "./select"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + SfngDropDownModule, + SfngTooltipModule, + CdkScrollableModule + ], + declarations: [ + SfngSelectComponent, + SfngSelectValueDirective, + SfngSelectItemComponent, + SfngSelectRenderedItemDirective + ], + exports: [ + SfngSelectComponent, + SfngSelectValueDirective, + SfngSelectItemComponent, + ] +}) +export class SfngSelectModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/select/select.ts b/desktop/angular/projects/safing/ui/src/lib/select/select.ts new file mode 100644 index 00000000..9375f21f --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/select/select.ts @@ -0,0 +1,495 @@ +import { ListKeyManager, ListKeyManagerOption } from '@angular/cdk/a11y'; +import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, DestroyRef, Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnDestroy, Output, QueryList, TemplateRef, ViewChild, ViewChildren, forwardRef, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { BehaviorSubject, combineLatest } from 'rxjs'; +import { startWith } from 'rxjs/operators'; +import { SfngDropdownComponent } from '../dropdown'; +import { SelectOption, SfngSelectValueDirective } from './item'; + + +export type SelectModes = 'single' | 'multi'; + +type ModeInput = { + mode: SelectModes; +} + +type SelectValue = S['mode'] extends 'single' ? T : T[]; + +export type SortByFunc = (a: SelectOption, b: SelectOption) => number; + +export type SelectDisplayMode = 'dropdown' | 'inline'; + +@Directive({ + selector: '[sfngSelectRenderedListItem]' +}) +export class SfngSelectRenderedItemDirective implements ListKeyManagerOption { + @Input('sfngSelectRenderedListItem') + option: SelectOption | null = null; + + getLabel() { + return this.option?.label || ''; + } + + get disabled() { + return this.option?.disabled || false; + } + + @HostBinding('class.bg-gray-300') + set focused(v: boolean) { + this._focused = v; + } + get focused() { return this._focused } + private _focused = false; + + constructor(public readonly elementRef: ElementRef) { } +} + +@Component({ + selector: 'sfng-select', + templateUrl: './select.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SfngSelectComponent), + multi: true, + }, + ] +}) +export class SfngSelectComponent implements AfterViewInit, ControlValueAccessor, OnDestroy { + /** emits the search text entered by the user */ + private search$ = new BehaviorSubject(''); + + /** emits and completes when the component is destroyed. */ + private destroyRef = inject(DestroyRef); + + /** the key manager used for keyboard support */ + private keyManager!: ListKeyManager; + + @ViewChild(SfngDropdownComponent, { static: false }) + dropdown: SfngDropdownComponent | null = null; + + /** A reference to the cdk-scrollable directive that's placed on the item list */ + @ViewChild('scrollable', { read: ElementRef }) + scrollableList?: ElementRef; + + @ContentChildren(SfngSelectValueDirective) + userProvidedItems!: QueryList; + + @ViewChildren('renderedItem', { read: SfngSelectRenderedItemDirective }) + renderedItems!: QueryList; + + /** A list of all items available in the select box including dynamic ones. */ + allItems: SelectOption[] = [] + + /** The acutally rendered list of items after applying search and item threshold */ + items: SelectOption[] = []; + + @Input() + @HostBinding('attr.tabindex') + readonly tabindex = 0; + + @HostBinding('attr.role') + readonly role = 'listbox'; + + value?: SelectValue; + + /** A list of currently selected items */ + currentItems: SelectOption[] = []; + + /** The current search text. Used by ngModel */ + searchText = ''; + + /** Whether or not the select operates in "single" or "multi" mode */ + @Input() + mode: SelectModes = 'single'; + + @Input() + displayMode: SelectDisplayMode = 'dropdown'; + + /** The placehodler to show when nothing is selected */ + @Input() + placeholder = 'Select' + + /** The type of item to show in multi mode when more than one value is selected */ + @Input() + itemName = ''; + + /** The maximum number of items to render. */ + @Input() + set itemLimit(v: any) { + this._maxItemLimit = coerceNumberProperty(v) + } + get itemLimit(): number { return this._maxItemLimit } + private _maxItemLimit = Infinity; + + /** The placeholder text for the search bar */ + @Input() + searchPlaceholder = ''; + + /** Whether or not the search bar is visible */ + @Input() + set allowSearch(v: any) { + this._allowSearch = coerceBooleanProperty(v); + } + get allowSearch(): boolean { + return this._allowSearch; + } + private _allowSearch = false; + + /** The minimum number of items required for the search bar to be visible */ + @Input() + set searchItemThreshold(v: any) { + this._searchItemThreshold = coerceNumberProperty(v); + } + get searchItemThreshold(): number { + return this._searchItemThreshold; + } + private _searchItemThreshold = 0; + + /** + * Whether or not the select should be disabled when not options + * are available. + */ + @Input() + set disableWhenEmpty(v: any) { + this._disableWhenEmpty = coerceBooleanProperty(v); + } + get disableWhenEmpty() { + return this._disableWhenEmpty; + } + private _disableWhenEmpty = false; + + /** Whether or not the select component will add options for dynamic values as well. */ + @Input() + set dynamicValues(v: any) { + this._dynamicValues = coerceBooleanProperty(v); + } + get dynamicValues() { + return this._dynamicValues + } + private _dynamicValues = false; + + /** An optional template to use for dynamic values. */ + @Input() + dynamicValueTemplate?: TemplateRef; + + /** The minimum-width of the drop-down. See {@link SfngDropdownComponent.minWidth} */ + @Input() + minWidth: any; + + /** The minimum-width of the drop-down. See {@link SfngDropdownComponent.minHeight} */ + @Input() + minHeight: any; + + /** Whether or not selected items should be sorted to the top */ + @Input() + set sortValues(v: any) { + this._sortValues = coerceBooleanProperty(v); + } + get sortValues() { + if (this._sortValues === null) { + return this.mode === 'multi'; + } + return this._sortValues; + } + private _sortValues: boolean | null = null; + + /** The sort function to use. Defaults to sort by label/value */ + @Input() + sortBy: SortByFunc = (a: SelectOption, b: SelectOption) => { + if ((a.label || a.value) < (b.label || b.value)) { + return 1; + } + if ((a.label || a.value) > (b.label || b.value)) { + return -1; + } + + return 0; + } + + @Input() + set disabled(v: any) { + const disabled = coerceBooleanProperty(v); + this.setDisabledState(disabled); + } + get disabled() { + return this._disabled; + } + private _disabled: boolean = false; + + @HostListener('keydown.enter', ['$event']) + @HostListener('keydown.space', ['$event']) + onEnter(event: Event) { + if (!this.dropdown?.isOpen) { + this.dropdown?.toggle() + + event.preventDefault(); + event.stopPropagation(); + + return; + } + + if (this.keyManager.activeItem !== null && !!this.keyManager.activeItem?.option) { + this.selectItem(this.keyManager.activeItem.option) + + event.preventDefault(); + event.stopPropagation(); + + return; + } + } + + @HostListener('keydown', ['$event']) + onKeyDown(event: KeyboardEvent) { + this.keyManager.onKeydown(event); + } + + @Output() + closed = new EventEmitter(); + + @Output() + opened = new EventEmitter(); + + trackItem(_: number, item: SelectOption) { + return item.value; + } + + setDisabledState(disabled: boolean) { + this._disabled = disabled; + this.cdr.markForCheck(); + } + + constructor(private cdr: ChangeDetectorRef) { } + + ngAfterViewInit(): void { + this.keyManager = new ListKeyManager(this.renderedItems) + .withVerticalOrientation() + .withHomeAndEnd() + .withWrap() + .withTypeAhead(); + + this.keyManager.change + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(itemIdx => { + this.renderedItems.forEach(item => { + item.focused = false; + }) + + this.keyManager.activeItem!.focused = true; + + // the item might be out-of-view so make sure + // we scroll enough to have it inside the view + const scrollable = this.scrollableList?.nativeElement; + if (!!scrollable) { + const active = this.keyManager.activeItem!.elementRef.nativeElement; + const activeHeight = active.getBoundingClientRect().height; + const bottom = scrollable.scrollTop + scrollable.getBoundingClientRect().height; + const top = scrollable.scrollTop; + + let scrollTo = -1; + if (active.offsetTop >= bottom) { + scrollTo = top + active.offsetTop - bottom + activeHeight; + } else if (active.offsetTop < top) { + scrollTo = active.offsetTop; + } + + if (scrollTo > -1) { + scrollable.scrollTo({ + behavior: 'smooth', + top: scrollTo, + }) + } + } + + this.cdr.markForCheck(); + }) + + + combineLatest([ + this.userProvidedItems!.changes + .pipe(startWith(undefined)), + this.search$ + ]) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe( + ([_, search]) => { + this.updateItems(); + + search = (search || '').toLocaleLowerCase() + let items: SelectOption[] = []; + if (search === '') { + items = this.allItems!; + } else { + items = this.allItems!.filter(item => { + // we always count selected items as a "match" in search mode. + // this is to ensure the user always see all selected items. + if (item.selected) { + return true; + } + + if (!!item.value && typeof item.value === 'string') { + if (item.value.toLocaleLowerCase().includes(search)) { + return true; + } + } + + if (!!item.label) { + if (item.label.toLocaleLowerCase().includes(search)) { + return true + } + } + return false; + }) + } + + this.items = items.slice(0, this._maxItemLimit); + this.keyManager.setActiveItem(0); + + this.cdr.detectChanges(); + } + ); + } + + ngOnDestroy(): void { + this.search$.complete(); + } + + @HostListener('blur') + onBlur(): void { + this.onTouch(); + } + + /** @private - called when the internal dropdown opens */ + onDropdownOpen() { + // emit the open event on this component as well + this.opened.next(); + + // reset the search. We do that when opened instead of closed + // to avoid flickering when the component height increases + // during the "close" animation + this.onSearch(''); + } + + /** @private - called when the internal dropdown closes */ + onDropdownClose() { + this.closed.next(); + } + + onSearch(text: string) { + this.searchText = text; + this.search$.next(text); + } + + selectItem(item: SelectOption) { + if (item.disabled) { + return; + } + + const isSelected = this.currentItems.findIndex(selected => item.value === selected.value); + if (isSelected === -1) { + item.selected = true; + + if (this.mode === 'single') { + this.currentItems.forEach(i => i.selected = false); + this.currentItems = [item]; + this.value = item.value; + } else { + this.currentItems.push(item); + // TODO(ppacher): somehow typescript does not correctly pick up + // the type of this.value here although it can be infered from the + // mode === 'single' check above. + this.value = [ + ...(this.value || []) as any, + item.value, + ] as any + } + } else if (this.mode !== 'single') { // "unselecting" a value is not allowed in single mode + this.currentItems.splice(isSelected, 1) + item.selected = false; + // same note about typescript as above. + this.value = (this.value as T[]).filter(val => val !== item.value) as any; + } + + // only close the drop down in single mode. In multi-mode + // we keep it open as the user might want to select an additional + // item as well. + if (this.mode === 'single') { + this.dropdown?.close(); + } + this.onChange(this.value!); + } + + private updateItems() { + let values: T[] = []; + if (this.mode === 'single') { + values = [this.value as T]; + } else { + values = (this.value as T[]) || []; + } + + this.currentItems = []; + this.allItems = []; + + // mark all user-selected items as "deselected" first + this.userProvidedItems?.forEach(item => { + item.selected = false; + this.allItems.push(item); + }); + + for (let i = 0; i < values.length; i++) { + const val = values[i]; + let option: SelectOption | undefined = this.userProvidedItems?.find(item => item.value === val); + if (!option) { + if (!this._dynamicValues) { + continue + } + + option = { + selected: true, + value: val, + label: `${val}`, + } + this.allItems.push(option); + } else { + option.selected = true + } + + this.currentItems.push(option); + } + + if (this.sortValues) { + this.allItems.sort((a, b) => { + if (b.selected && !a.selected) { + return 1; + } + + if (a.selected && !b.selected) { + return -1; + } + + return this.sortBy(a, b) + }) + } + } + + writeValue(value: SelectValue): void { + this.value = value; + + this.updateItems(); + + this.cdr.markForCheck(); + } + + onChange = (value: SelectValue): void => { } + registerOnChange(fn: (value: SelectValue) => void): void { + this.onChange = fn; + } + + onTouch = (): void => { } + registerOnTouched(fn: () => void): void { + this.onTouch = fn; + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tabs/_tab-group.scss b/desktop/angular/projects/safing/ui/src/lib/tabs/_tab-group.scss new file mode 100644 index 00000000..4d63b670 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tabs/_tab-group.scss @@ -0,0 +1,3 @@ +sfng-tab-group { + @apply flex flex-col overflow-hidden; +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tabs/index.ts b/desktop/angular/projects/safing/ui/src/lib/tabs/index.ts new file mode 100644 index 00000000..4fd3296a --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tabs/index.ts @@ -0,0 +1,4 @@ +export { SfngTabComponent, SfngTabContentDirective } from './tab'; +export { SfngTabContentScrollEvent, SfngTabGroupComponent } from './tab-group'; +export { SfngTabModule as TabModule } from './tabs.module'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/tabs/tab-group.html b/desktop/angular/projects/safing/ui/src/lib/tabs/tab-group.html new file mode 100644 index 00000000..f78ff738 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tabs/tab-group.html @@ -0,0 +1,24 @@ +
+ + +
+ + {{ tab.title }} + + + + + +
+ + +
+
+ + diff --git a/desktop/angular/projects/safing/ui/src/lib/tabs/tab-group.ts b/desktop/angular/projects/safing/ui/src/lib/tabs/tab-group.ts new file mode 100644 index 00000000..e4a65f6e --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tabs/tab-group.ts @@ -0,0 +1,352 @@ +import { ListKeyManager } from "@angular/cdk/a11y"; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { CdkPortalOutlet, ComponentPortal } from "@angular/cdk/portal"; +import { AfterContentInit, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, ContentChildren, DestroyRef, ElementRef, EventEmitter, Injector, Input, OnInit, Output, QueryList, ViewChild, ViewChildren, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute, Router } from "@angular/router"; +import { Observable, Subject } from "rxjs"; +import { distinctUntilChanged, map, startWith } from "rxjs/operators"; +import { SfngTabComponent, TAB_ANIMATION_DIRECTION, TAB_PORTAL, TAB_SCROLL_HANDLER, TabOutletComponent } from "./tab"; + +export interface SfngTabContentScrollEvent { + event?: Event; + scrollTop: number; + previousScrollTop: number; +} + +/** + * Tab group component for rendering a tab-style navigation with support for + * keyboard navigation and type-ahead. Tab content are lazy loaded using a + * structural directive. + * The tab group component also supports adding the current active tab index + * to the active route so it is possible to navigate through tabs using back/forward + * keys (browser history) as well. + * + * Example: + * + * + * + *
+ * Some content + *
+ *
+ * + * + *
+ * Some different content + *
+ *
+ * + *
+ */ +@Component({ + selector: 'sfng-tab-group', + templateUrl: './tab-group.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngTabGroupComponent implements AfterContentInit, AfterViewInit, OnInit { + @ContentChildren(SfngTabComponent) + tabs: QueryList | null = null; + + /** References to all tab header elements */ + @ViewChildren('tabHeader', { read: ElementRef }) + tabHeaders: QueryList> | null = null; + + /** Reference to the active tab bar element */ + @ViewChild('activeTabBar', { read: ElementRef, static: false }) + activeTabBar: ElementRef | null = null; + + /** Reference to the portal outlet that we will use to render a TabOutletComponent. */ + @ViewChild(CdkPortalOutlet, { static: true }) + portalOutlet: CdkPortalOutlet | null = null; + + @Output() + tabContentScroll = new EventEmitter(); + + /** The name of the tab group. Used to update the currently active tab in the route */ + @Input() + name = 'tab' + + @Input() + outletClass = ''; + + private scrollTop: number = 0; + + /** Whether or not the current tab should be syncronized with the angular router using a query parameter */ + @Input() + set linkRouter(v: any) { + this._linkRouter = coerceBooleanProperty(v) + } + get linkRouter() { return this._linkRouter } + private _linkRouter = true; + + /** Whether or not the default tab header should be rendered */ + @Input() + set customHeader(v: any) { + this._customHeader = coerceBooleanProperty(v) + } + get customHeader() { return this._customHeader } + private _customHeader = false; + + private tabActivate$ = new Subject(); + private destroyRef = inject(DestroyRef); + + /** Emits the tab QueryList every time there are changes to the content-children */ + get tabs$() { + return this.tabs?.changes + .pipe( + map(() => this.tabs), + startWith(this.tabs) + ) + } + + /** onActivate fires when a tab has been activated. */ + get onActivate(): Observable { return this.tabActivate$.asObservable() } + + /** the index of the currently active tab. */ + activeTabIndex = -1; + + /** The key manager used to support keyboard navigation and type-ahead in the tab group */ + private keymanager: ListKeyManager | null = null; + + /** Used to force the animation direction when calling activateTab. */ + private forceAnimationDirection: 'left' | 'right' | null = null; + + /** + * pendingTabIdx holds the id or the index of a tab that should be activated after the component + * has been bootstrapped. We need to cache this value here because the ActivatedRoute might emit + * before we are AfterViewInit. + */ + private pendingTabIdx: string | null = null; + + constructor( + private injector: Injector, + private route: ActivatedRoute, + private router: Router, + private cdr: ChangeDetectorRef + ) { } + + /** + * @private + * Used to forward keyboard events to the keymanager. + */ + onKeydown(v: KeyboardEvent) { + this.keymanager?.onKeydown(v); + } + + ngOnInit(): void { + this.route.queryParamMap + .pipe( + takeUntilDestroyed(this.destroyRef), + map(params => params.get(this.name)), + distinctUntilChanged(), + ) + .subscribe(newIdx => { + if (!this._linkRouter) { + return; + } + + if (!!this.keymanager && !!this.tabs) { + const actualIndex = this.getIndex(newIdx); + if (actualIndex !== null) { + this.keymanager.setActiveItem(actualIndex); + this.cdr.markForCheck(); + } + } else { + this.pendingTabIdx = newIdx; + } + }) + } + + ngAfterContentInit(): void { + this.keymanager = new ListKeyManager(this.tabs!) + .withHomeAndEnd() + .withHorizontalOrientation("ltr") + .withTypeAhead() + .withWrap() + + this.tabs!.changes + .subscribe(() => { + if (this.portalOutlet?.hasAttached()) { + if (this.tabs!.length === 0) { + this.portalOutlet.detach(); + } + } else { + if (this.tabs!.length > 0) { + this.activateTab(0) + } + } + + }) + + this.keymanager.change + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(change => { + const activeTab = this.tabs!.get(change); + if (!!activeTab && !!activeTab.tabContent) { + const prevIdx = this.activeTabIndex; + + let animationDirection: 'left' | 'right' = prevIdx < change ? 'left' : 'right'; + if (this.forceAnimationDirection !== null) { + animationDirection = this.forceAnimationDirection; + this.forceAnimationDirection = null; + } + + if (this.portalOutlet?.attachedRef) { + // we know for sure that attachedRef is a ComponentRef of TabOutletComponent + const ref = (this.portalOutlet.attachedRef as ComponentRef) + ref.instance._animateDirection = animationDirection; + ref.instance.outletClass = this.outletClass; + ref.changeDetectorRef.detectChanges(); + } + + this.portalOutlet?.detach(); + + const newOutletPortal = this.createTabOutlet(activeTab, animationDirection); + this.activeTabIndex = change; + this.tabContentScroll.next({ + scrollTop: 0, + previousScrollTop: this.scrollTop, + }) + + this.scrollTop = 0; + + this.tabActivate$.next(activeTab.id); + this.portalOutlet?.attach(newOutletPortal); + + this.repositionTabBar(); + + if (this._linkRouter) { + this.router.navigate([], { + queryParams: { + ...this.route.snapshot.queryParams, + [this.name]: this.activeTabIndex, + } + }) + } + this.cdr.markForCheck(); + } + }); + + if (this.pendingTabIdx === null) { + // active the first tab that is NOT disabled + const firstActivatable = this.tabs?.toArray().findIndex(tap => !tap.disabled); + if (firstActivatable !== undefined) { + this.keymanager.setActiveItem(firstActivatable); + } + } else { + const idx = this.getIndex(this.pendingTabIdx); + if (idx !== null) { + this.keymanager.setActiveItem(idx); + this.pendingTabIdx = null; + } + } + } + + ngAfterViewInit(): void { + this.repositionTabBar(); + this.tabHeaders?.changes.subscribe(() => this.repositionTabBar()) + setTimeout(() => this.repositionTabBar(), 250) + } + + /** + * @private + * Activates a new tab + * + * @param idx The index of the new tab. + */ + activateTab(idx: number, forceDirection?: 'left' | 'right') { + if (forceDirection !== undefined) { + this.forceAnimationDirection = forceDirection; + } + + this.keymanager?.setActiveItem(idx); + } + + private getIndex(newIdx: string | null): number | null { + let actualIndex: number = -1; + if (!this.tabs) { + return null; + } + + if (newIdx === undefined || newIdx === null) { // not present in the URL + return null; + } + if (isNaN(+newIdx)) { // likley the ID of a tab + actualIndex = this.tabs?.toArray().findIndex(tab => tab.id === newIdx) || -1; + } else { // it's a number as a string + actualIndex = +newIdx; + } + + if (actualIndex < 0) { + return null; + } + return actualIndex; + } + + private repositionTabBar() { + if (!this.tabHeaders) { + return; + } + + requestAnimationFrame(() => { + const tabHeader = this.tabHeaders!.get(this.activeTabIndex); + if (!tabHeader || !this.activeTabBar) { + return; + } + const rect = tabHeader.nativeElement.getBoundingClientRect(); + const transform = `translate(${tabHeader.nativeElement.offsetLeft}px, ${tabHeader.nativeElement.offsetTop + rect.height}px)` + this.activeTabBar.nativeElement.style.width = `${rect.width}px` + this.activeTabBar.nativeElement.style.transform = transform; + this.activeTabBar.nativeElement.style.opacity = '1'; + + // initialize animations on the active-tab-bar required + if (!this.activeTabBar.nativeElement.classList.contains("transition-all")) { + // only initialize the transitions if this is the very first "reposition" + // this is to prevent the bar from animating to the "bottom" line of the tab + // header the first time. + requestAnimationFrame(() => { + this.activeTabBar?.nativeElement.classList.add("transition-all", "duration-200"); + }) + } + }) + } + + private createTabOutlet(tab: SfngTabComponent, animationDir: 'left' | 'right'): ComponentPortal { + const injector = Injector.create({ + providers: [ + { + provide: TAB_PORTAL, + useValue: tab.tabContent!.portal, + }, + { + provide: TAB_ANIMATION_DIRECTION, + useValue: animationDir, + }, + { + provide: TAB_SCROLL_HANDLER, + useValue: (e: Event) => { + const newScrollTop = (e.target as HTMLElement).scrollTop; + + tab.tabContentScroll.next(e); + this.tabContentScroll.next({ + event: e, + scrollTop: newScrollTop, + previousScrollTop: this.scrollTop, + }); + + this.scrollTop = newScrollTop; + } + }, + ], + parent: this.injector, + name: 'TabOutletInjectot', + }) + + return new ComponentPortal( + TabOutletComponent, + undefined, + injector + ) + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tabs/tab.ts b/desktop/angular/projects/safing/ui/src/lib/tabs/tab.ts new file mode 100644 index 00000000..31f71226 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tabs/tab.ts @@ -0,0 +1,167 @@ +import { animate, style, transition, trigger } from "@angular/animations"; +import { ListKeyManagerOption } from "@angular/cdk/a11y"; +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { CdkPortalOutlet, TemplatePortal } from "@angular/cdk/portal"; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Directive, EventEmitter, Inject, InjectionToken, Input, Output, TemplateRef, ViewChild, ViewContainerRef } from "@angular/core"; + +/** TAB_PORTAL is the injection token used to inject the TabContentDirective portal into TabOutletComponent */ +export const TAB_PORTAL = new InjectionToken('TAB_PORTAL'); + +/** TAB_ANIMATION_DIRECTION is the injection token used to control the :enter animation origin of TabOutletComponent */ +export const TAB_ANIMATION_DIRECTION = new InjectionToken<'left' | 'right'>('TAB_ANIMATION_DIRECTION'); + +/** TAB_SCROLL_HANDLER is called by the SfngTabOutletComponent when a scroll event occurs. */ +export const TAB_SCROLL_HANDLER = new InjectionToken<(_: Event) => void>('TAB_SCROLL_HANDLER') + +/** + * Structural directive (*sfngTabContent) to defined lazy-loaded tab content. + */ +@Directive({ + selector: '[sfngTabContent]', +}) +export class SfngTabContentDirective { + portal: TemplatePortal; + + constructor( + public readonly templateRef: TemplateRef, + public readonly viewRef: ViewContainerRef, + ) { + this.portal = new TemplatePortal(this.templateRef, this.viewRef); + } +} + + +/** + * The tab component that is used to define a new tab as a part of a tab group. + * The content of the tab is lazy-loaded by using the TabContentDirective. + */ +@Component({ + selector: 'sfng-tab', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngTabComponent implements ListKeyManagerOption { + @ContentChild(SfngTabContentDirective, { static: false }) + tabContent: SfngTabContentDirective | null = null; + + /** The ID of the tab used to programatically activate the tab. */ + @Input() + id = ''; + + /** The title for the tab as displayed in the tab group header. */ + @Input() + title = ''; + + /** The key for the tip up in the tab group header. */ + @Input() + tipUpKey = ''; + + @Input() + set warning(v) { + this._warning = coerceBooleanProperty(v) + } + get warning() { return this._warning } + private _warning = false; + + /** Emits when the tab content is scrolled */ + @Output() + tabContentScroll = new EventEmitter(); + + /** Whether or not the tab is currently disabled. */ + @Input() + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v); + } + get disabled() { + return this._disabled; + } + private _disabled: boolean = false; + + /** getLabel is used by the list key manager to support type-ahead */ + getLabel() { return this.title } +} + + +/** + * A simple wrapper component around CdkPortalOutlet to add nice + * move animations. + */ +@Component({ + selector: 'sfng-tab-outlet', + template: ` +
+ +
+ `, + styles: [ + ` + :host{ + display: flex; + flex-direction: column; + overflow: hidden; + } + ` + ], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger( + 'moveInOut', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateX({{ in }})' }), + animate('.2s ease-in', + style({ opacity: 1, transform: 'translateX(0%)' })) + ], + { params: { in: '100%' } } // default parameters + ), + transition( + ':leave', + [ + style({ opacity: 1 }), + animate('.2s ease-out', + style({ opacity: 0, transform: 'translateX({{ out }})' })) + ], + { params: { out: '-100%' } } // default parameters + ) + ] + )] +}) +export class TabOutletComponent implements AfterViewInit { + _appAnimate = false; + + @Input() + outletClass = '' + + get in() { + return this._animateDirection == 'left' ? '100%' : '-100%' + } + get out() { + return this._animateDirection == 'left' ? '-100%' : '100%' + } + + onTabContentScroll(event: Event) { + if (!!this.scrollHandler) { + this.scrollHandler(event) + } + } + + @ViewChild(CdkPortalOutlet, { static: true }) + portalOutlet!: CdkPortalOutlet; + + constructor( + @Inject(TAB_PORTAL) public portal: TemplatePortal, + @Inject(TAB_ANIMATION_DIRECTION) public _animateDirection: 'left' | 'right', + @Inject(TAB_SCROLL_HANDLER) public scrollHandler: (_: Event) => void, + private cdr: ChangeDetectorRef + ) { } + + ngAfterViewInit(): void { + this.portalOutlet?.attached + .subscribe(() => { + this._appAnimate = true; + this.cdr.detectChanges(); + }) + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tabs/tabs.module.ts b/desktop/angular/projects/safing/ui/src/lib/tabs/tabs.module.ts new file mode 100644 index 00000000..e1540cb4 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tabs/tabs.module.ts @@ -0,0 +1,28 @@ +import { PortalModule } from "@angular/cdk/portal"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { SfngTipUpModule } from "../tipup"; +import { SfngTabComponent, SfngTabContentDirective, TabOutletComponent } from "./tab"; +import { SfngTabGroupComponent } from "./tab-group"; + +@NgModule({ + imports: [ + CommonModule, + PortalModule, + SfngTipUpModule, + BrowserAnimationsModule + ], + declarations: [ + SfngTabContentDirective, + SfngTabComponent, + SfngTabGroupComponent, + TabOutletComponent, + ], + exports: [ + SfngTabContentDirective, + SfngTabComponent, + SfngTabGroupComponent + ] +}) +export class SfngTabModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/_tipup.scss b/desktop/angular/projects/safing/ui/src/lib/tipup/_tipup.scss new file mode 100644 index 00000000..b6b93040 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/_tipup.scss @@ -0,0 +1,52 @@ +sfng-tipup-container { + display: block; + + caption { + @apply text-sm; + opacity: .6; + font-size: .6rem; + } + + h1 { + font-size: 0.85rem; + font-weight: 500; + margin-bottom: 1rem; + } + + .message, + h1 { + flex-shrink: 0; + text-overflow: ellipsis; + word-break: normal; + } + + .message { + font-size: 0.75rem; + flex-grow: 1; + opacity: .8; + max-width: 300px; + padding: 0; + } + + .close-icon { + position: absolute; + top: 1rem; + right: 1rem; + opacity: .7; + cursor: pointer; + + &:hover { + opacity: 1; + } + } + + .buttons { + width: 100%; + display: flex; + justify-content: space-between; + } + + a { + text-decoration: underline; + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/anchor.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/anchor.ts new file mode 100644 index 00000000..986d0378 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/anchor.ts @@ -0,0 +1,43 @@ +import { Directive, ElementRef, HostBinding, Input, isDevMode } from "@angular/core"; +import { SfngTipUpPlacement } from "./utils"; + +@Directive({ + selector: '[sfngTipUpAnchor]', +}) +export class SfngTipUpAnchorDirective implements SfngTipUpPlacement { + constructor( + public readonly elementRef: ElementRef, + ) { } + + origin: 'left' | 'right' = 'right'; + offset: number = 10; + + @HostBinding('class.active-tipup-anchor') + isActiveAnchor = false; + + @Input() + set sfngTipUpAnchor(posSpec: string | undefined) { + const parts = (posSpec || '').split(';') + if (parts.length > 2) { + if (isDevMode()) { + throw new Error(`Invalid value "${posSpec}" for [sfngTipUpAnchor]`); + } + return; + } + + if (parts[0] === 'left') { + this.origin = 'left'; + } else { + this.origin = 'right'; + } + + if (parts.length === 2) { + this.offset = +parts[1]; + if (isNaN(this.offset)) { + this.offset = 10; + } + } else { + this.offset = 10; + } + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/clone-node.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/clone-node.ts new file mode 100644 index 00000000..e0550060 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/clone-node.ts @@ -0,0 +1,128 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** Creates a deep clone of an element. */ +export function deepCloneNode(node: HTMLElement): HTMLElement { + const clone = node.cloneNode(true) as HTMLElement; + const descendantsWithId = clone.querySelectorAll('[id]'); + const nodeName = node.nodeName.toLowerCase(); + + // Remove the `id` to avoid having multiple elements with the same id on the page. + clone.removeAttribute('id'); + + for (let i = 0; i < descendantsWithId.length; i++) { + descendantsWithId[i].removeAttribute('id'); + } + + if (nodeName === 'canvas') { + transferCanvasData(node as HTMLCanvasElement, clone as HTMLCanvasElement); + } else if (nodeName === 'input' || nodeName === 'select' || nodeName === 'textarea') { + transferInputData(node as HTMLInputElement, clone as HTMLInputElement); + } + + transferData('canvas', node, clone, transferCanvasData); + transferData('input, textarea, select', node, clone, transferInputData); + return clone; +} + +/** Matches elements between an element and its clone and allows for their data to be cloned. */ +function transferData(selector: string, node: HTMLElement, clone: HTMLElement, + callback: (source: T, clone: T) => void) { + const descendantElements = node.querySelectorAll(selector); + + if (descendantElements.length) { + const cloneElements = clone.querySelectorAll(selector); + + for (let i = 0; i < descendantElements.length; i++) { + callback(descendantElements[i], cloneElements[i]); + } + } +} + +// Counter for unique cloned radio button names. +let cloneUniqueId = 0; + +/** Transfers the data of one input element to another. */ +function transferInputData(source: Element & { value: string }, + clone: Element & { value: string; name: string; type: string }) { + // Browsers throw an error when assigning the value of a file input programmatically. + if (clone.type !== 'file') { + clone.value = source.value; + } + + // Radio button `name` attributes must be unique for radio button groups + // otherwise original radio buttons can lose their checked state + // once the clone is inserted in the DOM. + if (clone.type === 'radio' && clone.name) { + clone.name = `sfng-clone-${clone.name}-${cloneUniqueId++}`; + } +} + +/** Transfers the data of one canvas element to another. */ +function transferCanvasData(source: HTMLCanvasElement, clone: HTMLCanvasElement) { + const context = clone.getContext('2d'); + + if (context) { + // In some cases `drawImage` can throw (e.g. if the canvas size is 0x0). + // We can't do much about it so just ignore the error. + try { + context.drawImage(source, 0, 0); + } catch { } + } +} + +/** + * Gets a 3d `transform` that can be applied to an element. + * @param x Desired position of the element along the X axis. + * @param y Desired position of the element along the Y axis. + */ +export function getTransform(x: number, y: number): string { + // Round the transforms since some browsers will + // blur the elements for sub-pixel transforms. + return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`; +} + +/** + * Matches the target element's size to the source's size. + * @param target Element that needs to be resized. + * @param sourceRect Dimensions of the source element. + */ +export function matchElementSize(target: HTMLElement, sourceRect: ClientRect): void { + target.style.width = `${sourceRect.width}px`; + target.style.height = `${sourceRect.height}px`; + target.style.transform = getTransform(sourceRect.left, sourceRect.top); +} + +/** + * Shallow-extends a stylesheet object with another stylesheet-like object. + * Note that the keys in `source` have to be dash-cased. + */ +export function extendStyles(dest: CSSStyleDeclaration, + source: Record, + importantProperties?: Set) { + for (let key in source) { + if (source.hasOwnProperty(key)) { + const value = source[key]; + + if (value) { + dest.setProperty(key, value, importantProperties?.has(key) ? 'important' : ''); + } else { + dest.removeProperty(key); + } + } + } + + return dest; +} + +export function removeNode(node: Node | null) { + if (node && node.parentNode) { + node.parentNode.removeChild(node); + } +} + diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/css-utils.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/css-utils.ts new file mode 100644 index 00000000..8f58dff2 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/css-utils.ts @@ -0,0 +1,87 @@ + +export function synchronizeCssStyles(src: HTMLElement, destination: HTMLElement, skipStyles: Set) { + // Get a list of all the source and destination elements + const srcElements = >src.getElementsByTagName('*'); + const dstElements = >destination.getElementsByTagName('*'); + + cloneStyle(src, destination, skipStyles); + + // For each element + for (let i = srcElements.length; i--;) { + const srcElement = srcElements[i]; + const dstElement = dstElements[i]; + cloneStyle(srcElement, dstElement, skipStyles); + } +} + +function cloneStyle(srcElement: HTMLElement, dstElement: HTMLElement, skipStyles: Set) { + const sourceElementStyles = document.defaultView!.getComputedStyle(srcElement, ''); + const styleAttributeKeyNumbers = Object.keys(sourceElementStyles); + + // Copy the attribute + for (let j = 0; j < styleAttributeKeyNumbers.length; j++) { + const attributeKeyNumber = styleAttributeKeyNumbers[j]; + const attributeKey: string = sourceElementStyles[attributeKeyNumber as any]; + if (!isNaN(+attributeKey)) { + continue + } + if (attributeKey === 'cssText') { + continue + } + + if (skipStyles.has(attributeKey)) { + continue + } + + try { + dstElement.style[attributeKey as any] = sourceElementStyles[attributeKey as any]; + } catch (e) { + console.error(attributeKey, e); + } + } +} + +/** + * Returns a CSS selector for el from rootNode. + * + * @param el The source element to get the CSS path to + * @param rootNode The root node at which the CSS path should be applyable + * @returns A CSS selector to access el from rootNode. + */ +export function getCssSelector(el: HTMLElement, rootNode: HTMLElement | null): string { + if (!el) { + return ''; + } + let stack = []; + let isShadow = false; + while (el !== rootNode && el.parentNode !== null) { + // console.log(el.nodeName); + let sibCount = 0; + let sibIndex = 0; + // get sibling indexes + for (let i = 0; i < (el.parentNode as HTMLElement).childNodes.length; i++) { + let sib = (el.parentNode as HTMLElement).childNodes[i]; + if (sib.nodeName == el.nodeName) { + if (sib === el) { + sibIndex = sibCount; + } + sibCount++; + } + } + let nodeName = el.nodeName.toLowerCase(); + if (isShadow) { + throw new Error(`cannot traverse into shadow dom.`) + } + if (sibCount > 1) { + stack.unshift(nodeName + ':nth-of-type(' + (sibIndex + 1) + ')'); + } else { + stack.unshift(nodeName); + } + el = el.parentNode as HTMLElement; + if (el.nodeType === 11) { // for shadow dom, we + isShadow = true; + el = (el as any).host; + } + } + return stack.join(' > '); +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/index.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/index.ts new file mode 100644 index 00000000..bd600272 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/index.ts @@ -0,0 +1,6 @@ +export * from './anchor'; +export * from './tipup'; +export * from './tipup-component'; +export * from './tipup.module'; +export * from './translations'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/safe.pipe.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/safe.pipe.ts new file mode 100644 index 00000000..0cbf2855 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/safe.pipe.ts @@ -0,0 +1,21 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeHtml, SafeStyle, SafeScript, SafeUrl, SafeResourceUrl } from '@angular/platform-browser'; + +@Pipe({ + name: 'safe' +}) +export class SafePipe implements PipeTransform { + + constructor(protected sanitizer: DomSanitizer) { } + + public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl { + switch (type) { + case 'html': return this.sanitizer.bypassSecurityTrustHtml(value); + case 'style': return this.sanitizer.bypassSecurityTrustStyle(value); + case 'script': return this.sanitizer.bypassSecurityTrustScript(value); + case 'url': return this.sanitizer.bypassSecurityTrustUrl(value); + case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value); + default: throw new Error(`Invalid safe type specified: ${type}`); + } + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/tipup-component.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup-component.ts new file mode 100644 index 00000000..8747b098 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup-component.ts @@ -0,0 +1,67 @@ +import { ChangeDetectionStrategy, Component, Inject, OnInit } from "@angular/core"; +import { SfngDialogRef, SFNG_DIALOG_REF } from "../dialog"; +import { SfngTipUpService } from "./tipup"; +import { ActionRunner, Button, SFNG_TIP_UP_ACTION_RUNNER, TipUp } from './translations'; +import { TIPUP_TOKEN } from "./utils"; + +@Component({ + selector: 'sfng-tipup-container', + templateUrl: './tipup.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngTipUpComponent implements OnInit, TipUp { + title: string = 'N/A'; + content: string = 'N/A'; + nextKey?: string; + buttons?: Button[]; + url?: string; + urlText: string = 'Read More'; + + constructor( + @Inject(TIPUP_TOKEN) public readonly token: string, + @Inject(SFNG_DIALOG_REF) private readonly dialogRef: SfngDialogRef, + @Inject(SFNG_TIP_UP_ACTION_RUNNER) private runner: ActionRunner, + private tipupService: SfngTipUpService, + ) { } + + ngOnInit() { + const doc = this.tipupService.getTipUp(this.token); + if (!!doc) { + Object.assign(this, doc); + this.urlText = doc.urlText || 'Read More'; + } + } + + async next() { + if (!this.nextKey) { + return; + } + + this.tipupService.open(this.nextKey); + this.dialogRef.close(); + } + + async runAction(btn: Button) { + await this.runner.performAction(btn.action); + + // if we have a nextKey for the button but do not do in-app + // routing we should be able to open the next tipup as soon + // as the action finished + if (!!btn.nextKey) { + this.tipupService.waitFor(btn.nextKey!) + .subscribe({ + next: () => { + this.dialogRef.close(); + this.tipupService.open(btn.nextKey!); + }, + error: console.error + }) + } else { + this.close(); + } + } + + close() { + this.dialogRef.close(); + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.html b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.html new file mode 100644 index 00000000..ac54fe8a --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.html @@ -0,0 +1,22 @@ +
+ Tip + + + + +

+ + + + {{ urlText }} + +
+
+ +
+ +
+
diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.module.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.module.ts new file mode 100644 index 00000000..42378c6f --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.module.ts @@ -0,0 +1,47 @@ +import { CommonModule } from "@angular/common"; +import { ModuleWithProviders, NgModule, Type } from "@angular/core"; +import { MarkdownModule } from "ngx-markdown"; +import { SfngDialogModule } from "../dialog"; +import { SfngTipUpAnchorDirective } from './anchor'; +import { SfngsfngTipUpTriggerDirective, SfngTipUpIconComponent } from './tipup'; +import { SfngTipUpComponent } from './tipup-component'; +import { ActionRunner, HelpTexts, SFNG_TIP_UP_ACTION_RUNNER, SFNG_TIP_UP_CONTENTS } from "./translations"; +import { SafePipe } from "./safe.pipe"; + +@NgModule({ + imports: [ + CommonModule, + MarkdownModule.forChild(), + SfngDialogModule, + ], + declarations: [ + SfngTipUpIconComponent, + SfngsfngTipUpTriggerDirective, + SfngTipUpComponent, + SfngTipUpAnchorDirective, + SafePipe + ], + exports: [ + SfngTipUpIconComponent, + SfngsfngTipUpTriggerDirective, + SfngTipUpComponent, + SfngTipUpAnchorDirective + ], +}) +export class SfngTipUpModule { + static forRoot(text: HelpTexts, runner: Type>): ModuleWithProviders { + return { + ngModule: SfngTipUpModule, + providers: [ + { + provide: SFNG_TIP_UP_CONTENTS, + useValue: text, + }, + { + provide: SFNG_TIP_UP_ACTION_RUNNER, + useExisting: runner, + } + ] + } + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.ts new file mode 100644 index 00000000..7f6fbd85 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/tipup.ts @@ -0,0 +1,526 @@ +/* eslint-disable @angular-eslint/no-input-rename */ +import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion'; +import { ConnectedPosition } from '@angular/cdk/overlay'; +import { _getShadowRoot } from '@angular/cdk/platform'; +import { DOCUMENT } from '@angular/common'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, ElementRef, HostBinding, HostListener, Inject, Injectable, Injector, Input, NgZone, OnDestroy, Optional, Renderer2, RendererFactory2 } from '@angular/core'; +import { Observable, of, Subject } from 'rxjs'; +import { debounce, debounceTime, filter, map, skip, take, timeout } from 'rxjs/operators'; +import { SfngDialogRef, SfngDialogService } from '../dialog'; +import { SfngTipUpAnchorDirective } from './anchor'; +import { deepCloneNode, extendStyles, matchElementSize, removeNode } from './clone-node'; +import { getCssSelector, synchronizeCssStyles } from './css-utils'; +import { SfngTipUpComponent } from './tipup-component'; +import { Button, HelpTexts, SFNG_TIP_UP_CONTENTS, TipUp } from './translations'; +import { SfngTipUpPlacement, TIPUP_TOKEN } from './utils'; + +@Directive({ + selector: '[sfngTipUpTrigger]', +}) +export class SfngsfngTipUpTriggerDirective implements OnDestroy { + constructor( + public readonly elementRef: ElementRef, + public dialog: SfngDialogService, + @Optional() @Inject(SfngTipUpAnchorDirective) public anchor: SfngTipUpAnchorDirective | ElementRef | HTMLElement, + @Inject(SFNG_TIP_UP_CONTENTS) private tipUpContents: HelpTexts, + private tipupService: SfngTipUpService, + private cdr: ChangeDetectorRef, + ) { } + + private dialogRef: SfngDialogRef | null = null; + + /** + * The helptext token used to search for the tip up defintion. + */ + @Input('sfngTipUpTrigger') + set textKey(s: string) { + if (!!this._textKey) { + this.tipupService.deregister(this._textKey, this); + } + this._textKey = s; + this.tipupService.register(this._textKey, this); + } + get textKey() { return this._textKey; } + private _textKey: string = ''; + + /** + * The text to display inside the tip up. If unset, the tipup definition + * will be loaded form helptexts.yaml. + * This input property is mainly designed for programatic/dynamic tip-up generation + */ + @Input('sfngTipUpText') + text: string | undefined; + + @Input('sfngTipUpTitle') + title: string | undefined; + + @Input('sfngTipUpButtons') + buttons: Button[] | undefined; + + /** + * asTipUp returns a tip-up definition built from the input + * properties sfngTipUpText and sfngTipUpTitle. If none are set + * then null is returned. + */ + asTipUp(): TipUp | null { + // TODO(ppacher): we could also merge the defintions from MyYamlFile + // and the properties set on this directive.... + if (!this.text) { + return this.tipUpContents[this.textKey]; + } + return { + title: this.title || '', + content: this.text, + buttons: this.buttons, + } + } + + /** + * The default anchor for the tipup if non is provided via Dependency-Injection + * or using sfngTipUpAnchorRef + */ + @Input('sfngTipUpDefaultAnchor') + defaultAnchor: ElementRef | HTMLElement | null = null; + + /** Optionally overwrite the anchor element received via Dependency Injection */ + @Input('sfngTipUpAnchorRef') + set anchorRef(ref: ElementRef | HTMLElement | null) { + this.anchor = ref ?? this.anchor; + } + + /** Used to ensure all tip-up triggers have a pointer cursor */ + @HostBinding('style.cursor') + cursor = 'pointer'; + + /** De-register ourself upon destroy */ + ngOnDestroy() { + this.tipupService.deregister(this.textKey, this); + } + + /** Whether or not we're passive-only and thus do not handle click-events form the user */ + @Input('sfngTipUpPassive') + set passive(v: any) { + this._passive = coerceBooleanProperty(v ?? true); + } + get passive() { return this._passive; } + private _passive = false; + + @Input('sfngTipUpOffset') + set offset(v: any) { + this._defaultOffset = coerceNumberProperty(v) + } + get offset() { return this._defaultOffset } + private _defaultOffset = 20; + + @Input('sfngTipUpPlacement') + placement: SfngTipUpPlacement | null = null; + + @HostListener('click', ['$event']) + onClick(event?: MouseEvent): Promise { + if (!!event) { + // if there's a click event the user actually clicked the element. + // we only handle this if we're not marked as passive. + if (this._passive) { + return Promise.resolve(); + } + + event.preventDefault(); + event.stopPropagation(); + } + + if (!!this.dialogRef) { + this.dialogRef.close(); + return Promise.resolve(); + } + + let anchorElement: ElementRef | HTMLElement | null = this.defaultAnchor || this.elementRef; + let placement: SfngTipUpPlacement | null = this.placement; + + if (!!this.anchor) { + if (this.anchor instanceof SfngTipUpAnchorDirective) { + anchorElement = this.anchor.elementRef; + placement = this.anchor; + } else { + anchorElement = this.anchor; + } + } + + this.dialogRef = this.tipupService.createTipup( + anchorElement, + this.textKey, + this, + placement, + ) + + this.dialogRef.onClose + .pipe(take(1)) + .subscribe(() => { + this.dialogRef = null; + this.cdr.markForCheck(); + }); + + this.cdr.detectChanges(); + + return this.dialogRef.onStateChange + .pipe( + filter(state => state === 'opening'), + take(1), + ) + .toPromise() + } +} + +@Component({ + selector: 'sfng-tipup', + template: + ` + + + + + `, + styles: [ + ` + :host { + display: inline-block; + width : 1rem; + position: relative; + opacity: 0.55; + cursor : pointer; + align-self: center; + } + + :host:hover { + opacity: 1; + } + ` + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngTipUpIconComponent implements SfngTipUpPlacement { + @Input() + key: string = ''; + + // see sfngTipUpTrigger sfngTipUpText and sfngTipUpTitle + @Input() text: string | undefined = undefined; + @Input() title: string | undefined = undefined; + @Input() buttons: Button[] | undefined = undefined; + + @Input() + anchor: ElementRef | HTMLElement | null = null; + + @Input('placement') + origin: 'left' | 'right' = 'right'; + + @Input() + set offset(v: any) { + this._offset = coerceNumberProperty(v); + } + get offset() { return this._offset; } + private _offset: number = 10; + + constructor(private elementRef: ElementRef) { } + + get placement(): SfngTipUpPlacement { + return this + } + + get parent(): HTMLElement | null { + return (this.elementRef?.nativeElement as HTMLElement)?.parentElement; + } +} + + +@Injectable({ + providedIn: 'root' +}) +export class SfngTipUpService { + tipups = new Map(); + + private _onRegister = new Subject(); + private _onUnregister = new Subject(); + + get onRegister(): Observable { + return this._onRegister.asObservable(); + } + + get onUnregister(): Observable { + return this._onUnregister.asObservable(); + } + + waitFor(key: string): Observable { + if (this.tipups.has(key)) { + return of(undefined); + } + + return this.onRegister + .pipe( + filter(val => val === key), + debounce(() => this.ngZone.onStable.pipe(skip(2))), + debounceTime(1000), + take(1), + map(() => { }), + timeout(5000), + ); + } + + private renderer: Renderer2; + + constructor( + @Inject(DOCUMENT) private _document: Document, + private dialog: SfngDialogService, + private ngZone: NgZone, + private injector: Injector, + rendererFactory: RendererFactory2 + ) { + this.renderer = rendererFactory.createRenderer(null, null) + } + + register(key: string, trigger: SfngsfngTipUpTriggerDirective) { + if (this.tipups.has(key)) { + return; + } + + this.tipups.set(key, trigger); + this._onRegister.next(key); + } + + deregister(key: string, trigger: SfngsfngTipUpTriggerDirective) { + if (this.tipups.get(key) === trigger) { + this.tipups.delete(key); + this._onUnregister.next(key); + } + } + + getTipUp(key: string): TipUp | null { + return this.tipups.get(key)?.asTipUp() || null; + } + + private _latestTipUp: SfngDialogRef | null = null; + + createTipup( + anchor: HTMLElement | ElementRef, + key: string, + origin?: SfngsfngTipUpTriggerDirective, + opts: SfngTipUpPlacement | null = {}, + injector?: Injector): SfngDialogRef { + + const lastTipUp = this._latestTipUp + let closePrevious = () => { + if (!!lastTipUp) { + lastTipUp.close(); + } + } + + // make sure we have an ElementRef to work with + if (!(anchor instanceof ElementRef)) { + anchor = new ElementRef(anchor) + } + + // the the origin placement of the tipup + const positions: ConnectedPosition[] = []; + if (opts?.origin === 'left') { + positions.push({ + originX: 'start', + originY: 'center', + overlayX: 'end', + overlayY: 'center', + }) + } else { + positions.push({ + originX: 'end', + originY: 'center', + overlayX: 'start', + overlayY: 'center', + }) + } + + // determine the offset to the tipup origin + let offset = opts?.offset ?? 10; + if (opts?.origin === 'left') { + offset *= -1; + } + + let postitionStrategy = this.dialog.position() + .flexibleConnectedTo(anchor) + .withPositions(positions) + .withDefaultOffsetX(offset); + + const inj = Injector.create({ + providers: [ + { + useValue: key, + provide: TIPUP_TOKEN, + } + ], + parent: injector || this.injector, + }); + + + const newTipUp = this.dialog.create(SfngTipUpComponent, { + dragable: false, + autoclose: true, + backdrop: 'light', + injector: inj, + positionStrategy: postitionStrategy + }); + this._latestTipUp = newTipUp; + + const _preview = this._createPreview(anchor.nativeElement, _getShadowRoot(anchor.nativeElement)); + + // construct a CSS selector that targets the clicked origin (sfngTipUpTriggerDirective) from within + // the anchor. We use that path to highlight the copy of the trigger-directive in the preview. + if (!!origin) { + const originSelector = getCssSelector(origin.elementRef.nativeElement, anchor.nativeElement); + let target: HTMLElement | null = null; + if (!!originSelector) { + target = _preview.querySelector(originSelector); + } else { + target = _preview; + } + + this.renderer.addClass(target, 'active-tipup-trigger') + } + + newTipUp.onStateChange + .pipe( + filter(state => state === 'open'), + take(1) + ) + .subscribe(() => { + closePrevious(); + _preview.attach() + }) + + newTipUp.onStateChange + .pipe( + filter(state => state === 'closing'), + take(1) + ) + .subscribe(() => { + if (this._latestTipUp === newTipUp) { + this._latestTipUp = null; + } + _preview.classList.remove('visible'); + setTimeout(() => { + removeNode(_preview); + }, 300) + }); + + return newTipUp; + } + + private _createPreview(element: HTMLElement, shadowRoot: ShadowRoot | null): HTMLElement & { attach: () => void } { + const preview = deepCloneNode(element); + // clone all CSS styles by applying them directly to the copied + // nodes. Though, we skip the opacity property because we use that + // a lot and it makes the preview strange .... + synchronizeCssStyles(element, preview, new Set([ + 'opacity' + ])); + + // make sure the preview element is at the exact same position + // as the original one. + matchElementSize(preview, element.getBoundingClientRect()); + + extendStyles(preview.style, { + // We have to reset the margin, because it can throw off positioning relative to the viewport. + 'margin': '0', + 'position': 'fixed', + 'top': '0', + 'left': '0', + 'z-index': '1000', + 'opacity': 'unset', + }, new Set(['position'])); + + // We add a dedicated class to the preview element so + // it can handle special higlighting itself. + preview.classList.add('tipup-preview') + + // since the user might want to click on the preview element we must + // intercept the click-event, determine the path to the target element inside + // the preview and eventually dispatch a click-event on the actual + // - real - target inside the cloned element. + preview.onclick = function (event: MouseEvent) { + let path = getCssSelector(event.target as HTMLElement, preview); + if (!!path) { + // find the target by it's CSS path + let actualTarget: HTMLElement | null = element.querySelector(path); + + // some (SVG) elements don't have a direct click() listener so we need to search + // the parents upwards to find one that implements click(). + // we're basically searching up until we reach the tag. + // + // TODO(ppacher): stop searching at the respective root node. + if (!!actualTarget) { + let iter: HTMLElement = actualTarget; + while (iter != null) { + if ('click' in iter && typeof iter['click'] === 'function') { + iter.click(); + break; + } + iter = iter.parentNode as HTMLElement; + } + } + } else { + // the user clicked the preview element directly + try { + element.click() + } catch (e) { + console.error(e); + } + } + } + + let attach = () => { + const parent = this._getPreviewInserationPoint(shadowRoot) + const cdkOverlayContainer = parent.getElementsByClassName('cdk-overlay-container')[0] + // if we find a cdkOverlayContainer in our inseration point (which we expect to be there) + // we insert the preview element right after the overlay-backdrop. This way the tip-up + // dialog will still be on top of the preview. + if (!!cdkOverlayContainer) { + const reference = cdkOverlayContainer.getElementsByClassName("cdk-overlay-backdrop")[0].nextSibling; + cdkOverlayContainer.insertBefore(preview, reference) + } else { + parent.appendChild(preview); + } + + setTimeout(() => { + preview.classList.add('visible'); + }) + } + + Object.defineProperty(preview, 'attach', { + value: attach, + }) + + return preview as any; + } + + private _getPreviewInserationPoint(shadowRoot: ShadowRoot | null): HTMLElement { + const documentRef = this._document; + return shadowRoot || + documentRef.fullscreenElement || + (documentRef as any).webkitFullscreenElement || + (documentRef as any).mozFullScreenElement || + (documentRef as any).msFullscreenElement || + documentRef.body; + } + + async open(key: string) { + const comp = this.tipups.get(key); + if (!comp) { + console.error('Tried to open unknown tip-up with key ' + key); + return; + } + comp.onClick() + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/translations.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/translations.ts new file mode 100644 index 00000000..fdc0ecd5 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/translations.ts @@ -0,0 +1,27 @@ +import { InjectionToken } from '@angular/core'; + +export const SFNG_TIP_UP_CONTENTS = new InjectionToken>('SfngTipUpContents'); +export const SFNG_TIP_UP_ACTION_RUNNER = new InjectionToken>('SfngTipUpActionRunner') + +export interface Button { + name: string; + action: T; + nextKey?: string; +} + +export interface TipUp { + title: string; + content: string; + url?: string; + urlText?: string; + buttons?: Button[]; + nextKey?: string; +} + +export interface HelpTexts { + [key: string]: TipUp; +} + +export interface ActionRunner { + performAction(action: T): Promise; +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tipup/utils.ts b/desktop/angular/projects/safing/ui/src/lib/tipup/utils.ts new file mode 100644 index 00000000..7ccffbd4 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tipup/utils.ts @@ -0,0 +1,8 @@ +import { InjectionToken } from "@angular/core"; + +export const TIPUP_TOKEN = new InjectionToken('TipUPJSONToken'); + +export interface SfngTipUpPlacement { + origin?: 'left' | 'right'; + offset?: number; +} diff --git a/desktop/angular/projects/safing/ui/src/lib/toggle-switch/_toggle-switch.scss b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/_toggle-switch.scss new file mode 100644 index 00000000..246a7953 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/_toggle-switch.scss @@ -0,0 +1,35 @@ +sfng-toggle { + @apply flex items-center; + + label { + @apply inline-block w-10 h-5 relative bg-gray-500 rounded-full; + } + + .slider { + @apply absolute cursor-pointer top-0 left-0 right-0 bottom-0 bg-gray-600 transition-all duration-100 rounded-full shadow-inner-xs; + } + + .dot { + @apply absolute transition-all duration-200 rounded-full bg-white; + height: 18px; + width: 18px; + bottom: 1px; + left: 1px; + } + + input:checked:not(:disabled)+.slider { + @apply bg-green-300 bg-opacity-50 text-green; + } + + input:disabled+.slider { + @apply opacity-75 cursor-not-allowed; + } + + .dot.checked { + transform: translateX(calc(2.5rem - 18px - 2px)); + } + + .dot.disabled { + transform: translateX(calc((2.5rem - 18px - 2px)/2)); + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/toggle-switch/index.ts b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/index.ts new file mode 100644 index 00000000..fbc94093 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/index.ts @@ -0,0 +1,3 @@ +export * from './toggle-switch'; +export * from './toggle.module'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle-switch.html b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle-switch.html new file mode 100644 index 00000000..69320c3a --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle-switch.html @@ -0,0 +1,20 @@ + diff --git a/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle-switch.ts b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle-switch.ts new file mode 100644 index 00000000..6b90f961 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle-switch.ts @@ -0,0 +1,59 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, HostListener } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +@Component({ + selector: 'sfng-toggle', + templateUrl: './toggle-switch.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SfngToggleSwitchComponent), + multi: true, + } + ] +}) +export class SfngToggleSwitchComponent implements ControlValueAccessor { + @HostListener('blur') + onBlur() { + this.onTouch(); + } + + set disabled(v: any) { + this.setDisabledState(coerceBooleanProperty(v)) + } + get disabled() { + return this._disabled; + } + private _disabled = false; + + value: boolean = false; + + constructor(private _changeDetector: ChangeDetectorRef) { } + + setDisabledState(isDisabled: boolean) { + this._disabled = isDisabled; + this._changeDetector.markForCheck(); + } + + onValueChange(value: boolean) { + this.value = value; + this.onChange(this.value); + } + + writeValue(value: boolean) { + this.value = value; + this._changeDetector.markForCheck(); + } + + onChange = (_: any): void => { }; + registerOnChange(fn: (value: any) => void) { + this.onChange = fn; + } + + onTouch = (): void => { }; + registerOnTouched(fn: () => void) { + this.onTouch = fn; + } +} diff --git a/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle.module.ts b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle.module.ts new file mode 100644 index 00000000..db27249b --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/toggle-switch/toggle.module.ts @@ -0,0 +1,18 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { SfngToggleSwitchComponent } from "./toggle-switch"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ], + declarations: [ + SfngToggleSwitchComponent, + ], + exports: [ + SfngToggleSwitchComponent, + ] +}) +export class SfngToggleSwitchModule { } diff --git a/desktop/angular/projects/safing/ui/src/lib/tooltip/_tooltip-component.scss b/desktop/angular/projects/safing/ui/src/lib/tooltip/_tooltip-component.scss new file mode 100644 index 00000000..ff90d82a --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tooltip/_tooltip-component.scss @@ -0,0 +1,5 @@ +sfng-tooltip-container { + @apply relative block; + + max-width: 16rem; +} diff --git a/desktop/angular/projects/safing/ui/src/lib/tooltip/index.ts b/desktop/angular/projects/safing/ui/src/lib/tooltip/index.ts new file mode 100644 index 00000000..fb071730 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tooltip/index.ts @@ -0,0 +1,3 @@ +export * from './tooltip'; +export * from './tooltip.module'; + diff --git a/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip-component.html b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip-component.html new file mode 100644 index 00000000..ad68d95c --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip-component.html @@ -0,0 +1,6 @@ +
+ {{ message }} + +
diff --git a/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip-component.ts b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip-component.ts new file mode 100644 index 00000000..a206d5b3 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip-component.ts @@ -0,0 +1,139 @@ +import { animate, AnimationEvent, style, transition, trigger } from "@angular/animations"; +import { OverlayRef } from "@angular/cdk/overlay"; +import { TemplatePortal } from "@angular/cdk/portal"; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, HostListener, Inject, InjectionToken, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core"; +import { SfngTooltipDirective } from "./tooltip"; + +export const SFNG_TOOLTIP_CONTENT = new InjectionToken>('SFNG_TOOLTIP_CONTENT'); +export const SFNG_TOOLTIP_OVERLAY = new InjectionToken('SFNG_TOOLTIP_OVERLAY'); + +@Component({ + selector: 'sfng-tooltip-container', + templateUrl: './tooltip-component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger( + 'moveInOut', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translate{{ what }}({{ value }}) scale(0.75)' }), + animate('.1s ease-in', + style({ opacity: 1, transform: 'translate{{ what }}(0%) scale(1)' })) + ], + { params: { what: 'Y', value: '-8px' } } // default parameters + ), + transition( + ':leave', + [ + style({ opacity: 1 }), + animate('.1s ease-out', + style({ opacity: 0, transform: 'translate{{ what }}({{ value }}) scale(0.75)' })) + ], + { params: { what: 'Y', value: '8px' } } // default parameters + ) + ] + )] + +}) +export class SfngTooltipComponent implements AfterViewInit, OnDestroy { + /** + * Adds snfg-tooltip-instance class to the host element. + * This is used as a selector in the FlexibleConnectedPosition stragegy + * to set a transform-origin. That origin is then used for the "arrow" anchor + * placement. + */ + @HostBinding('class.sfng-tooltip-instance') + _hostClass = true; + + /** + * Used to clear the "hide" timeout when the cursor moves from the the origin + * into the tooltip content. + * This is required if the tooltip contains rich and likely clickable content. + */ + @HostListener('mouseenter') + onMouseEnter() { this.directive.show() } + + /** + * If the tooltip is visible because the user moved inside the tooltip-component + * (see comment above) then we need to handle a mouse-leave event as well. + */ + @HostListener('mouseleave') + onMouseLeave() { this.directive.hide() } + + what = 'Y'; + value = '8px' + transformOrigin = ''; + + _appAnimate = false; + + private observer: MutationObserver | null = null; + + /** Message is the tooltip message to display in case tooltipContent is a string */ + message = ''; + + /** Portal is the tooltip content to display in case tooltipContent is a template reference */ + portal: TemplatePortal | null = null; + + constructor( + @Inject(SFNG_TOOLTIP_CONTENT) tooltipContent: string | TemplateRef, + @Inject(SFNG_TOOLTIP_OVERLAY) public overlayRef: OverlayRef, + private directive: SfngTooltipDirective, + private elementRef: ElementRef, + private cdr: ChangeDetectorRef, + private viewContainer: ViewContainerRef + ) { + if (tooltipContent instanceof TemplateRef) { + this.portal = new TemplatePortal(tooltipContent, this.viewContainer) + } else { + this.message = tooltipContent; + } + } + + dispose() { + this._appAnimate = false; + this.cdr.markForCheck(); + } + + animationDone(event: AnimationEvent) { + if (event.toState === 'void') { + this.overlayRef.dispose(); + } + } + + ngOnDestroy(): void { + this.observer?.disconnect(); + } + + ngAfterViewInit(): void { + this.observer = new MutationObserver(mutations => { + this.transformOrigin = this.elementRef.nativeElement.style.transformOrigin; + if (!this.transformOrigin) { + return; + } + + const [x, y] = this.transformOrigin.split(" "); + if (x === 'center') { + this.what = 'Y' + if (y === 'top') { + this.value = '-8px' + } else { + this.value = '8px' + } + } else { + this.what = 'X' + if (x === 'left') { + this.value = '-8px' + } else { + this.value = '8px' + } + } + + this._appAnimate = true; + this.cdr.detectChanges(); + }); + this.observer.observe(this.elementRef.nativeElement, { attributes: true, attributeFilter: ['style'] }) + } +} + diff --git a/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip.module.ts b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip.module.ts new file mode 100644 index 00000000..49bd0a14 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip.module.ts @@ -0,0 +1,23 @@ +import { OverlayModule } from "@angular/cdk/overlay"; +import { PortalModule } from "@angular/cdk/portal"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SfngTooltipDirective } from "./tooltip"; +import { SfngTooltipComponent } from "./tooltip-component"; + +@NgModule({ + imports: [ + PortalModule, + OverlayModule, + CommonModule, + ], + declarations: [ + SfngTooltipDirective, + SfngTooltipComponent + ], + exports: [ + SfngTooltipDirective + ] +}) +export class SfngTooltipModule { } + diff --git a/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip.ts b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip.ts new file mode 100644 index 00000000..032e6bec --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/tooltip/tooltip.ts @@ -0,0 +1,244 @@ +/* eslint-disable @angular-eslint/no-input-rename */ +import { coerceNumberProperty } from "@angular/cdk/coercion"; +import { ConnectedPosition, Overlay, OverlayRef, PositionStrategy } from "@angular/cdk/overlay"; +import { ComponentPortal } from "@angular/cdk/portal"; +import { ComponentRef, Directive, ElementRef, HostListener, Injector, Input, isDevMode, OnChanges, OnDestroy, OnInit, TemplateRef } from "@angular/core"; +import { Subject } from "rxjs"; +import { SfngTooltipComponent, SFNG_TOOLTIP_CONTENT, SFNG_TOOLTIP_OVERLAY } from "./tooltip-component"; + +/** The allowed tooltip positions. */ +export type SfngTooltipPosition = 'left' | 'right' | 'bottom' | 'top'; + +@Directive({ + selector: '[sfng-tooltip],[snfgTooltip]', +}) +export class SfngTooltipDirective implements OnInit, OnDestroy, OnChanges { + /** Used to control the visibility of the tooltip */ + private attach$ = new Subject(); + + /** Holds a reference to the tooltip overlay */ + private tooltipRef: ComponentRef | null = null; + + /** + * A reference to a timeout created by setTimeout used to debounce + * displaying the tooltip + */ + private debouncer: any | null = null; + + constructor( + private overlay: Overlay, + private injector: Injector, + private originRef: ElementRef, + ) { } + + @HostListener('mouseenter') + show(delay = this.delay) { + if (this.debouncer !== null) { + clearTimeout(this.debouncer); + } + + this.debouncer = setTimeout(() => { + this.debouncer = null; + this.attach$.next(true); + }, delay); + } + + @HostListener('mouseleave') + hide(delay = this.delay / 2) { + // if we're currently debouncing a "show" than + // we should clear that out to avoid re-attaching + // the tooltip right after we disposed it. + if (this.debouncer !== null) { + clearTimeout(this.debouncer); + this.debouncer = null; + } + + this.debouncer = setTimeout(() => { + this.attach$.next(false); + this.debouncer = null; + }, delay); + } + + /** Debounce delay before showing the tooltip */ + @Input('sfngTooltipDelay') + set delay(v: any) { + this._delay = coerceNumberProperty(v); + } + get delay() { return this._delay } + private _delay = 500; + + /** An additional offset between the tooltip overlay and the origin centers */ + @Input('sfngTooltipOffset') + set offset(v: any) { + this._offset = coerceNumberProperty(v); + } + private _offset: number | null = 8; + + /** The actual content that should be displayed in the tooltip overlay. */ + @Input('sfngTooltip') + @Input('sfng-tooltip') + tooltipContent: string | TemplateRef | null = null; + + @Input('snfgTooltipPosition') + position: ConnectedPosition | SfngTooltipPosition | (SfngTooltipPosition | ConnectedPosition)[] | 'any' = 'any'; + + ngOnInit() { + this.attach$ + .subscribe(attach => { + if (attach) { + this.createTooltip(); + return; + } + if (!!this.tooltipRef) { + this.tooltipRef.instance.dispose(); + this.tooltipRef = null; + } + }) + } + + ngOnDestroy(): void { + this.attach$.next(false); + this.attach$.complete(); + } + + ngOnChanges(): void { + // if the tooltip content has be set to null and we're still + // showing the tooltip we treat that as an attempt to hide. + if (this.tooltipContent === null && !!this.tooltipRef) { + this.hide(); + } + } + + /** Creates the actual tooltip overlay */ + private createTooltip() { + // there's nothing to do if the tooltip is still active. + if (!!this.tooltipRef) { + return; + } + + // support disabling the tooltip by passing "null" for + // the content. + if (this.tooltipContent === null) { + return; + } + + const position = this.buildPositionStrategy(); + + const overlayRef = this.overlay.create({ + positionStrategy: position, + scrollStrategy: this.overlay.scrollStrategies.close(), + disposeOnNavigation: true, + }); + + // make sure we close the tooltip if the user clicks on our + // originRef. + overlayRef.outsidePointerEvents() + .subscribe(() => this.hide()); + + overlayRef.attachments() + .subscribe(() => { + if (!overlayRef) { + return + } + overlayRef.updateSize({}); + overlayRef.updatePosition(); + }) + + // create a component portal for the tooltip component + // and attach it to our newly created overlay. + const portal = this.getOverlayPortal(overlayRef); + this.tooltipRef = overlayRef.attach(portal); + } + + private getOverlayPortal(ref: OverlayRef): ComponentPortal { + const inj = Injector.create({ + providers: [ + { provide: SFNG_TOOLTIP_CONTENT, useValue: this.tooltipContent }, + { provide: SFNG_TOOLTIP_OVERLAY, useValue: ref }, + ], + parent: this.injector, + name: 'SfngTooltipDirective' + }) + + const portal = new ComponentPortal( + SfngTooltipComponent, + undefined, + inj + ) + + return portal; + } + + /** Builds a FlexibleConnectedPositionStrategy for the tooltip overlay */ + private buildPositionStrategy(): PositionStrategy { + let pos = this.position; + if (pos === 'any') { + pos = ['top', 'bottom', 'right', 'left'] + } else if (!Array.isArray(pos)) { + pos = [pos]; + } + + let allowedPositions: ConnectedPosition[] = + pos.map(p => { + if (typeof p === 'string') { + return this.getAllowedConnectedPosition(p); + } + // this is already a ConnectedPosition + return p + }); + + let position = this.overlay.position() + .flexibleConnectedTo(this.originRef) + .withFlexibleDimensions(true) + .withPush(true) + .withPositions(allowedPositions) + .withGrowAfterOpen(true) + .withTransformOriginOn('.sfng-tooltip-instance') + + return position; + } + + private getAllowedConnectedPosition(type: SfngTooltipPosition): ConnectedPosition { + switch (type) { + case 'left': + return { + originX: 'start', + originY: 'center', + overlayX: 'end', + overlayY: 'center', + offsetX: - (this._offset || 0), + } + case 'right': + return { + originX: 'end', + originY: 'center', + overlayX: 'start', + overlayY: 'center', + offsetX: (this._offset || 0), + } + case 'top': + return { + originX: 'center', + originY: 'top', + overlayX: 'center', + overlayY: 'bottom', + offsetY: - (this._offset || 0), + } + case 'bottom': + return { + originX: 'center', + originY: 'bottom', + overlayX: 'center', + overlayY: 'top', + offsetY: (this._offset || 0), + } + default: + if (isDevMode()) { + throw new Error(`invalid value for SfngTooltipPosition: ${type}`) + } + // fallback to "right" + return this.getAllowedConnectedPosition('right') + } + } +} + diff --git a/desktop/angular/projects/safing/ui/src/lib/ui.module.ts b/desktop/angular/projects/safing/ui/src/lib/ui.module.ts new file mode 100644 index 00000000..e1f772ae --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/lib/ui.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { SfngAccordionModule } from './accordion'; + + +@NgModule({ + exports: [ + SfngAccordionModule + ] +}) +export class UiModule { } diff --git a/desktop/angular/projects/safing/ui/src/public-api.ts b/desktop/angular/projects/safing/ui/src/public-api.ts new file mode 100644 index 00000000..e07d6adf --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/public-api.ts @@ -0,0 +1,16 @@ +/* + * Public API Surface of ui + */ + +export * from './lib/accordion'; +export * from './lib/dialog'; +export * from './lib/dropdown'; +export * from './lib/overlay-stepper'; +export * from './lib/pagination'; +export * from './lib/select'; +export * from './lib/tabs'; +export * from './lib/tipup'; +export * from './lib/toggle-switch'; +export * from './lib/tooltip'; +export * from './lib/ui.module'; + diff --git a/desktop/angular/projects/safing/ui/src/test.ts b/desktop/angular/projects/safing/ui/src/test.ts new file mode 100644 index 00000000..ceee7e40 --- /dev/null +++ b/desktop/angular/projects/safing/ui/src/test.ts @@ -0,0 +1,16 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; +import 'zone.js'; +import 'zone.js/testing'; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + { teardown: { destroyAfterEach: true } }, +); diff --git a/desktop/angular/projects/safing/ui/theming.scss b/desktop/angular/projects/safing/ui/theming.scss new file mode 100644 index 00000000..9c5bb3c9 --- /dev/null +++ b/desktop/angular/projects/safing/ui/theming.scss @@ -0,0 +1,8 @@ +@import "./src/lib/select/select"; +@import "./src/lib/dialog/dialog"; +@import "./src/lib/pagination/pagination"; +@import "./src/lib/tabs/tab-group"; +@import "./src/lib/tipup/tipup"; +@import "./src/lib/tooltip/tooltip-component"; +@import "./src/lib/toggle-switch/toggle-switch"; +@import "./src/lib/dialog/confirm.dialog"; diff --git a/desktop/angular/projects/safing/ui/tsconfig.lib.json b/desktop/angular/projects/safing/ui/tsconfig.lib.json new file mode 100644 index 00000000..703cd4fd --- /dev/null +++ b/desktop/angular/projects/safing/ui/tsconfig.lib.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/lib", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [], + "lib": [ + "dom", + "es2018" + ] + }, + "exclude": [ + "src/test.ts", + "**/*.spec.ts" + ] +} diff --git a/desktop/angular/projects/safing/ui/tsconfig.lib.prod.json b/desktop/angular/projects/safing/ui/tsconfig.lib.prod.json new file mode 100644 index 00000000..71b135f6 --- /dev/null +++ b/desktop/angular/projects/safing/ui/tsconfig.lib.prod.json @@ -0,0 +1,7 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, +} diff --git a/desktop/angular/projects/safing/ui/tsconfig.spec.json b/desktop/angular/projects/safing/ui/tsconfig.spec.json new file mode 100644 index 00000000..85392ee8 --- /dev/null +++ b/desktop/angular/projects/safing/ui/tsconfig.spec.json @@ -0,0 +1,17 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "files": [ + "src/test.ts" + ], + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/desktop/angular/projects/tauri-builtin/src/app/app.component.html b/desktop/angular/projects/tauri-builtin/src/app/app.component.html new file mode 100644 index 00000000..c8897e1e --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/src/app/app.component.html @@ -0,0 +1,105 @@ +
+ + + +
+

Safing

+

+ Portmaster +

+
+
+ +
+ + + Connecting to System Service ... + + + + Connecting to System Service ... + + + + + Portmaster System Service is not running: + + + + + + + + + + + + Failed to find Portmaster System Service. +
+ Please reinstall the application. +
+ + +
+ + + + + + + + Your System Service Manager is not supported. Please make sure Portmaster is running. + + + + + + + + + + Your System Service Manager is not supported. Please make sure Portmaster is running. + + + + Unknown error: {{ status }} +
\ No newline at end of file diff --git a/desktop/angular/projects/tauri-builtin/src/app/app.component.ts b/desktop/angular/projects/tauri-builtin/src/app/app.component.ts new file mode 100644 index 00000000..b39cd515 --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/src/app/app.component.ts @@ -0,0 +1,52 @@ +import { OnInit, Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ServiceManagerStatus, TauriIntegrationService } from 'src/app/integration/taur-app'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [CommonModule], + templateUrl: './app.component.html', + styles: [ + ` + :host { + @apply block w-screen h-screen bg-background; + } + + #logo svg { + @apply absolute w-20; + } + `, + ], +}) +export class AppComponent implements OnInit { + private tauri = inject(TauriIntegrationService); + + status: ServiceManagerStatus | string | null = null; + + getHelp() { + this.tauri.openExternal("https://wiki.safing.io/en/Portmaster/App") + } + + startService() { + this.tauri.startService() + .then(() => this.getStatus()) + .catch(err => { + this.status = err.error; + }); + } + + getStatus() { + this.tauri.getServiceManagerStatus() + .then(result => { + this.status = result; + }) + .catch(err => { + this.status = err.error; + }) + } + + ngOnInit() { + this.getStatus(); + } +} diff --git a/desktop/angular/projects/tauri-builtin/src/app/app.config.ts b/desktop/angular/projects/tauri-builtin/src/app/app.config.ts new file mode 100644 index 00000000..2b4aa00c --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/src/app/app.config.ts @@ -0,0 +1,12 @@ +import { ApplicationConfig } from '@angular/core'; +import { TauriIntegrationService } from 'src/app/integration/taur-app'; + +export const appConfig: ApplicationConfig = { + providers: [ + { + provide: TauriIntegrationService, + useClass: TauriIntegrationService, + deps: [] + }, + ], +}; diff --git a/desktop/angular/projects/tauri-builtin/src/assets b/desktop/angular/projects/tauri-builtin/src/assets new file mode 120000 index 00000000..2978ef39 --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/src/assets @@ -0,0 +1 @@ +../../../assets \ No newline at end of file diff --git a/desktop/angular/projects/tauri-builtin/src/favicon.ico b/desktop/angular/projects/tauri-builtin/src/favicon.ico new file mode 100644 index 00000000..997406ad Binary files /dev/null and b/desktop/angular/projects/tauri-builtin/src/favicon.ico differ diff --git a/desktop/angular/projects/tauri-builtin/src/index.html b/desktop/angular/projects/tauri-builtin/src/index.html new file mode 100644 index 00000000..e99290a6 --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/src/index.html @@ -0,0 +1,13 @@ + + + + + TauriBuiltin + + + + + + + + diff --git a/desktop/angular/projects/tauri-builtin/src/main.ts b/desktop/angular/projects/tauri-builtin/src/main.ts new file mode 100644 index 00000000..35b00f34 --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, appConfig) + .catch((err) => console.error(err)); diff --git a/desktop/angular/projects/tauri-builtin/src/styles.scss b/desktop/angular/projects/tauri-builtin/src/styles.scss new file mode 100644 index 00000000..66a2c66c --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/src/styles.scss @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@import "safing/ui/theming"; + +/** foboar **/ diff --git a/desktop/angular/projects/tauri-builtin/tsconfig.app.json b/desktop/angular/projects/tauri-builtin/tsconfig.app.json new file mode 100644 index 00000000..f12c6239 --- /dev/null +++ b/desktop/angular/projects/tauri-builtin/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts", "../../src/electron-app.d.ts"] +} diff --git a/desktop/angular/proxy.json b/desktop/angular/proxy.json new file mode 100644 index 00000000..c60a2a4c --- /dev/null +++ b/desktop/angular/proxy.json @@ -0,0 +1,6 @@ +{ + "/api": { + "target": "http://localhost:817/", + "secure": false + } +} diff --git a/desktop/angular/src/app/app-routing.module.ts b/desktop/angular/src/app/app-routing.module.ts new file mode 100644 index 00000000..2324ae8b --- /dev/null +++ b/desktop/angular/src/app/app-routing.module.ts @@ -0,0 +1,68 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AppViewComponent } from './pages/app-view'; +import { DashboardPageComponent } from './pages/dashboard/dashboard.component'; +import { MonitorPageComponent } from './pages/monitor'; +import { SettingsComponent } from './pages/settings/settings'; +import { SpnPageComponent } from './pages/spn'; +import { SupportPageComponent } from './pages/support'; +import { SupportFormComponent } from './pages/support/form'; + +const routes: Routes = [ + { + path: '', + pathMatch: 'full', + redirectTo: 'dashboard', + }, + { + path: 'settings', + component: SettingsComponent, + }, + { + path: 'app', + pathMatch: 'full', + redirectTo: 'app/overview', + }, + { + path: 'app/overview', + component: AppViewComponent, + }, + { + path: 'app/:source/:id', + component: AppViewComponent, + }, + { + path: 'monitor', + component: MonitorPageComponent, + }, + { + path: 'monitor/profile/:source/:profile', + redirectTo: 'monitor', + }, + { + path: 'support', + component: SupportPageComponent, + }, + { + path: 'support/:id', + component: SupportFormComponent, + }, + { + path: 'spn', + component: SpnPageComponent, + }, + { + path: '**', + redirectTo: 'dashboard' + }, + { + path: 'dashboard', + component: DashboardPageComponent + } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled' })], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/desktop/angular/src/app/app.component.html b/desktop/angular/src/app/app.component.html new file mode 100644 index 00000000..401b4a49 --- /dev/null +++ b/desktop/angular/src/app/app.component.html @@ -0,0 +1,53 @@ + + + +
+ + + + +
+ +
+ +
+
+ +

{{overlayText}}

+

...

+
+
diff --git a/desktop/angular/src/app/app.component.scss b/desktop/angular/src/app/app.component.scss new file mode 100644 index 00000000..52cb3a92 --- /dev/null +++ b/desktop/angular/src/app/app.component.scss @@ -0,0 +1,114 @@ +:host { + display: flex; + @apply bg-background; + @apply h-screen overflow-hidden; + + &>* { + flex-shrink: 0; + } +} + +app-navigation, +app-side-dash { + @apply border-r; + @apply border-cards-tertiary; + @apply bg-background; +} + +app-navigation { + @apply w-16; +} + +div.main { + flex-grow: 1; + flex-shrink: 1; + + display: flex; + flex-direction: column; + align-items: center; + @apply bg-background; + height: 100vh; + overflow: hidden; +} + +app-debug { + @apply border-l; + @apply border-cards-tertiary; + @apply bg-background; + + width: 30vw; + height: 100vh; + min-width: 350px; + top: 0px; + position: sticky; +} + +.loading { + z-index: 100; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + backdrop-filter: blur(10px); + background-color: rgba(#222222, 0.35); + + .message { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + flex-direction: column; + } + + svg { + width: 100%; + position: absolute; + top: 0; + left: 0; + } + + div.logo { + opacity: 0.8; + position: relative; + width: 10vh; + height: 10vh; + @apply mt-4; + } + + .spin { + animation-name: spin; + animation-duration: 3500ms; + animation-iteration-count: infinite; + animation-timing-function: linear; + } + + .reverse { + animation-name: spin-reverse; + } +} + + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes spin-reverse { + 0% { + transform: rotate(360deg); + } + + 100% { + transform: rotate(0deg); + } +} diff --git a/desktop/angular/src/app/app.component.spec.ts b/desktop/angular/src/app/app.component.spec.ts new file mode 100644 index 00000000..200892c0 --- /dev/null +++ b/desktop/angular/src/app/app.component.spec.ts @@ -0,0 +1,28 @@ +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'portmaster'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('portmaster'); + }); +}); diff --git a/desktop/angular/src/app/app.component.ts b/desktop/angular/src/app/app.component.ts new file mode 100644 index 00000000..1ea813cd --- /dev/null +++ b/desktop/angular/src/app/app.component.ts @@ -0,0 +1,234 @@ +import { Overlay } from '@angular/cdk/overlay'; +import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, HostListener, Inject, NgZone, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { Params, Router } from '@angular/router'; +import { PortapiService } from '@safing/portmaster-api'; +import { OverlayStepper, SfngDialogService, StepperRef } from '@safing/ui'; +import { BehaviorSubject, merge, Subject } from 'rxjs'; +import { debounceTime, filter, mergeMap, skip, startWith, take } from 'rxjs/operators'; +import { IntroModule } from './intro'; +import { NotificationsService, UIStateService } from './services'; +import { ActionIndicatorService } from './shared/action-indicator'; +import { fadeInAnimation, fadeOutAnimation } from './shared/animations'; +import { ExitService } from './shared/exit-screen'; +import { SfngNetquerySearchOverlayComponent } from './shared/netquery/search-overlay'; +import { INTEGRATION_SERVICE, IntegrationService } from './integration'; +import { TauriIntegrationService } from './integration/taur-app'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + animations: [ + fadeInAnimation, + fadeOutAnimation, + ] +}) +export class AppComponent implements OnInit, AfterViewInit { + readonly connected = this.portapi.connected$.pipe( + debounceTime(250), + startWith(false) + ); + title = 'portmaster'; + + /** The current status of the side dash as emitted by the navigation component */ + sideDashStatus: 'collapsed' | 'expanded' = 'expanded'; + + /** Whether or not the side-dash is in overlay mode */ + sideDashOverlay = false; + + /** The MQL to watch for screen size changes. */ + private mql!: MediaQueryList; + + /** Emits when the side-dash is opened or closed in non-overlay mode */ + private sideDashOpen = new BehaviorSubject(false); + + /** Used to emit when the window size changed */ + windowResizeChange = new Subject(); + + get sideDashOpen$() { return this.sideDashOpen.asObservable() } + + get showOverlay$() { return this.exitService.showOverlay$ } + + get onContentSizeChange$() { + return merge( + this.windowResizeChange, + this.sideDashOpen$ + ) + .pipe( + startWith(undefined), + debounceTime(100), + ) + } + + @ViewChild('mainContent', { read: ElementRef, static: true }) + mainContent!: ElementRef; + + @HostListener('window:resize') + onWindowResize() { + this.windowResizeChange.next(); + } + + @HostListener('document:keydown', ['$event']) + onKeyDown(event: KeyboardEvent) { + if (event.key === ' ' && event.ctrlKey) { + this.dialog.create( + SfngNetquerySearchOverlayComponent, + { + positionStrategy: this.overlay + .position() + .global() + .centerHorizontally() + .top('1rem'), + backdrop: 'light', + autoclose: true, + } + ) + return; + } + } + + constructor( + public ngZone: NgZone, + public portapi: PortapiService, + public changeDetectorRef: ChangeDetectorRef, + private router: Router, + private exitService: ExitService, + private overlayStepper: OverlayStepper, + private dialog: SfngDialogService, + private overlay: Overlay, + private stateService: UIStateService, + private renderer2: Renderer2, + @Inject(INTEGRATION_SERVICE) private integration: IntegrationService, + ) { + (window as any).portapi = portapi; + } + + onSideDashChange(state: 'expanded' | 'collapsed' | 'force-overlay') { + if (state === 'force-overlay') { + state = 'expanded'; + if (!this.sideDashOverlay) { + this.sideDashOverlay = true; + } + } else { + this.sideDashOverlay = this.mql.matches; + } + + this.sideDashStatus = state; + + if (!this.sideDashOverlay) { + this.sideDashOpen.next(this.sideDashStatus === 'expanded') + } + } + + ngOnInit() { + // default breakpoints used by tailwindcss + const minContentWithBp = [ + 640, // sfng-sm: + 768, // sfng-md: + 1024, // sfng-lg: + 1280, // sfng-xl: + 1536 // sfng-2xl: + ] + + // prepare our breakpoint listeners and add the classes to our main element + merge( + this.windowResizeChange, + this.sideDashOpen$ + ) + .pipe( + startWith(undefined), + debounceTime(100), + ) + .subscribe(() => { + const rect = (this.mainContent.nativeElement as HTMLElement).getBoundingClientRect(); + + minContentWithBp.forEach((bp, idx) => { + if (rect.width >= bp) { + this.renderer2.addClass(this.mainContent.nativeElement, `min-width-${bp}px`) + } else { + this.renderer2.removeClass(this.mainContent.nativeElement, `min-width-${bp}px`) + } + }) + + this.changeDetectorRef.markForCheck(); + }) + + // force a reload of the current route if we reconnected to + // portmaster. This ensures we'll refresh any data that's currently + // displayed. + this.connected + .pipe( + filter(connected => !!connected), + skip(1), + ) + .subscribe(async () => { + const location = new URL(window.location.toString()); + + const params: Params = {} + location.searchParams.forEach((value, key) => { + params[key] = [ + ...(params[key] || []), + value, + ] + }) + + await this.router.navigateByUrl('/', { skipLocationChange: true }) + this.router.navigate([location.pathname], { + queryParams: params, + }); + }) + + this.stateService.uiState() + .pipe(take(1)) + .subscribe(state => { + if (!state.introScreenFinished) { + this.showIntro(); + } + }) + + this.mql = window.matchMedia('(max-width: 1200px)'); + this.sideDashOverlay = this.mql.matches; + + this.mql.addEventListener('change', () => { + this.sideDashOverlay = this.mql.matches; + + if (!this.sideDashOverlay) { + this.sideDashOpen.next(this.sideDashStatus === 'expanded') + } + }) + } + + ngAfterViewInit(): void { + this.sideDashOpen.next(this.sideDashStatus !== 'collapsed') + + if (this.integration instanceof TauriIntegrationService) { + let tauri = this.integration; + + tauri.shouldShow() + .then(show => { + console.log("should open window: ", show) + if (show) { + tauri.openApp(); + } + }); + } + } + + showIntro(): StepperRef { + const stepperRef = this.overlayStepper.create(IntroModule.Stepper) + + stepperRef.onFinish.subscribe(() => { + this.stateService.uiState() + .pipe( + take(1), + mergeMap(state => this.stateService.saveState({ + ...state, + introScreenFinished: true + })) + ) + .subscribe(); + }) + + return stepperRef; + } +} diff --git a/desktop/angular/src/app/app.module.ts b/desktop/angular/src/app/app.module.ts new file mode 100644 index 00000000..c90aaec5 --- /dev/null +++ b/desktop/angular/src/app/app.module.ts @@ -0,0 +1,240 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { PortalModule } from '@angular/cdk/portal'; +import { ScrollingModule } from '@angular/cdk/scrolling'; +import { CdkTableModule } from '@angular/cdk/table'; +import { CommonModule, registerLocaleData } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; + +import { APP_INITIALIZER, LOCALE_ID, NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { FaIconLibrary, FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { faGithub } from '@fortawesome/free-brands-svg-icons'; +import { far } from '@fortawesome/free-regular-svg-icons'; +import { fas } from '@fortawesome/free-solid-svg-icons'; +import { ConfigService, PortmasterAPIModule, StringSetting, getActualValue } from '@safing/portmaster-api'; +import { OverlayStepperModule, SfngAccordionModule, SfngDialogModule, SfngDropDownModule, SfngPaginationModule, SfngSelectModule, SfngTipUpModule, SfngToggleSwitchModule, SfngTooltipModule, TabModule, UiModule } from '@safing/ui'; +import MyYamlFile from 'js-yaml-loader!../i18n/helptexts.yaml'; +import * as i18n from 'ng-zorro-antd/i18n'; +import { MarkdownModule } from 'ngx-markdown'; +import { firstValueFrom } from 'rxjs'; +import { environment } from 'src/environments/environment'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { IntroModule } from './intro'; +import { NavigationComponent } from './layout/navigation/navigation'; +import { SideDashComponent } from './layout/side-dash/side-dash'; +import { AppOverviewComponent, AppViewComponent, QuickSettingInternetButtonComponent } from './pages/app-view'; +import { QsHistoryComponent } from './pages/app-view/qs-history/qs-history.component'; +import { QuickSettingSelectExitButtonComponent } from './pages/app-view/qs-select-exit/qs-select-exit'; +import { QuickSettingUseSPNButtonComponent } from './pages/app-view/qs-use-spn/qs-use-spn'; +import { DashboardPageComponent } from './pages/dashboard/dashboard.component'; +import { FeatureCardComponent } from './pages/dashboard/feature-card/feature-card.component'; +import { MonitorPageComponent } from './pages/monitor'; +import { SettingsComponent } from './pages/settings/settings'; +import { SPNModule } from './pages/spn/spn.module'; +import { SupportPageComponent } from './pages/support'; +import { SupportFormComponent } from './pages/support/form'; +import { NotificationsService } from './services'; +import { ActionIndicatorModule } from './shared/action-indicator'; +import { SfngAppIconModule } from './shared/app-icon'; +import { ConfigModule } from './shared/config'; +import { CountIndicatorModule } from './shared/count-indicator'; +import { CountryFlagModule } from './shared/country-flag'; +import { EditProfileDialog } from './shared/edit-profile-dialog'; +import { ExitScreenComponent } from './shared/exit-screen/exit-screen'; +import { ExpertiseModule } from './shared/expertise/expertise.module'; +import { ExternalLinkDirective } from './shared/external-link.directive'; +import { FeatureScoutComponent } from './shared/feature-scout'; +import { SfngFocusModule } from './shared/focus'; +import { FuzzySearchPipe } from './shared/fuzzySearch'; +import { LoadingComponent } from './shared/loading'; +import { SfngMenuModule } from './shared/menu'; +import { SfngMultiSwitchModule } from './shared/multi-switch'; +import { NetqueryModule } from './shared/netquery'; +import { NetworkScoutComponent } from './shared/network-scout'; +import { NotificationListComponent } from './shared/notification-list/notification-list.component'; +import { NotificationComponent } from './shared/notification/notification'; +import { CommonPipesModule } from './shared/pipes'; +import { ProcessDetailsDialogComponent } from './shared/process-details-dialog'; +import { PromptListComponent } from './shared/prompt-list/prompt-list.component'; +import { SecurityLockComponent } from './shared/security-lock'; +import { SPNAccountDetailsComponent } from './shared/spn-account-details'; +import { SPNLoginComponent } from './shared/spn-login'; +import { SPNStatusComponent } from './shared/spn-status'; +import { PilotWidgetComponent } from './shared/status-pilot'; +import { PlaceholderComponent } from './shared/text-placeholder'; +import { DashboardWidgetComponent } from './pages/dashboard/dashboard-widget/dashboard-widget.component'; +import { MergeProfileDialogComponent } from './pages/app-view/merge-profile-dialog/merge-profile-dialog.component'; +import { AppInsightsComponent } from './pages/app-view/app-insights/app-insights.component'; +import { INTEGRATION_SERVICE, integrationServiceFactory } from './integration'; +import { SupportProgressDialogComponent } from './pages/support/progress-dialog'; + +function loadAndSetLocaleInitializer(configService: ConfigService) { + return async function () { + let angularLocaleID = 'en-GB'; + let nzLocaleID: string = 'en_GB'; + + try { + const setting = await firstValueFrom(configService.get("core/locale")) + + const currentValue = getActualValue(setting as StringSetting); + switch (currentValue) { + case 'en-US': + angularLocaleID = 'en-US' + nzLocaleID = 'en_US' + break; + case 'en-GB': + angularLocaleID = 'en-GB' + nzLocaleID = 'en_GB' + break; + + default: + console.error(`Unsupported locale value: ${currentValue}, defaulting to en-GB`) + } + } catch (err) { + console.error(`failed to get locale setting, using default en-GB:`, err) + } + + try { + // Get name of module. + let localeModuleID = angularLocaleID; + if (localeModuleID == "en-US") { + localeModuleID = "en"; + } + + /* webpackInclude: /(en|en-GB)\.mjs$/ */ + /* webpackChunkName: "./l10n-base/[request]"*/ + await import(`../../node_modules/@angular/common/locales/${localeModuleID}.mjs`) + .then(locale => { + registerLocaleData(locale.default) + + localeConfig.localeId = angularLocaleID; + localeConfig.nzLocale = (i18n as any)[nzLocaleID]; + }) + } catch (err) { + console.error(`failed to load locale module for ${angularLocaleID}:`, err) + } + } +} + +const localeConfig = { + nzLocale: i18n.en_GB, + localeId: 'en-GB' +} + +@NgModule({ + declarations: [ + AppComponent, + NotificationComponent, + SettingsComponent, + MonitorPageComponent, + SideDashComponent, + NavigationComponent, + PilotWidgetComponent, + NotificationListComponent, + PromptListComponent, + FuzzySearchPipe, + AppViewComponent, + QuickSettingInternetButtonComponent, + QuickSettingUseSPNButtonComponent, + QuickSettingSelectExitButtonComponent, + AppOverviewComponent, + PlaceholderComponent, + LoadingComponent, + ExternalLinkDirective, + ExitScreenComponent, + SupportPageComponent, + SupportFormComponent, + SecurityLockComponent, + SPNStatusComponent, + FeatureScoutComponent, + SPNLoginComponent, + SPNAccountDetailsComponent, + NetworkScoutComponent, + EditProfileDialog, + ProcessDetailsDialogComponent, + QsHistoryComponent, + DashboardPageComponent, + DashboardWidgetComponent, + FeatureCardComponent, + MergeProfileDialogComponent, + AppInsightsComponent, + SupportProgressDialogComponent + ], + imports: [ + BrowserModule, + CommonModule, + BrowserAnimationsModule, + FormsModule, + ReactiveFormsModule, + AppRoutingModule, + FontAwesomeModule, + OverlayModule, + PortalModule, + CdkTableModule, + DragDropModule, + HttpClientModule, + MarkdownModule.forRoot(), + ScrollingModule, + SfngAccordionModule, + TabModule, + SfngTipUpModule.forRoot(MyYamlFile, NotificationsService), + SfngTooltipModule, + ActionIndicatorModule, + SfngDialogModule, + OverlayStepperModule, + IntroModule, + SfngDropDownModule, + SfngSelectModule, + SfngMultiSwitchModule, + SfngMenuModule, + SfngFocusModule, + SfngToggleSwitchModule, + SfngPaginationModule, + SfngAppIconModule, + ExpertiseModule, + ConfigModule, + CountryFlagModule, + CountIndicatorModule, + NetqueryModule, + CommonPipesModule, + UiModule, + SPNModule, + PortmasterAPIModule.forRoot({ + httpAPI: environment.httpAPI, + websocketAPI: environment.portAPI, + }), + ], + bootstrap: [AppComponent], + providers: [ + { + provide: APP_INITIALIZER, useFactory: loadAndSetLocaleInitializer, deps: [ConfigService], multi: true + }, + { + provide: i18n.NZ_I18N, useFactory: () => { + console.log("nz-locale is set to", localeConfig.nzLocale) + return localeConfig.nzLocale + } + }, + { + provide: LOCALE_ID, useFactory: () => { + console.log("locale-id is set to", localeConfig.localeId) + return localeConfig.localeId + } + }, + { + provide: INTEGRATION_SERVICE, + useFactory: integrationServiceFactory + } + ] +}) +export class AppModule { + constructor(library: FaIconLibrary) { + library.addIconPacks(fas, far); + library.addIcons(faGithub) + } +} + diff --git a/desktop/angular/src/app/integration/browser.ts b/desktop/angular/src/app/integration/browser.ts new file mode 100644 index 00000000..82504c3b --- /dev/null +++ b/desktop/angular/src/app/integration/browser.ts @@ -0,0 +1,41 @@ +import { AppInfo, IntegrationService, ProcessInfo } from "./integration"; + +export class BrowserIntegrationService implements IntegrationService { + writeToClipboard(text: string): Promise { + if (!!navigator.clipboard) { + return navigator.clipboard.writeText(text); + } + + return Promise.reject(new Error(`Clipboard API not supported`)) + } + + openExternal(pathOrUrl: string): Promise { + window.open(pathOrUrl, '_blank') + + return Promise.resolve(); + } + + getInstallDir(): Promise { + return Promise.reject('Not supported in browser') + } + + getAppIcon(_: ProcessInfo): Promise { + return Promise.reject('Not supported in browser') + } + + getAppInfo(_: ProcessInfo): Promise { + return Promise.reject('Not supported in browser') + } + + exitApp(): Promise { + window.close(); + + return Promise.resolve(); + } + + onExitRequest(cb: () => void): () => void { + // nothing to do, there + return () => { } + } +} + diff --git a/desktop/angular/src/app/integration/electron.ts b/desktop/angular/src/app/integration/electron.ts new file mode 100644 index 00000000..71b63984 --- /dev/null +++ b/desktop/angular/src/app/integration/electron.ts @@ -0,0 +1,55 @@ +import { BrowserIntegrationService } from "./browser"; +import { AppInfo, ProcessInfo } from "./integration"; + +export class ElectronIntegrationService extends BrowserIntegrationService { + + openExternal(pathOrUrl: string): Promise { + if (!!window.app) { + return window.app.openExternal(pathOrUrl); + } + + return Promise.reject('No electron API available') + } + + getInstallDir(): Promise { + if (!!window.app) { + return window.app.getInstallDir() + } + + return Promise.reject('No electron API available') + } + + getAppIcon(info: ProcessInfo): Promise { + if (!!window.app) { + return window.app.getFileIcon(info.execPath) + } + + return Promise.reject('No electron API available') + } + + getAppInfo(_: ProcessInfo): Promise { + return Promise.reject('Not supported in electron') + } + + exitApp(): Promise { + if (!!window.app) { + window.app.exitApp(); + } + + return Promise.resolve(); + } + + onExitRequest(cb: () => void): () => void { + let listener = (event: MessageEvent) => { + if (event.data === 'on-app-close') { + cb(); + } + } + + window.addEventListener('message', listener); + + return () => { + window.removeEventListener('message', listener) + } + } +} diff --git a/desktop/angular/src/app/integration/factory.ts b/desktop/angular/src/app/integration/factory.ts new file mode 100644 index 00000000..419f3ea9 --- /dev/null +++ b/desktop/angular/src/app/integration/factory.ts @@ -0,0 +1,22 @@ +import { InjectionToken } from "@angular/core"; +import { BrowserIntegrationService } from "./browser"; +import { ElectronIntegrationService } from "./electron"; +import { IntegrationService } from "./integration"; +import { TauriIntegrationService } from "./taur-app"; + +export function integrationServiceFactory(): IntegrationService { + if ('__TAURI__' in window) { + console.log("[app] running under tauri") + return new TauriIntegrationService(); + } + + if ('app' in window) { + console.log("[app] running under electron") + return new ElectronIntegrationService(); + } + + console.log("[app] running in browser") + return new BrowserIntegrationService(); +} + +export const INTEGRATION_SERVICE = new InjectionToken('INTEGRATION_SERVICE'); diff --git a/desktop/angular/src/app/integration/index.ts b/desktop/angular/src/app/integration/index.ts new file mode 100644 index 00000000..de9e8105 --- /dev/null +++ b/desktop/angular/src/app/integration/index.ts @@ -0,0 +1,2 @@ +export * from './integration'; +export * from './factory'; diff --git a/desktop/angular/src/app/integration/integration.ts b/desktop/angular/src/app/integration/integration.ts new file mode 100644 index 00000000..ae426353 --- /dev/null +++ b/desktop/angular/src/app/integration/integration.ts @@ -0,0 +1,41 @@ + +export interface AppInfo { + app_name: string; + comment: string; + icon_dataurl: string; + icon_path: string; +} + +export interface ProcessInfo { + execPath: string; + cmdline: string; + pid: number; + matchingPath: string; +} + +export interface IntegrationService { + /** writeToClipboard copies text to the system clipboard */ + writeToClipboard(text: string): Promise; + + /** openExternal opens a file or URL in an external window */ + openExternal(pathOrUrl: string): Promise; + + /** Gets the path to the portmaster installation directory */ + getInstallDir(): Promise; + + /** Load application information (currently linux only) */ + getAppInfo(info: ProcessInfo): Promise; + + /** Loads the application icon as a dataurl */ + getAppIcon(info: ProcessInfo): Promise; + + /** Closes the application, does not return */ + exitApp(): Promise; + + /** Registers a listener for on-close requests. */ + onExitRequest(cb: () => void): () => void; +} + + + + diff --git a/desktop/angular/src/app/integration/taur-app.ts b/desktop/angular/src/app/integration/taur-app.ts new file mode 100644 index 00000000..f48c3499 --- /dev/null +++ b/desktop/angular/src/app/integration/taur-app.ts @@ -0,0 +1,216 @@ +import { AppInfo, IntegrationService, ProcessInfo } from "./integration"; +import { writeText } from '@tauri-apps/plugin-clipboard-manager'; +import { open } from '@tauri-apps/plugin-shell'; +import { listen, once } from '@tauri-apps/api/event'; +import { invoke } from '@tauri-apps/api/core' +import { getCurrent, Window } from '@tauri-apps/api/window'; + +// Returns a new uuidv4. If crypto.randomUUID is not available it fals back to +// using Math.random(). While this is not as random as it should be it's still +// enough for our use-case here (which is just to generate a random response-id). +function uuid(): string { + if (typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + + // This one is not really random and not RFC compliant but serves enough for fallback + // purposes if the UI is opened in a browser that does not yet support randomUUID + console.warn('Using browser with lacking support for crypto.randomUUID()'); + + return Date.now().toString(36) + Math.random().toString(36).substring(2); +} + +function asyncInvoke(method: string, args: object): Promise { + return new Promise((resolve, reject) => { + const eventId = uuid(); + + once(eventId, (event) => { + if (typeof event.payload === 'object' && 'error' in event.payload) { + reject(event.payload); + return + }; + + resolve(event.payload); + }) + + invoke(method, { + ...args, + responseId: eventId, + }).catch((err: any) => { + console.error("tauri:invoke rejected: ", method, args, err); + reject(err) + }); + }) +} + +export type ServiceManagerStatus = 'Running' | 'Stopped' | 'NotFound' | 'unsupported service manager' | 'unsupported operating system'; + +export class TauriIntegrationService implements IntegrationService { + private withPrompts = false; + + constructor() { + this.shouldHandlePrompts() + .then(result => { + this.withPrompts = result; + }); + + // listen for the portmaster:show event that is emitted + // when tauri want's to tell us that we should make our + // window visible. + listen("portmaster:show", () => { + this.openApp(); + }) + } + + writeToClipboard(text: string): Promise { + return writeText(text); + } + + openExternal(pathOrUrl: string): Promise { + return open(pathOrUrl); + } + + getInstallDir(): Promise { + return Promise.reject("not yet supported in tauri") + } + + getAppInfo(info: ProcessInfo): Promise { + return asyncInvoke("plugin:portmaster|get_app_info", { + ...info, + }) + } + + getAppIcon(info: ProcessInfo): Promise { + return this.getAppInfo(info) + .then(info => info.icon_dataurl) + } + + exitApp(): Promise { + // we have two options here: + // - close(): close the native tauri window and release all resources of it. + // this has the disadvantage that if the user re-opens the window, + // it will take slightly longer because angular need to re-bootstrap + // the application. + // + // IMPORTANT: the angular application will automatically launch prompt + // windows via the tauri window interface. If we would call close(), + // those prompts wouldn't work anymore because the angular app would not + // be running in the background. + // + // - hide(): just set the window visibility to false. The advantage is that angular + // is still running and interacting with portmaster but it also means that + // we waste some system resources due to tauri window objects and the angular + // application. + + getCurrent().hide() + + return Promise.resolve(); + } + + // Tauri specific functions that are not defined in the IntegrationService interface. + // to use those methods you must check if integration instanceof TauriIntegrationService. + + async shouldShow(): Promise { + try { + const response = await invoke("plugin:portmaster|should_show"); + return response === "show"; + } catch (err) { + console.error(err); + return true; + } + } + + async shouldHandlePrompts(): Promise { + try { + const response = await invoke("plugin:portmaster|should_handle_prompts") + return response === "true" + } catch (err) { + console.error(err); + return false; + } + } + + get_state(key: string): Promise { + return invoke("plugin:portmaster|get_state"); + } + + set_state(key: string, value: string): Promise { + return invoke("plugin:portmaster|set_state", { + key, + value + }) + } + + getServiceManagerStatus(): Promise { + return asyncInvoke("plugin:portmaster|get_service_manager_status", {}) + } + + startService(): Promise { + return asyncInvoke("plugin:portmaster|start_service", {}); + } + + onExitRequest(cb: () => void): () => void { + let unlisten: () => void = () => { }; + + listen('exit-requested', () => { + cb(); + }).then(cleanup => { + unlisten = cleanup; + }) + + return () => { + unlisten(); + } + } + + openApp() { + Window.getByLabel("splash")?.close(); + const current = Window.getCurrent() + + current.isVisible() + .then(visible => { + if (!visible) { + current.show(); + current.setFocus(); + } + }); + } + + closePrompt() { + Window.getByLabel("prompt")?.close(); + } + + openPrompt() { + if (!this.withPrompts) { + return; + } + + if (Window.getByLabel("prompt")) { + return; + } + + let promptWindow = new Window("prompt", { + alwaysOnTop: true, + decorations: false, + minimizable: false, + maximizable: false, + resizable: false, + title: 'Portmaster Prompt', + visible: false, // the prompt marks it self as visible. + skipTaskbar: true, + closable: false, + center: true, + width: 600, + height: 300, + + // in src/main.ts we check the current location path + // and if it matches /prompt, we bootstrap the PromptEntryPointComponent + // instead of the AppComponent. + url: `http://${window.location.host}/prompt`, + } as any) + + promptWindow.once("tauri://error", (err) => { + console.error(err); + }); + } +} diff --git a/desktop/angular/src/app/intro/index.ts b/desktop/angular/src/app/intro/index.ts new file mode 100644 index 00000000..b0328f4b --- /dev/null +++ b/desktop/angular/src/app/intro/index.ts @@ -0,0 +1 @@ +export * from './intro.module'; diff --git a/desktop/angular/src/app/intro/intro.module.ts b/desktop/angular/src/app/intro/intro.module.ts new file mode 100644 index 00000000..8be0f36b --- /dev/null +++ b/desktop/angular/src/app/intro/intro.module.ts @@ -0,0 +1,36 @@ +import { OverlayModule } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { SfngDropDownModule, SfngTipUpModule, StepperConfig } from "@safing/ui"; +import { ConfigModule } from "../shared/config"; +import { Step1WelcomeComponent } from "./step-1-welcome"; +import { Step2TrackersComponent } from "./step-2-trackers"; +import { Step3DNSComponent } from "./step-3-dns"; +import { Step4TipupsComponent } from "./step-4-tipups"; + +const steps = [ + Step1WelcomeComponent, + Step2TrackersComponent, + Step3DNSComponent, + Step4TipupsComponent, +] + +@NgModule({ + imports: [ + CommonModule, + OverlayModule, + FormsModule, + SfngDropDownModule, + ConfigModule, + SfngTipUpModule, + ], + declarations: steps +}) +export class IntroModule { + static Stepper: StepperConfig = { + steps: steps, + canAbort: (idx) => idx === 0, + } +} + diff --git a/desktop/angular/src/app/intro/step-1-welcome/index.ts b/desktop/angular/src/app/intro/step-1-welcome/index.ts new file mode 100644 index 00000000..4261731e --- /dev/null +++ b/desktop/angular/src/app/intro/step-1-welcome/index.ts @@ -0,0 +1 @@ +export * from './step-1-welcome'; diff --git a/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.html b/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.html new file mode 100644 index 00000000..2c8010c6 --- /dev/null +++ b/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.html @@ -0,0 +1,14 @@ +

Portmaster Protects Your Privacy

+ +

+ Portmaster enhances your privacy with powerful defaults - no configuration needed! Of course you can customize + everything to your specific needs. +

+ + + + + diff --git a/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.ts b/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.ts new file mode 100644 index 00000000..e6af4a15 --- /dev/null +++ b/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component, Inject, TemplateRef, ViewChild } from "@angular/core"; +import { Step, StepRef, STEP_REF } from "@safing/ui"; +import { of } from "rxjs"; + +@Component({ + templateUrl: './step-1-welcome.html', + styleUrls: ['../step.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Step1WelcomeComponent implements Step { + validChange = of(true) + + readonly nextButtonLabel = 'Quick Setup'; + + @ViewChild('buttonTemplate', { static: true }) + buttonTemplate!: TemplateRef; + + constructor( + @Inject(STEP_REF) public stepRef: StepRef, + ) { } +} + diff --git a/desktop/angular/src/app/intro/step-2-trackers/index.ts b/desktop/angular/src/app/intro/step-2-trackers/index.ts new file mode 100644 index 00000000..60b7451b --- /dev/null +++ b/desktop/angular/src/app/intro/step-2-trackers/index.ts @@ -0,0 +1 @@ +export * from './step-2-trackers' diff --git a/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.html b/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.html new file mode 100644 index 00000000..bfd16cd1 --- /dev/null +++ b/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.html @@ -0,0 +1,11 @@ +

Trackers Are Blocked System-Wide

+ +

Portmaster automatically blocks ads, trackers and malware hosts on your whole device. Portmaster knows what to block + through trusted domain lists, which are also used by Ad-Blockers in browsers, etc. You can always customize this in + the settings.

+ + + + + diff --git a/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.ts b/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.ts new file mode 100644 index 00000000..82d5be4d --- /dev/null +++ b/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.ts @@ -0,0 +1,48 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ConfigService, Setting } from "@safing/portmaster-api"; +import { Step } from "@safing/ui"; +import { of } from "rxjs"; +import { mergeMap } from "rxjs/operators"; +import { SaveSettingEvent } from "src/app/shared/config/generic-setting"; + +@Component({ + templateUrl: './step-2-trackers.html', + styleUrls: ['../step.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Step2TrackersComponent implements Step, OnInit { + private destroyRef = inject(DestroyRef); + + validChange = of(true) + + setting: Setting | null = null; + + constructor( + public configService: ConfigService, + public readonly elementRef: ElementRef, + private cdr: ChangeDetectorRef, + ) { } + + ngOnInit(): void { + this.configService.get('filter/lists') + .pipe( + mergeMap(setting => { + this.setting = setting; + + return this.configService.watch(setting.Key) + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(value => { + this.setting!.Value = value; + + this.cdr.markForCheck(); + }); + } + + saveSetting(event: SaveSettingEvent) { + this.configService.save(event.key, event.value) + .subscribe() + } +} diff --git a/desktop/angular/src/app/intro/step-3-dns/index.ts b/desktop/angular/src/app/intro/step-3-dns/index.ts new file mode 100644 index 00000000..85ccdb1a --- /dev/null +++ b/desktop/angular/src/app/intro/step-3-dns/index.ts @@ -0,0 +1 @@ +export * from './step-3-dns' diff --git a/desktop/angular/src/app/intro/step-3-dns/step-3-dns.html b/desktop/angular/src/app/intro/step-3-dns/step-3-dns.html new file mode 100644 index 00000000..aa17288a --- /dev/null +++ b/desktop/angular/src/app/intro/step-3-dns/step-3-dns.html @@ -0,0 +1,17 @@ +

Secure DNS for All Connections

+ +

Portmaster automatically encrypts all your DNS queries to safeguard them from prying eyes. Portmaster sets a default + provider, but you can always switch to a custom DNS-over-TLS provider in the global settings.

+ + +
+ + + +
+
diff --git a/desktop/angular/src/app/intro/step-3-dns/step-3-dns.ts b/desktop/angular/src/app/intro/step-3-dns/step-3-dns.ts new file mode 100644 index 00000000..a1dddae6 --- /dev/null +++ b/desktop/angular/src/app/intro/step-3-dns/step-3-dns.ts @@ -0,0 +1,106 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ConfigService, QuickSetting, Setting, applyQuickSetting } from "@safing/portmaster-api"; +import { Step } from "@safing/ui"; +import { of } from "rxjs"; +import { mergeMap } from "rxjs/operators"; +import { SaveSettingEvent } from "src/app/shared/config/generic-setting"; + +interface QuickSettingModel extends QuickSetting { + active: boolean; +} + +@Component({ + templateUrl: './step-3-dns.html', + styleUrls: ['../step.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Step3DNSComponent implements Step, OnInit { + private destroyRef = inject(DestroyRef); + + validChange = of(true) + + setting: Setting | null = null; + quickSettings: QuickSettingModel[] = []; + isCustomValue = false; + + constructor( + public configService: ConfigService, + public readonly elementRef: ElementRef, + private cdr: ChangeDetectorRef, + ) { } + + private getQuickSettings(): QuickSettingModel[] { + if (!this.setting) { + return []; + } + + let val = this.setting.Annotations["safing/portbase:ui:quick-setting"]; + if (val === undefined) { + return []; + } + + if (!Array.isArray(val)) { + return [{ + ...val, + active: false, + }] + } + + return val.map(v => ({ + ...v, + active: false, + })) + } + + ngOnInit(): void { + this.configService.get('dns/nameservers') + .pipe( + mergeMap(setting => { + this.setting = setting; + this.quickSettings = this.getQuickSettings(); + return this.configService.watch(setting.Key) + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(value => { + this.setting!.Value = value; + + let hasActive = false; + this.isCustomValue = false; + + this.quickSettings.forEach(setting => { + if (this.setting?.Value !== undefined && JSON.stringify(this.setting.Value) === JSON.stringify(setting.Value)) { + setting.active = true; + hasActive = true; + } else { + setting.active = false; + } + }); + + if (!hasActive) { + if (this.setting?.Value !== undefined && JSON.stringify(this.setting!.Value) !== JSON.stringify(this.setting!.DefaultValue)) { + this.isCustomValue = true; + } else if (this.quickSettings.length > 0) { + this.quickSettings[0].active = true; + } + } + + this.cdr.markForCheck(); + }); + } + + saveSetting(event: SaveSettingEvent) { + this.configService.save(event.key, event.value) + .subscribe() + } + + applyQuickSetting(action: QuickSetting) { + const newValue = applyQuickSetting( + this.setting!.Value || this.setting!.DefaultValue, + action, + ) + this.configService.save(this.setting!.Key, newValue) + .subscribe(); + } +} diff --git a/desktop/angular/src/app/intro/step-4-tipups/index.ts b/desktop/angular/src/app/intro/step-4-tipups/index.ts new file mode 100644 index 00000000..02886c50 --- /dev/null +++ b/desktop/angular/src/app/intro/step-4-tipups/index.ts @@ -0,0 +1 @@ +export * from './step-4-tipups' diff --git a/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.html b/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.html new file mode 100644 index 00000000..f15afd36 --- /dev/null +++ b/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.html @@ -0,0 +1,11 @@ +

Learn More as You Explore

+ +

Portmaster has a lot more to offer. When you decide to dive deeper you can always click on an information icon to + learn more about a certain feature. Look out for those!

+ +
+ Click Me! +
+ +
+
diff --git a/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.ts b/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.ts new file mode 100644 index 00000000..5b0463a1 --- /dev/null +++ b/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.ts @@ -0,0 +1,12 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { Step } from "@safing/ui"; +import { of } from "rxjs"; + +@Component({ + templateUrl: './step-4-tipups.html', + styleUrls: ['../step.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Step4TipupsComponent implements Step { + validChange = of(true) +} diff --git a/desktop/angular/src/app/intro/step.scss b/desktop/angular/src/app/intro/step.scss new file mode 100644 index 00000000..1d17d9a2 --- /dev/null +++ b/desktop/angular/src/app/intro/step.scss @@ -0,0 +1,11 @@ +:host { + @apply flex flex-col items-center justify-center; +} + +h1 { + @apply text-primary text-2xl font-medium capitalize text-center py-5; +} + +p { + @apply text-tertiary text-sm font-medium text-center; +} diff --git a/desktop/angular/src/app/layout/navigation/navigation.html b/desktop/angular/src/app/layout/navigation/navigation.html new file mode 100644 index 00000000..0359b2dd --- /dev/null +++ b/desktop/angular/src/app/layout/navigation/navigation.html @@ -0,0 +1,230 @@ +
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+ + + + +
+ + + + diff --git a/desktop/angular/src/app/layout/navigation/navigation.scss b/desktop/angular/src/app/layout/navigation/navigation.scss new file mode 100644 index 00000000..4683bec5 --- /dev/null +++ b/desktop/angular/src/app/layout/navigation/navigation.scss @@ -0,0 +1,98 @@ +:host { + height: 100vh; + top: 0px; + position: sticky; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + user-select: none; + + .logo-image { + @apply w-6 -top-3 -left-3 absolute; + position: absolute; + } + + svg { + &:not(.connected) { + animation-timing-function: cubic-bezier(0.445, 0.05, 0.55, 0.95); + + path.inner { + fill: theme('colors.info.red'); + } + } + } + + div.nav-list { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + } + + div.nav-lower-list { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + padding-bottom: 1.5rem; + } + + div.link { + @apply my-2; + + width: 2rem; + height: 2rem; + border-radius: 10px; + + display: flex; + justify-content: space-around; + align-items: center; + + cursor: pointer; + + & { + outline: none; + + svg, + fa-icon { + opacity: .5; + } + } + + &:target, + &.active { + background-color: #2c2c2c; + + svg, + fa-icon { + opacity: 1; + transform: scale(1.08); + } + } + + &:hover { + + svg, + fa-icon { + opacity: 1; + } + } + + svg, + fa-icon { + + &.dash, + &.spn, + &.monitor, + &.app, + &.help, + &.settings { + @apply text-white; + width: 1.1rem; + position: relative; + stroke: currentColor; + } + } + } +} diff --git a/desktop/angular/src/app/layout/navigation/navigation.ts b/desktop/angular/src/app/layout/navigation/navigation.ts new file mode 100644 index 00000000..4752301f --- /dev/null +++ b/desktop/angular/src/app/layout/navigation/navigation.ts @@ -0,0 +1,298 @@ +import { INTEGRATION_SERVICE, IntegrationService } from 'src/app/integration'; +import { ConnectedPosition } from '@angular/cdk/overlay'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Inject, OnInit, Output, inject } from '@angular/core'; +import { ConfigService, DebugAPI, PortapiService, SPNService, StringSetting } from '@safing/portmaster-api'; +import { tap } from 'rxjs/operators'; +import { AppComponent } from 'src/app/app.component'; +import { NotificationType, NotificationsService, StatusService, VersionStatus } from 'src/app/services'; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; +import { fadeInAnimation, fadeOutAnimation } from 'src/app/shared/animations'; +import { ExitService } from 'src/app/shared/exit-screen'; +import { TauriIntegrationService } from 'src/app/integration/taur-app'; + +@Component({ + selector: 'app-navigation', + templateUrl: './navigation.html', + styleUrls: ['./navigation.scss'], + exportAs: 'navigation', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInAnimation, + fadeOutAnimation, + ] +}) +export class NavigationComponent implements OnInit { + private readonly integration = inject(INTEGRATION_SERVICE); + + /** Emits the current portapi connection state on changes. */ + readonly connected$ = this.portapi.connected$; + + /** @private The available and selected resource versions. */ + versions: VersionStatus | null = null; + + /** Whether or not we have new, unseen notifications */ + hasNewNotifications = false; + + /** The color to use for the notifcation-available hint (dot) */ + notificationColor: string = 'text-green-300'; + + /** Whether or not we have new, unseen prompts */ + hasNewPrompts = false; + + /** Whether or not prompting is globally enabled. */ + globalPromptingEnabled = false; + + @Output() + sideDashChange = new EventEmitter<'collapsed' | 'expanded' | 'force-overlay'>(); + + /** Whether or not the side dash should be expanded or collapsed */ + sideDashStatus: 'collapsed' | 'expanded' = 'expanded'; + + constructor( + private portapi: PortapiService, + private exitService: ExitService, + private statusService: StatusService, + private configService: ConfigService, + private appComponent: AppComponent, + private debugAPI: DebugAPI, + private actionIndicator: ActionIndicatorService, + private notificationService: NotificationsService, + private spnService: SPNService, + private cdr: ChangeDetectorRef + ) { } + + dropDownPositions: ConnectedPosition[] = [ + { + originX: 'end', + originY: 'top', + overlayX: 'start', + overlayY: 'top' + } + ] + + ngOnInit() { + const mql = window.matchMedia('(max-width: 1200px)'); + + if (mql.matches) { + this.sideDashStatus = 'collapsed'; + this.sideDashChange.next(this.sideDashStatus); + } + + mql.addEventListener('change', () => { + if (mql.matches) { + this.sideDashStatus = 'collapsed'; + } else { + this.sideDashStatus = 'expanded'; + } + this.sideDashChange.next(this.sideDashStatus); + }) + + this.statusService.getVersions() + .subscribe(versions => { + this.versions = versions; + this.cdr.markForCheck(); + }); + + this.configService.watch('filter/defaultAction') + .subscribe(defaultAction => { + this.globalPromptingEnabled = defaultAction === 'ask'; + this.cdr.markForCheck(); + }) + + this.notificationService.new$ + .subscribe(notif => { + + + if (notif.some(n => n.Type === NotificationType.Prompt && n.EventID.startsWith("filter:prompt"))) { + this.hasNewPrompts = true; + + if (this.integration instanceof TauriIntegrationService) { + this.integration.openPrompt(); + } + } else { + this.hasNewPrompts = false; + + if (this.integration instanceof TauriIntegrationService) { + this.integration.closePrompt(); + } + } + + if (notif.some(n => !n.EventID.startsWith("filter:prompt"))) { + this.hasNewNotifications = true; + } else { + this.hasNewNotifications = false; + } + + if (notif.some(n => n.Type === NotificationType.Error)) { + this.notificationColor = 'text-red-300'; + } else if (notif.some(n => n.Type === NotificationType.Warning)) { + this.notificationColor = 'text-yellow-300'; + } else { + this.notificationColor = 'text-green-300'; + } + + this.cdr.markForCheck(); + }) + } + + toggleSideDash(event: MouseEvent) { + let notify: 'expanded' | 'collapsed' | 'force-overlay' = this.sideDashStatus; + + if (this.sideDashStatus === 'collapsed') { + this.sideDashStatus = 'expanded'; + notify = 'expanded'; + if (event.shiftKey) { + notify = 'force-overlay' + } + } else { + this.sideDashStatus = 'collapsed'; + notify = 'collapsed' + } + + this.sideDashChange.next(notify); + } + + /** + * @private + * Injects a ui/reload event and performs a complete + * reload of the window once the portmaster re-opened the + * UI bundle. + */ + reloadUI(_: Event) { + this.portapi.reloadUI() + .pipe( + tap(() => { + setTimeout(() => window.location.reload(), 1000) + }) + ) + .subscribe(this.actionIndicator.httpObserver( + 'Reloading UI ...', + 'Failed to Reload UI', + )) + } + + /** Re-initialize the SPN */ + reinitSPN(_: Event) { + this.portapi.reinitSPN() + .subscribe(this.actionIndicator.httpObserver( + 'Re-initialized SPN', + 'Failed to re-initialize the SPN' + )) + } + + /** Logs the user out of the SPN completely by purgin the user profile from the local storage */ + logoutCompletely(_: Event) { + this.spnService.logout(true) + .subscribe(this.actionIndicator.httpObserver( + 'Logout', + 'You have been logged out of the SPN completely.' + )) + } + + /** + * @private + * Clear the DNS name cache. + */ + clearDNSCache(_: Event) { + this.portapi.clearDNSCache() + .subscribe(this.actionIndicator.httpObserver( + 'DNS Cache Cleared', + 'Failed to Clear DNS Cache.', + )) + } + + cleanupHistory(_: Event) { + this.portapi.cleanupHistory() + .subscribe(this.actionIndicator.httpObserver( + 'Network History Cleaned Up', + 'Failed to Cleanup Network History.' + )) + } + + /** + * @private + * Trigger downloading of updates + * + * @param event - The mouse event + */ + downloadUpdates(event: Event) { + this.portapi.checkForUpdates() + .subscribe(this.actionIndicator.httpObserver( + 'Downloading Updates ...', + 'Failed to Check for Updates', + )) + } + + /** + * @private + * Trigger a shutdown of the portmaster-core service + */ + shutdown(_: Event) { + this.exitService.shutdownPortmaster(); + } + + /** + * @private + * Trigger a restart of the portmaster-core service. Requires + * that portmaster has been started with a service-wrapper. + * + * @param event The mouse event + */ + restart(event: Event) { + // prevent default and stop-propagation to avoid + // expanding the accordion body. + event.preventDefault(); + event.stopPropagation(); + + this.portapi.restartPortmaster() + .subscribe(this.actionIndicator.httpObserver( + 'Restarting ...', + 'Failed to Restart', + )) + } + + /** + * @private + * Opens the data-directory of the portmaster installation. + * Requires the application to run inside electron. + */ + async openDataDir(event: Event) { + const dir = await this.integration.getInstallDir() + await this.integration.openExternal(dir); + } + + openChangeLog() { + const url = "https://github.com/safing/portmaster/releases"; + this.integration.openExternal(url); + } + + showIntro() { + this.appComponent.showIntro() + } + + resetBroadcastState() { + this.portapi.resetBroadcastState() + .subscribe(this.actionIndicator.httpObserver( + 'Broadcast State Cleared', + 'Failed to Reset Broadcast State.', + )) + } + + copyDebugInfo(event: Event) { + // prevent default and stop-propagation to avoid + // expanding the accordion body. + event.preventDefault(); + event.stopPropagation(); + + this.debugAPI.getCoreDebugInfo() + .subscribe( + async info => { + await this.integration.writeToClipboard(info); + }, + err => { + console.error(err); + this.actionIndicator.error('Failed loading debug data', err); + } + ) + } +} diff --git a/desktop/angular/src/app/layout/side-dash/side-dash.html b/desktop/angular/src/app/layout/side-dash/side-dash.html new file mode 100644 index 00000000..81e8b15f --- /dev/null +++ b/desktop/angular/src/app/layout/side-dash/side-dash.html @@ -0,0 +1,10 @@ +
+ +
+ + + + + + + diff --git a/desktop/angular/src/app/layout/side-dash/side-dash.scss b/desktop/angular/src/app/layout/side-dash/side-dash.scss new file mode 100644 index 00000000..6862c275 --- /dev/null +++ b/desktop/angular/src/app/layout/side-dash/side-dash.scss @@ -0,0 +1,11 @@ +:host { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + overflow: hidden; + overflow-y: hidden; + width: 419px; + + @apply pt-4; +} diff --git a/desktop/angular/src/app/layout/side-dash/side-dash.ts b/desktop/angular/src/app/layout/side-dash/side-dash.ts new file mode 100644 index 00000000..c4836634 --- /dev/null +++ b/desktop/angular/src/app/layout/side-dash/side-dash.ts @@ -0,0 +1,13 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'app-side-dash', + templateUrl: './side-dash.html', + styleUrls: ['./side-dash.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SideDashComponent { + /** Whether or not a SPN account login is required */ + spnLoginRequired = false; + +} diff --git a/desktop/angular/src/app/package-lock.json b/desktop/angular/src/app/package-lock.json new file mode 100644 index 00000000..4ef966ec --- /dev/null +++ b/desktop/angular/src/app/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "portmaster", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "portmaster", + "devDependencies": { + "@types/node": "^17.0.31" + } + }, + "node_modules/@types/node": { + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==", + "dev": true + } + }, + "dependencies": { + "@types/node": { + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==", + "dev": true + } + } +} diff --git a/desktop/angular/src/app/package.json b/desktop/angular/src/app/package.json new file mode 100644 index 00000000..119f6216 --- /dev/null +++ b/desktop/angular/src/app/package.json @@ -0,0 +1,12 @@ +{ + "name": "portmaster", + "private": true, + "description_1": "This is a special package.json file that is not used by package managers.", + "description_2": "It is used to tell the tools and bundlers whether the code under this directory is free of code with non-local side-effect. Any code that does have non-local side-effects can't be well optimized (tree-shaken) and will result in unnecessary increased payload size.", + "description_3": "It should be safe to set this option to 'false' for new applications, but existing code bases could be broken when built with the production config if the application code does contain non-local side-effects that the application depends on.", + "description_4": "To learn more about this file see: https://angular.io/config/app-package-json.", + "sideEffects": false, + "devDependencies": { + "@types/node": "^17.0.31" + } +} diff --git a/desktop/angular/src/app/pages/app-view/app-insights/app-insights.component.html b/desktop/angular/src/app/pages/app-view/app-insights/app-insights.component.html new file mode 100644 index 00000000..32ab491c --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/app-insights/app-insights.component.html @@ -0,0 +1,13 @@ +
+ + + + + + + + + + + +
diff --git a/desktop/angular/src/app/pages/app-view/app-insights/app-insights.component.ts b/desktop/angular/src/app/pages/app-view/app-insights/app-insights.component.ts new file mode 100644 index 00000000..264a5615 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/app-insights/app-insights.component.ts @@ -0,0 +1,96 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Input, OnInit, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { AppProfile, BandwidthChartResult, ChartResult, Netquery } from '@safing/portmaster-api'; +import { repeat } from 'rxjs'; +import { CircularBarChartConfig, splitQueryResult } from 'src/app/shared/netquery/circular-bar-chart/circular-bar-chart.component'; +import { DefaultBandwidthChartConfig } from 'src/app/shared/netquery/line-chart/line-chart'; + +interface CountryBarData { + series: 'country'; + value: number; + country: string; +} + +@Component({ + selector: 'app-app-insights', + templateUrl: './app-insights.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AppInsightsComponent implements OnInit { + private readonly netquery = inject(Netquery); + private readonly destroyRef = inject(DestroyRef); + private readonly cdr = inject(ChangeDetectorRef); + + @Input() + profile!: AppProfile; + + connectionChart: ChartResult[] = []; + + bandwidthChart: BandwidthChartResult[] = []; + + bwChartConfig = DefaultBandwidthChartConfig; + + countryData: CountryBarData[] = []; + + readonly countryBarConfig: CircularBarChartConfig = { + stack: 'country', + seriesKey: 'series', + value: 'value', + ticks: 3, + colorAsClass: true, + series: { + 'count': { + color: 'text-green-300 text-opacity-50', + }, + }, + } + + ngOnInit() { + const key = `${this.profile.Source}/${this.profile.ID}` + + this.netquery.batch({ + countryData: { + select: [ + 'country', + { $count: { field: '*', as: 'count' } }, + ], + query: { + internal: { $eq: false }, + country: { $ne: '' } + }, + groupBy: ['country'] + } + }) + .pipe( + repeat({ delay: 10000 }), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe(result => { + this.countryData = splitQueryResult(result.countryData, ['count']) as CountryBarData[]; + console.log(this.countryData) + this.cdr.markForCheck(); + }) + + this.netquery.activeConnectionChart({ profile: key }) + .pipe( + repeat({ delay: 10000 }), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe(data => { + this.connectionChart = data; + this.cdr.markForCheck(); + }) + + this.netquery.bandwidthChart({ profile: key }, undefined, 60) + .pipe( + repeat({ delay: 10000 }), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe(data => { + this.bandwidthChart = data; + this.cdr.markForCheck(); + }) + + } + +} diff --git a/desktop/angular/src/app/pages/app-view/app-view.html b/desktop/angular/src/app/pages/app-view/app-view.html new file mode 100644 index 00000000..8003881c --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/app-view.html @@ -0,0 +1,425 @@ + + +
+ +
+ Apps + + + + + + {{ appProfile.Name }} +
+ + + +
+ + + +
+
+ +
+ +

+ + + {{appProfile!.Name}} +

+ + +
+
+ Path: + + {{ applicationDirectory }} + +
+
+ Binary: + + {{ binaryName }} + +
+
+ Active Connections: + {{stats?.countAliveConnections || 0}} +
+
+ Network History: + + As of {{ historyAvailableSince | date }} + Remove all {{ connectionsInHistory }} Connections + + + None + +
+
+ + +
+ + + + + + + + + + + + + + + + Edit App Profile + Export App Profile + Delete App Profile + + + +
+
+ + +
+
+

{{ stats!.size | prettyCount }}

+ Connections +
+ +
+

{{ (100 / stats!.size) * (stats!.size + - stats!.countAllowed) | number:'1.0-1' }}%

+ Blocked +
+ +
+

+ {{ stats.bytes_received | bytes }} +

+ + + Available in Plus + + + Received +
+ +
+

+ {{ stats.bytes_sent | bytes }} +

+ Sent +
+ +
+
+ +
+ + + +
+
+ + + + +
+ + +
+
+ + +
+ +
+ + + + + + + Get Help + + +
+ + + + View All + + + View Active + + +
+
+ +
+
+ App Specific Settings + +
+
+ + + + + + + +
+ + + + +

+ + {{ appProfile!.Name }} + + is fully using the global settings. +

+

+ Start creating exceptions for it now. +

+ +
+
+
+
+ + + +
+ +
+
+

+ + {{appProfile!.Name}} +

+

+ + {{appProfile!.PresentationPath}} +

+
+ +
+

+ + {{appProfile!.Created * 1000 | date:'medium'}} +

+

+ + {{appProfile!.LastEdited * 1000 | date:'medium'}} + N/A +

+
+ + +
+

+ + {{!!appProfile!.Internal ? 'yes' : 'no'}} +

+

+ + {{appProfile!.Source}} +

+

+ + {{appProfile!.ID}} +

+
+ +
+

+ + {{layeredProfile?.RevisionCounter}} +

+

+ + +

    +
  1. + {{layer}} +
  2. +
+ +

+
+
+
+ + +
+

+ + + + Description + +

+ + + +
+ + +
+

+ + + + Warning + +

+ + + + updated + {{ appProfile.WarningLastUpdated | timeAgo }} +
+ + +
+

+ + + + + Fingerprints + +

+ + This profile will be applied to processes that match one of the following + fingerprints: + +
+ + + + + + + + {{ fp.Type }} + + + where + {{ fp.Type === 'tag' ? (tagNames[fp.Key] || fp.Key) : fp.Key }} + + + {{ fp.Operation }} + {{ fp.Value }} +
+
+ + +
+

+ + + + Delete Profile + +

+ + You can completely delete this profile to get rid of any settings. The profile + will + be automatically re-created with default settings as soon as the application starts to use the + network. + + +
+ + +
+

+ + + + + + + Debugging + +

+ + When reporting issues with this app please make sure to include the + following + debug information: + + +
+
+
+ +
+ +
+
+
+
+ + diff --git a/desktop/angular/src/app/pages/app-view/app-view.scss b/desktop/angular/src/app/pages/app-view/app-view.scss new file mode 100644 index 00000000..977c3b72 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/app-view.scss @@ -0,0 +1,3 @@ +:host { + @apply flex flex-col h-screen max-h-screen; +} diff --git a/desktop/angular/src/app/pages/app-view/app-view.ts b/desktop/angular/src/app/pages/app-view/app-view.ts new file mode 100644 index 00000000..85a365a2 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/app-view.ts @@ -0,0 +1,641 @@ +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnDestroy, + OnInit, + ViewChild, + inject, +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, Router } from '@angular/router'; +import { + AppProfile, + AppProfileService, + Condition, + ConfigService, + Database, + DebugAPI, + ExpertiseLevel, + FeatureID, + FlatConfigObject, + IProfileStats, + LayeredProfile, + Netquery, + PortapiService, + SPNService, + Setting, + flattenProfileConfig, + setAppSetting +} from '@safing/portmaster-api'; +import { SfngDialogService } from '@safing/ui'; +import { + BehaviorSubject, + Observable, + Subscription, + combineLatest, + interval, + of, + throwError, +} from 'rxjs'; +import { + catchError, + distinctUntilChanged, + map, + mergeMap, + startWith, + switchMap, +} from 'rxjs/operators'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; +import { SessionDataService } from 'src/app/services'; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; +import { fadeInAnimation, fadeOutAnimation } from 'src/app/shared/animations'; +import { + ExportConfig, + ExportDialogComponent, +} from 'src/app/shared/config/export-dialog/export-dialog.component'; +import { SaveSettingEvent } from 'src/app/shared/config/generic-setting/generic-setting'; +import { ExpertiseService } from 'src/app/shared/expertise'; +import { SfngNetqueryViewer } from 'src/app/shared/netquery'; +import { EditProfileDialog } from './../../shared/edit-profile-dialog/edit-profile-dialog'; + +@Component({ + templateUrl: './app-view.html', + styleUrls: ['../page.scss', './app-view.scss'], + animations: [fadeOutAnimation, fadeInAnimation], +}) +export class AppViewComponent implements OnInit, OnDestroy { + private readonly integration = inject(INTEGRATION_SERVICE); + + @ViewChild(SfngNetqueryViewer) + netqueryViewer?: SfngNetqueryViewer; + + destroyRef = inject(DestroyRef); + spn = inject(SPNService); + + canUseHistory = false; + canViewBW = false; + canUseSPN = false; + + /** subscription to our update-process observable */ + private subscription = Subscription.EMPTY; + + /** + * @private + * historyAvailableSince holds the date of the oldes connection + * in the history database for this app. + */ + historyAvailableSince: Date | null = null; + + /** + * @private + * connectionsInHistory holds the total amount of connections + * in the history database for this app + */ + connectionsInHistory = 0; + + /** + * @private + * The current AppProfile we are showing. + */ + appProfile: AppProfile | null = null; + + /** + * @private + * Whether or not the overview componet should be rendered. + */ + get showOverview() { + return this.appProfile == null && !this._loading; + } + + /** + * @private + * The currently displayed list of settings + */ + settings: Setting[] = []; + + profileSettings: Setting[] = []; + + /** + * @private + * All available settings. + */ + allSettings: Setting[] = []; + + /** + * @private + * The current search term displayed in the search-input. + */ + searchTerm = ''; + + /** + * @private + * The key of the setting to highligh, if any ... + */ + highlightSettingKey: string | null = null; + + /** + * @private + * Emits whenever the currently used settings "view" changes. + */ + viewSettingChange = new BehaviorSubject<'all' | 'active'>('all'); + + /** + * @private + * The path of the application binary + */ + applicationDirectory = ''; + + /** + * @private + * The name of the binary + */ + binaryName = ''; + + /** + * @private + * Whether or not the profile warning message should be displayed + */ + displayWarning = false; + + /** + * @private + * The current profile statistics + */ + stats: IProfileStats | null = null; + + /** + * @private + * The internal, layered profile if the app is active + */ + layeredProfile: LayeredProfile | null = null; + + /** Used to track whether we are already initialized */ + private _loading = true; + + /** + * @private + * + * Defines what "view" we are currently in + */ + get viewSetting(): 'all' | 'active' { + return this.viewSettingChange.getValue(); + } + + /** A lookup map from tag ID to tag Name */ + tagNames: { + [tagID: string]: string; + } = {}; + + collapseHeader = false; + + constructor( + public sessionDataService: SessionDataService, + private profileService: AppProfileService, + private route: ActivatedRoute, + private netquery: Netquery, + private cdr: ChangeDetectorRef, + private configService: ConfigService, + private router: Router, + private actionIndicator: ActionIndicatorService, + private dialog: SfngDialogService, + private debugAPI: DebugAPI, + private expertiseService: ExpertiseService, + private portapi: PortapiService + ) { } + + /** + * @private + * Used to save a change in the app settings. Emitted by the config-view + * component + * + * @param event The emitted save-settings-event. + */ + saveSetting(event: SaveSettingEvent) { + // Guard against invalid usage and abort if there's not appProfile + // to save. + if (!this.appProfile) { + return; + } + + if (!this.appProfile!.Config) { + this.appProfile.Config = {} + } + + // If the value has been "reset to global value" we need to + // set the value to "undefined". + if (event.isDefault) { + setAppSetting(this.appProfile!.Config, event.key, undefined); + } else { + setAppSetting(this.appProfile!.Config, event.key, event.value); + } + + // Actually safe the profile + this.profileService.saveProfile(this.appProfile!).subscribe({ + next: () => { + if (!!event.accepted) { + event.accepted(); + } + }, + error: (err) => { + // if there's a callback function for errors call it. + if (!!event.rejected) { + event.rejected(err); + } + + console.error(err); + this.actionIndicator.error('Failed to save setting', err); + }, + }); + } + + exportProfile() { + if (!this.appProfile) { + return; + } + + this.portapi + .exportProfile(`${this.appProfile.Source}/${this.appProfile.ID}`) + .subscribe((exportBlob) => { + const exportConfig: ExportConfig = { + type: 'profile', + content: exportBlob, + }; + + this.dialog.create(ExportDialogComponent, { + data: exportConfig, + autoclose: false, + backdrop: true, + }); + }); + } + + editProfile() { + if (!this.appProfile) { + return; + } + + this.dialog + .create(EditProfileDialog, { + backdrop: true, + autoclose: false, + data: `${this.appProfile.Source}/${this.appProfile.ID}`, + }) + .onAction('deleted', () => { + // navigate to the app overview if it has been deleted. + this.router.navigate(['/app/']); + }); + } + + cleanProfileHistory() { + if (!this.appProfile) { + return; + } + + const observer = this.actionIndicator.httpObserver( + 'History successfully removed', + 'Failed to remove history' + ); + + this.netquery + .cleanProfileHistory(this.appProfile.Source + '/' + this.appProfile.ID) + .subscribe({ + next: (res) => { + observer.next!(res); + this.historyAvailableSince = null; + this.connectionsInHistory = 0; + this.cdr.markForCheck(); + }, + error: (err) => { + observer.error!(err); + }, + }); + } + + ngOnInit() { + this.profileService.tagDescriptions().subscribe((tags) => { + tags.forEach((t) => { + this.tagNames[t.ID] = t.Name; + this.cdr.markForCheck(); + }); + }); + + // watch the route parameters and start watching the referenced + // application profile, it's layer profile and polling the stats. + const profileStream: Observable< + [AppProfile, LayeredProfile | null, IProfileStats | null] | null + > = this.route.paramMap.pipe( + switchMap((params) => { + // Get the profile source and id. If one is unset (null) + // than return a"null" emit-once stream. + const source = params.get('source'); + const id = params.get('id'); + if (source === null || id === null) { + this._loading = false; + return of(null); + } + this._loading = true; + + this.historyAvailableSince = null; + this.connectionsInHistory = 0; + this.appProfile = null; + this.stats = null; + + // Start watching the application profile. + // switchMap will unsubscribe automatically if + // we start watching a different profile. + return this.profileService.getAppProfile(source, id).pipe( + catchError((err) => { + if (typeof err === 'string') { + err = new Error(err); + } + + this.router.navigate(['/app/overview'], { + onSameUrlNavigation: 'reload', + }); + + this.actionIndicator.error( + 'Failed To Get Profile', + this.actionIndicator.getErrorMessgae(err) + ); + + return throwError(() => err); + }), + mergeMap(() => { + return combineLatest([ + this.profileService.watchAppProfile(source, id), + this.profileService + .watchLayeredProfile(source, id) + .pipe(startWith(null)), + interval(10000).pipe( + startWith(-1), + mergeMap(() => + this.netquery + .getProfileStats({ + profile: `${source}/${id}`, + }) + .pipe(map((result) => result?.[0])) + ), + startWith(null) + ), + ]); + }) + ); + }) + ); + + // used to track changes to the object identity of the global configuration + let prevousGlobal: FlatConfigObject = {}; + + this.subscription = combineLatest([ + profileStream, // emits the current app profile everytime it changes + this.route.queryParamMap, // for changes to the settings= query parameter + this.profileService.globalConfig(), // for changes to ghe global profile + this.configService.query(''), // get ALL settings (once, only the defintion is of intereset) + this.viewSettingChange.pipe( + // watch the current "settings-view" setting, but only if it changes + distinctUntilChanged() + ), + ]).subscribe( + async ([profile, queryMap, global, allSettings, viewSetting]) => { + const previousProfile = this.appProfile; + + if (!!profile) { + const key = profile![0].Source + '/' + profile![0].ID; + + const query: Condition = { + profile: key, + }; + + // ignore internal connections if the user is not in developer mode. + if (this.expertiseService.currentLevel !== ExpertiseLevel.Developer) { + query.internal = { + $eq: false, + }; + } + + this.netquery + .query( + { + select: [ + { + $min: { + field: 'started', + as: 'first_connection', + }, + }, + { + $count: { + field: '*', + as: 'totalCount', + }, + }, + ], + groupBy: ['profile'], + query: { + profile: `${profile[0].Source}/${profile[0].ID}`, + }, + databases: [Database.History], + }, + 'app-view-get-first-connection' + ) + .subscribe((result) => { + if (result.length > 0) { + this.historyAvailableSince = new Date( + result[0].first_connection! + ); + this.connectionsInHistory = result[0].totalCount; + } else { + this.historyAvailableSince = null; + this.connectionsInHistory = 0; + } + + this.cdr.markForCheck(); + }); + + this.appProfile = profile[0] || null; + this.layeredProfile = profile[1] || null; + this.stats = profile[2] || null; + } else { + this.appProfile = null; + this.layeredProfile = null; + this.stats = null; + } + + this.displayWarning = false; + + if (this.appProfile?.WarningLastUpdated) { + const now = new Date().getTime(); + const diff = + now - new Date(this.appProfile.WarningLastUpdated).getTime(); + this.displayWarning = diff < 1000 * 60 * 60 * 24 * 7; + } + + if (!!this.netqueryViewer && this._loading) { + this.netqueryViewer.performSearch(); + } + + this._loading = false; + + if (!!this.appProfile?.PresentationPath) { + let parts: string[] = []; + let sep = '/'; + if (this.appProfile.PresentationPath[0] === '/') { + // linux, darwin, bsd ... + sep = '/'; + } else { + // windows ... + sep = '\\'; + } + parts = this.appProfile.PresentationPath.split(sep); + + this.binaryName = parts.pop()!; + this.applicationDirectory = parts.join(sep); + } else { + this.applicationDirectory = ''; + this.binaryName = ''; + } + + this.highlightSettingKey = queryMap.get('setting'); + let profileConfig: FlatConfigObject = {}; + + // if we have a profile flatten it's configuration map to something + // more useful. + if (!!this.appProfile) { + profileConfig = flattenProfileConfig(this.appProfile.Config); + } + + // if we should highlight a setting make sure to switch the + // viewSetting to all if it's the "global" default (that is, no + // value is set). Otherwise the setting won't render and we cannot + // highlight it. + // We need to keep this even though we default to "all" now since + // the following might happen: + // - user already navigated to an app-page and selected "View Active". + // - a notification comes in that has a "show setting" action + // - the user clicks the action button and the setting should be displayed + // - since the requested setting has not been changed it is not available + // in "View Active" so we need to switch back to "View All". Otherwise + // the action button would fail and the user would not notice something + // changing. + // + if (!!this.highlightSettingKey) { + if (profileConfig[this.highlightSettingKey] === undefined) { + this.viewSettingChange.next('all'); + } + } + + // check if we got new values for the profile or the settings. In both cases, we need to update the + // profile settings displayed as there might be new values to show. + const profileChanged = previousProfile !== this.appProfile; + const settingsChanged = allSettings !== this.allSettings; + const globalChanged = global !== prevousGlobal; + + const settingsNeedUpdate = + profileChanged || settingsChanged || globalChanged; + + // save the current global config object so we can compare for identity changes + // the next time we're executed + prevousGlobal = global; + + if (!!this.appProfile && settingsNeedUpdate) { + // filter the settings and remove all settings that are not + // profile specific (i.e. not part of the global config). Also + // update the current settings value (from the app profile) and + // the default value (from the global profile). + this.profileSettings = allSettings.map((setting) => { + setting.Value = profileConfig[setting.Key]; + setting.GlobalDefault = global[setting.Key]; + + return setting; + }); + + this.settings = this.profileSettings.filter((setting) => { + if (!(setting.Key in global)) { + return false; + } + + const isModified = setting.Value !== undefined; + if (this.viewSetting === 'all') { + return true; + } + return isModified; + }); + + this.allSettings = allSettings; + } + + this.cdr.markForCheck(); + } + ); + + this.spn.profile$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ + next: (profile) => { + this.canUseHistory = + profile?.current_plan?.feature_ids?.includes(FeatureID.History) || + false; + this.canViewBW = + profile?.current_plan?.feature_ids?.includes(FeatureID.Bandwidth) || + false; + this.canUseSPN = + profile?.current_plan?.feature_ids?.includes(FeatureID.SPN) || false; + }, + }); + } + + /** + * @private + * Retrieves debug information from the current + * profile and copies it to the clipboard + */ + copyDebugInfo() { + if (!this.appProfile) { + return; + } + + this.debugAPI + .getProfileDebugInfo(this.appProfile.Source, this.appProfile.ID) + .subscribe(async (data) => { + console.log(data); + // Copy to clip-board if supported + await this.integration.writeToClipboard(data); + this.actionIndicator.success('Copied to Clipboard'); + }); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + /** + * @private + * Delete the current profile. Requires a two-step confirmation. + */ + deleteProfile() { + if (!this.appProfile) { + return; + } + + this.dialog + .confirm({ + canCancel: true, + caption: 'Caution', + header: 'Deleting Profile ' + this.appProfile.Name, + message: + 'Do you really want to delete this profile? All settings will be lost.', + buttons: [ + { id: '', text: 'Cancel', class: 'outline' }, + { id: 'delete', class: 'danger', text: 'Yes, delete it' }, + ], + }) + .onAction('delete', () => { + this.profileService.deleteProfile(this.appProfile!).subscribe(() => { + this.router.navigate(['/app/overview']); + this.actionIndicator.success( + 'Profile Deleted', + 'Successfully deleted profile ' + this.appProfile?.Name + ); + }); + }); + } +} diff --git a/desktop/angular/src/app/pages/app-view/index.ts b/desktop/angular/src/app/pages/app-view/index.ts new file mode 100644 index 00000000..54220c42 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/index.ts @@ -0,0 +1,3 @@ +export { AppViewComponent } from './app-view'; +export { AppOverviewComponent } from './overview'; +export { QuickSettingInternetButtonComponent } from './qs-internet'; diff --git a/desktop/angular/src/app/pages/app-view/merge-profile-dialog/merge-profile-dialog.component.html b/desktop/angular/src/app/pages/app-view/merge-profile-dialog/merge-profile-dialog.component.html new file mode 100644 index 00000000..ef6d1829 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/merge-profile-dialog/merge-profile-dialog.component.html @@ -0,0 +1,36 @@ +
+

+ Merge Profiles +

+ + +
+ + + Please select the primary profile. All other selected profiles will be merged into the primary profile by copying metadata, fingerprints and icons into a new profile. + Only the settings of the primary profile will be kept. + + +
+ + + + + + {{ p.Name }} + + + +
+ +
+ + +
+ +
+ + +
diff --git a/desktop/angular/src/app/pages/app-view/merge-profile-dialog/merge-profile-dialog.component.ts b/desktop/angular/src/app/pages/app-view/merge-profile-dialog/merge-profile-dialog.component.ts new file mode 100644 index 00000000..d609afb1 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/merge-profile-dialog/merge-profile-dialog.component.ts @@ -0,0 +1,62 @@ +import { AppProfile } from './../../../../../dist-lib/safing/portmaster-api/lib/app-profile.types.d'; +import { ChangeDetectionStrategy, Component, OnInit, TrackByFunction, inject } from "@angular/core"; +import { Router } from '@angular/router'; +import { PortapiService } from '@safing/portmaster-api'; +import { SFNG_DIALOG_REF, SfngDialogRef } from "@safing/ui"; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; + +@Component({ + templateUrl: './merge-profile-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + :host { + @apply flex flex-col gap-2 justify-start h-96 w-96; + } + ` + ] +}) +export class MergeProfileDialogComponent implements OnInit { + readonly dialogRef: SfngDialogRef = inject(SFNG_DIALOG_REF); + private readonly portapi = inject(PortapiService); + private readonly router = inject(Router); + private readonly uai = inject(ActionIndicatorService); + + get profiles(): AppProfile[] { + return this.dialogRef.data; + } + + primary: AppProfile | null = null; + newName = ''; + + trackProfile: TrackByFunction = (_, p) => `${p.Source}/${p.ID}` + + ngOnInit(): void { + (() => { }); + } + + mergeProfiles() { + if (!this.primary) { + return + } + + this.portapi.mergeProfiles( + this.newName, + `${this.primary.Source}/${this.primary.ID}`, + this.profiles + .filter(p => p !== this.primary) + .map(p => `${p.Source}/${p.ID}`) + ) + .subscribe({ + next: newID => { + this.router.navigate(['/app/' + newID]) + this.uai.success('Profiles Merged Successfully', 'All selected profiles have been merged') + + this.dialogRef.close() + }, + error: err => { + this.uai.error('Failed To Merge Profiles', this.uai.getErrorMessgae(err)) + } + }) + } +} diff --git a/desktop/angular/src/app/pages/app-view/overview.html b/desktop/angular/src/app/pages/app-view/overview.html new file mode 100644 index 00000000..defb9c85 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/overview.html @@ -0,0 +1,193 @@ +
+ + +
+ +
+

+ All Apps + +

+
+ + + Create profile + Import Profile + Merge or Delete profiles + + +
+ +
+ Manage + + + + +
+
+ + + + Merge Profiles + Delete Profiles + Cancel + + + +
+ {{ selectedProfileCount}} selected + + + + +
+
+
+
+
+ +
+ +
+

Active

+
+ +
+ + +
+

Recently Edited

+
+ +
+ + +
+

All

+
+ +
+ + + +
+ + + + + + + + + + +
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ No applications match your search term. +
diff --git a/desktop/angular/src/app/pages/app-view/overview.scss b/desktop/angular/src/app/pages/app-view/overview.scss new file mode 100644 index 00000000..87462ccb --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/overview.scss @@ -0,0 +1,54 @@ +:host { + justify-content: flex-start; +} + +.header-title { + display: flex; + width: 100%; + margin-bottom: 0.5rem; + align-items: center; + height: 3rem; + flex-shrink: 0; + + h1 { + flex-grow: unset; + } + + fa-icon[icon*="question-circle"] { + margin-left: 0.35rem; + } +} + +.scrollable { + width: auto; + flex-grow: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; +} + + +.scrollable-header { + + @apply bg-background; + @apply pt-4; + @apply pb-1; + width: 100%; + position: sticky; + top: 0px; + display: flex; + + grid-column: 1 / -1; + + fa-icon[icon*="question-circle"] { + margin-left: 0.35rem; + } +} + + +.card-header { + // Card headers have top-margin by default. + // Since we're using a grid-gap anyway we need + // to clear the margin. + @apply mt-0; +} diff --git a/desktop/angular/src/app/pages/app-view/overview.ts b/desktop/angular/src/app/pages/app-view/overview.ts new file mode 100644 index 00000000..3c995621 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/overview.ts @@ -0,0 +1,305 @@ +import { + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + TrackByFunction, +} from '@angular/core'; +import { + AppProfile, + AppProfileService, + Netquery, + trackById, +} from '@safing/portmaster-api'; +import { SfngDialogService } from '@safing/ui'; +import { BehaviorSubject, Subscription, combineLatest, forkJoin } from 'rxjs'; +import { debounceTime, filter, startWith } from 'rxjs/operators'; +import { + fadeInAnimation, + fadeInListAnimation, + moveInOutListAnimation, +} from 'src/app/shared/animations'; +import { FuzzySearchService } from 'src/app/shared/fuzzySearch'; +import { EditProfileDialog } from './../../shared/edit-profile-dialog/edit-profile-dialog'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { MergeProfileDialogComponent } from './merge-profile-dialog/merge-profile-dialog.component'; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; +import { Router } from '@angular/router'; +import { + ImportConfig, + ImportDialogComponent, +} from 'src/app/shared/config/import-dialog/import-dialog.component'; + +interface LocalAppProfile extends AppProfile { + hasConfigChanges: boolean; + selected: boolean; +} + +@Component({ + selector: 'app-settings-overview', + templateUrl: './overview.html', + styleUrls: ['../page.scss', './overview.scss'], + animations: [fadeInAnimation, fadeInListAnimation, moveInOutListAnimation], +}) +export class AppOverviewComponent implements OnInit, OnDestroy { + private subscription = Subscription.EMPTY; + + /** Whether or not we are currently loading */ + loading = true; + + /** All application profiles that are actually running */ + runningProfiles: LocalAppProfile[] = []; + + /** All application profiles that have been edited recently */ + recentlyEdited: LocalAppProfile[] = []; + + /** All application profiles */ + profiles: LocalAppProfile[] = []; + + /** The current search term */ + searchTerm: string = ''; + + /** total number of profiles */ + total: number = 0; + + /** Whether or not we are in profile-selection mode */ + set selectMode(v: any) { + this._selectMode = coerceBooleanProperty(v); + + // reset all previous profile selections + if (!this._selectMode) { + this.profiles.forEach((profile) => (profile.selected = false)); + } + } + get selectMode() { + return this._selectMode; + } + private _selectMode = false; + + get selectedProfileCount() { + return this.profiles.reduce( + (sum, profile) => (profile.selected ? sum + 1 : sum), + 0 + ); + } + + /** Observable emitting the search term */ + private onSearch = new BehaviorSubject(''); + + /** TrackBy function for the profiles. */ + trackProfile: TrackByFunction = trackById; + + constructor( + private profileService: AppProfileService, + private changeDetector: ChangeDetectorRef, + private searchService: FuzzySearchService, + private netquery: Netquery, + private dialog: SfngDialogService, + private actionIndicator: ActionIndicatorService, + private router: Router + ) { } + + handleProfileClick(profile: LocalAppProfile, event: MouseEvent) { + if (event.shiftKey) { + // stay on the same page as clicking the app actually triggers + // a navigation before this handler is executed. + this.router.navigate(['/app/overview']); + + this.selectMode = true; + + event.preventDefault(); + event.stopImmediatePropagation(); + event.stopPropagation(); + } + + if (this.selectMode) { + profile.selected = !profile.selected; + } + + if (event.shiftKey && this.selectedProfileCount === 0) { + this.selectMode = false; + } + } + + importProfile() { + const importConfig: ImportConfig = { + type: 'profile', + key: '', + }; + + this.dialog.create(ImportDialogComponent, { + data: importConfig, + autoclose: false, + backdrop: 'light', + }); + } + + openMergeDialog() { + this.dialog.create(MergeProfileDialogComponent, { + autoclose: true, + backdrop: 'light', + data: this.profiles.filter((p) => p.selected), + }); + + this.selectMode = false; + } + + deleteSelectedProfiles() { + this.dialog + .confirm({ + header: 'Confirm Profile Deletion', + message: `Are you sure you want to delete all ${this.selectedProfileCount} selected profiles?`, + caption: 'Attention', + buttons: [ + { + id: 'no', + text: 'Cancel', + class: 'outline', + }, + { + id: 'yes', + text: 'Delete', + class: 'danger', + }, + ], + }) + .onAction('yes', () => { + forkJoin( + this.profiles + .filter((profile) => profile.selected) + .map((p) => this.profileService.deleteProfile(p)) + ).subscribe({ + next: () => { + this.actionIndicator.success( + 'Selected Profiles Delete', + 'All selected profiles have been deleted' + ); + }, + error: (err) => { + this.actionIndicator.error( + 'Failed To Delete Profiles', + `An error occured while deleting some profiles: ${this.actionIndicator.getErrorMessgae( + err + )}` + ); + }, + }); + }) + .onClose.subscribe(() => (this.selectMode = false)); + } + + ngOnInit() { + // watch all profiles and re-emit (debounced) when the user + // enters or chanages the search-text. + this.subscription = combineLatest([ + this.profileService.watchProfiles(), + this.onSearch.pipe(debounceTime(100), startWith('')), + this.netquery.getActiveProfileIDs().pipe(startWith([] as string[])), + ]).subscribe(([profiles, searchTerm, activeProfiles]) => { + this.loading = false; + + // find all profiles that match the search term. For searchTerm="" thsi + // will return all profiles. + const filtered = this.searchService.searchList(profiles, searchTerm, { + ignoreLocation: true, + ignoreFieldNorm: true, + threshold: 0.1, + minMatchCharLength: 3, + keys: ['Name', 'PresentationPath'], + }); + + // create a lookup map of all profiles we already loaded so we don't loose + // selection state when a profile has been updated. + const oldProfiles = new Map( + this.profiles.map((profile) => [ + `${profile.Source}/${profile.ID}`, + profile, + ]) + ); + + // Prepare new, empty lists for our groups + this.profiles = []; + this.runningProfiles = []; + this.recentlyEdited = []; + + // calcualte the threshold for "recently-used" (1 week). + const recentlyUsedThreshold = + new Date().valueOf() / 1000 - 60 * 60 * 24 * 7; + + // flatten the filtered profiles, sort them by name and group them into + // our "app-groups" (active, recentlyUsed, others) + this.total = filtered.length; + filtered + .map((item) => item.item) + .sort((a, b) => { + const aName = a.Name.toLocaleLowerCase(); + const bName = b.Name.toLocaleLowerCase(); + + if (aName > bName) { + return 1; + } + + if (aName < bName) { + return -1; + } + + return 0; + }) + .forEach((profile) => { + const local: LocalAppProfile = { + ...profile, + hasConfigChanges: + profile.LastEdited > 0 && Object.keys(profile.Config || {}).length > 0, + selected: + oldProfiles.get(`${profile.Source}/${profile.ID}`)?.selected || + false, + }; + + if (activeProfiles.includes(profile.Source + '/' + profile.ID)) { + this.runningProfiles.push(local); + } else if (profile.LastEdited >= recentlyUsedThreshold) { + this.recentlyEdited.push(local); + } + + // we always add the profile to "All Apps" + this.profiles.push(local); + }); + + this.changeDetector.markForCheck(); + }); + } + + /** + * @private + * + * Used as an ngModelChange callback on the search-input. + * + * @param term The search term entered by the user + */ + searchApps(term: string) { + this.searchTerm = term; + this.onSearch.next(term); + } + + /** + * @private + * + * Opens the create profile dialog + */ + createProfile() { + const ref = this.dialog.create(EditProfileDialog, { + backdrop: true, + autoclose: false, + }); + + ref.onClose.pipe(filter((action) => action === 'saved')).subscribe(() => { + // reset the search and reload to make sure the new + // profile shows up + this.searchApps(''); + }); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.html b/desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.html new file mode 100644 index 00000000..77c34199 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.html @@ -0,0 +1,12 @@ +
+ + Keep History + + + + Get Plus + + + +
diff --git a/desktop/angular/.gitkeep b/desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.scss similarity index 100% rename from desktop/angular/.gitkeep rename to desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.scss diff --git a/desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.ts b/desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.ts new file mode 100644 index 00000000..24e6296a --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-history/qs-history.component.ts @@ -0,0 +1,67 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + inject, +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + BoolSetting, + FeatureID, + SPNService, + Setting, + getActualValue, +} from '@safing/portmaster-api'; +import { BehaviorSubject, Observable, map } from 'rxjs'; +import { share } from 'rxjs/operators'; +import { SaveSettingEvent } from 'src/app/shared/config'; + +@Component({ + selector: 'app-qs-history', + templateUrl: './qs-history.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class QsHistoryComponent implements OnChanges { + currentValue = false; + historyFeatureAllowed: Observable = inject(SPNService).profile$.pipe( + takeUntilDestroyed(), + map((profile) => { + return ( + profile?.current_plan?.feature_ids?.includes(FeatureID.History) || false + ); + }), + share({ connector: () => new BehaviorSubject(false) }) + ); + + @Input() + canUse: boolean = true; + + @Input() + settings: Setting[] = []; + + @Output() + save = new EventEmitter>(); + + ngOnChanges(changes: SimpleChanges): void { + if ('settings' in changes) { + const historySetting = this.settings.find( + (s) => s.Key === 'history/enable' + ) as BoolSetting | undefined; + if (historySetting) { + this.currentValue = getActualValue(historySetting); + } + } + } + + updateHistoryEnabled(enabled: boolean) { + this.save.next({ + isDefault: false, + key: 'history/enable', + value: enabled, + }); + } +} diff --git a/desktop/angular/src/app/pages/app-view/qs-internet/index.ts b/desktop/angular/src/app/pages/app-view/qs-internet/index.ts new file mode 100644 index 00000000..7fd0d0c4 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-internet/index.ts @@ -0,0 +1 @@ +export * from './qs-internet'; diff --git a/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.html b/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.html new file mode 100644 index 00000000..cf87f254 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.html @@ -0,0 +1,30 @@ +
+ + Block Connections + + + + + + + + Prompting + +
+ + + The following enabled settings may interfere: +
    + +
  • + {{ setting.Name }} +
  • +
    +
+
diff --git a/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.ts b/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.ts new file mode 100644 index 00000000..7b606660 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.ts @@ -0,0 +1,79 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from "@angular/core"; +import { Setting, StringSetting, getActualValue } from "@safing/portmaster-api"; +import { SaveSettingEvent } from "src/app/shared/config/generic-setting/generic-setting"; + +const interferingSettings = { + 'permit': [ + 'filter/blockInternet', + 'filter/blockLAN', + 'filter/blockLocal', + 'filter/blockP2P', + 'filter/blockInbound', + 'filter/endpoints', + ], + 'block': [ + 'filter/endpoints', + ], +} + +@Component({ + selector: 'app-qs-internet', + templateUrl: './qs-internet.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class QuickSettingInternetButtonComponent implements OnChanges { + @Input() + settings: Setting[] = []; + + @Output() + save = new EventEmitter(); + + currentValue = '' + + interferingSettings: Setting[] = []; + + ngOnChanges(changes: SimpleChanges): void { + if ('settings' in changes) { + this.currentValue = ''; + const defaultActionSetting = this.settings.find(s => s.Key == 'filter/defaultAction') as (StringSetting | undefined); + if (!!defaultActionSetting) { + this.currentValue = getActualValue(defaultActionSetting); + this.updateInterfering(); + } + } + } + + updateUseInternet(blocked: boolean) { + const newValue = blocked ? 'block' : 'permit'; + this.save.next({ + isDefault: false, + key: 'filter/defaultAction', + value: newValue, + }) + } + + private updateInterfering() { + this.interferingSettings = []; + if (this.currentValue !== 'permit' && this.currentValue !== 'block') { + return; + } + + // create a lookup map for setting key to setting + const lm = new Map(); + this.settings.forEach(s => lm.set(s.Key, s)) + + this.interferingSettings = interferingSettings[this.currentValue] + .map(key => lm.get(key)) + .filter(setting => { + if (!setting) { + return false; + } + const value = getActualValue(setting); + if (Array.isArray(value)) { + return value.length > 0; + } + + return !!value; + }) as Setting[]; + } +} diff --git a/desktop/angular/src/app/pages/app-view/qs-select-exit/index.ts b/desktop/angular/src/app/pages/app-view/qs-select-exit/index.ts new file mode 100644 index 00000000..56c7267e --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-select-exit/index.ts @@ -0,0 +1 @@ +export * from './qs-select-exit'; diff --git a/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.html b/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.html new file mode 100644 index 00000000..66f9fa13 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.html @@ -0,0 +1,39 @@ +
+ + SPN Exit + + + + Get Pro + + + + + Automatic + + + + + + + + + + {{ option.Name }} + + + + + + + + + + Disabled + + +
diff --git a/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.scss b/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.scss new file mode 100644 index 00000000..e69de29b diff --git a/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.ts b/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.ts new file mode 100644 index 00000000..698607b6 --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-select-exit/qs-select-exit.ts @@ -0,0 +1,128 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, + inject, +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + BoolSetting, + StringArraySetting, + CountrySelectionQuickSetting, + ConfigService, + Setting, + getActualValue, +} from '@safing/portmaster-api'; +import { SaveSettingEvent } from 'src/app/shared/config/generic-setting/generic-setting'; + +@Component({ + selector: 'app-qs-select-exit', + templateUrl: './qs-select-exit.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class QuickSettingSelectExitButtonComponent + implements OnInit, OnChanges +{ + private destroyRef = inject(DestroyRef); + + @Input() + canUse: boolean = true; + + @Input() + settings: Setting[] = []; + + @Output() + save = new EventEmitter(); + + spnEnabled: boolean | null = null; + exitRuleSetting: StringArraySetting | null = null; + + selectedExitRules: string | undefined = undefined; + availableExitRules: CountrySelectionQuickSetting[] | null = null; + + constructor( + private configService: ConfigService, + private cdr: ChangeDetectorRef + ) {} + + updateExitRules(newExitRules: string) { + this.selectedExitRules = newExitRules; + + let newConfigValue: string[] = []; + if (!!newExitRules) { + newConfigValue = newExitRules.split(','); + } + + this.save.next({ + isDefault: false, + key: 'spn/exitHubPolicy', + value: newConfigValue, + }); + } + + ngOnChanges(changes: SimpleChanges): void { + if ('settings' in changes) { + this.exitRuleSetting = null; + this.selectedExitRules = undefined; + + const exitRuleSetting = this.settings.find( + (s) => s.Key == 'spn/exitHubPolicy' + ) as StringArraySetting | undefined; + if (exitRuleSetting) { + this.exitRuleSetting = exitRuleSetting; + this.updateOptions(); + } + } + } + + ngOnInit() { + this.configService + .watch('spn/enable') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((value) => { + this.spnEnabled = value; + this.updateOptions(); + }); + } + + private updateOptions() { + if (!this.exitRuleSetting) { + this.selectedExitRules = undefined; + this.availableExitRules = null; + return; + } + + if (!!this.exitRuleSetting.Value && this.exitRuleSetting.Value.length > 0) { + this.selectedExitRules = this.exitRuleSetting.Value.join(','); + } + this.availableExitRules = this.getQuickSettings(); + + this.cdr.markForCheck(); + } + + private getQuickSettings(): CountrySelectionQuickSetting[] { + if (!this.exitRuleSetting) { + return []; + } + + let val = this.exitRuleSetting.Annotations[ + 'safing/portbase:ui:quick-setting' + ] as CountrySelectionQuickSetting[]; + if (val === undefined) { + return []; + } + + if (!Array.isArray(val)) { + return []; + } + + return val; + } +} diff --git a/desktop/angular/src/app/pages/app-view/qs-use-spn/index.ts b/desktop/angular/src/app/pages/app-view/qs-use-spn/index.ts new file mode 100644 index 00000000..aba9748a --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-use-spn/index.ts @@ -0,0 +1 @@ +export * from './qs-use-spn'; diff --git a/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.html b/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.html new file mode 100644 index 00000000..58ceb09d --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.html @@ -0,0 +1,42 @@ +
+ + Use SPN + + + + Get Pro + + + + + + + Disabled + + + + + + + + +
+ + + The following enabled settings may interfere: +
    + +
  • + {{ setting.Name }} +
  • +
    +
+
diff --git a/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.ts b/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.ts new file mode 100644 index 00000000..f4e5bb2e --- /dev/null +++ b/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.ts @@ -0,0 +1,97 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BoolSetting, ConfigService, Setting, getActualValue } from "@safing/portmaster-api"; +import { SaveSettingEvent } from "src/app/shared/config/generic-setting/generic-setting"; + +const interferingSettingsWhenOn = [ + 'spn/usagePolicy' +] + +@Component({ + selector: 'app-qs-use-spn', + templateUrl: './qs-use-spn.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class QuickSettingUseSPNButtonComponent implements OnInit, OnChanges { + private destroyRef = inject(DestroyRef); + + @Input() + canUse: boolean = true; + + @Input() + settings: Setting[] = []; + + @Output() + save = new EventEmitter(); + + currentValue = false + + interferingSettings: Setting[] = []; + + /* Whether or not the SPN is currently disabled. null means we don't know yet ... */ + spnDisabled: boolean | null = null; + + constructor( + private configService: ConfigService, + private cdr: ChangeDetectorRef + ) { } + + ngOnChanges(changes: SimpleChanges): void { + if ('settings' in changes) { + this.currentValue = false; + + const useSpnSetting = this.settings.find(s => s.Key === 'spn/use') as (BoolSetting | undefined); + if (!!useSpnSetting) { + this.currentValue = getActualValue(useSpnSetting); + this.updateInterfering(); + } + } + } + + updateUseSpn(allowed: boolean) { + this.save.next({ + isDefault: false, + key: 'spn/use', + value: allowed, + }) + } + + ngOnInit() { + this.configService.watch('spn/enable') + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(value => { + this.spnDisabled = !value; + this.cdr.markForCheck(); + this.updateInterfering(); + }) + } + + private updateInterfering() { + this.interferingSettings = []; + + // only "enabled" state has interfering settings + // only show if we already know if the SPN module is enabled + if (!this.currentValue || this.spnDisabled !== false) { + return + } + + // create a lookup map for setting key to setting + const lm = new Map(); + this.settings.forEach(s => lm.set(s.Key, s)) + + + this.interferingSettings = interferingSettingsWhenOn + .map(key => lm.get(key)) + .filter(setting => { + if (!setting) { + return false; + } + const value = getActualValue(setting); + if (Array.isArray(value)) { + return value.length > 0; + } + + return !!value; + }) as Setting[]; + } +} diff --git a/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.html b/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.html new file mode 100644 index 00000000..65fd80cf --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.html @@ -0,0 +1,14 @@ + + + diff --git a/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.ts b/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.ts new file mode 100644 index 00000000..35d2668a --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.ts @@ -0,0 +1,30 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; + +@Component({ + selector: 'app-dashboard-widget', + templateUrl: './dashboard-widget.component.html', + styles: [ + ` + :host { + @apply bg-gray-200 p-4 self-stretch rounded-md flex flex-col gap-2; + } + + label { + @apply text-xs uppercase text-secondary font-light flex flex-row items-center gap-2 pb-2; + } + ` + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DashboardWidgetComponent { + @Input() + set beta(v: any) { + this._beta = coerceBooleanProperty(v) + } + get beta() { return this._beta } + private _beta = false; + + @Input() + label: string = ''; +} diff --git a/desktop/angular/src/app/pages/dashboard/dashboard.component.html b/desktop/angular/src/app/pages/dashboard/dashboard.component.html new file mode 100644 index 00000000..aaf3077c --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/dashboard.component.html @@ -0,0 +1,281 @@ +
+ + + + +
+ + +
+
+ + + + +
+
+ + {{ blockedConnections }} +
+ +
+ + {{ activeConnections }} +
+ +
+ + {{ activeProfiles }} +
+ +
+ + + {{ dataIncoming | bytes }} + + + Available in
Portmaster Plus +
+
+ +
+ + + {{ dataOutgoing | bytes }} + + + Available in
Portmaster Plus +
+
+ +
+ + {{ activeIdentities }} + + Available in
Portmaster Pro +
+
+
+
+ + +
+
+ + +
+
+ + + +
+
+
+ + +
+
    +
  • +
    + + {{ countryNames[country.key] || country.key || 'N/A' }} +
    + {{ country.value }} +
  • +
+
+
+ + +
+ + + + + + + No applications have been blocked in the last 10 minutes. + + + +
    + +
  • +
    + + {{ profile.Name }} +
    + {{ entry.count }} +
  • +
    +
+
+
+ + + + + + + + + + Available in Portmaster Plus + + + + + + + + Available in Portmaster Plus + + + + + +
+ News is only available if intel data updates are enabled + +
+ +
+ Just a second, we're loading the latest news... +
+ + + + +
+ +

+ {{ card.title }} + +

+
+ + + +
+
+
+
+ {{ progress.percent }}% +
+
+
+ + + +
+
+
+ +
+ +
+
+ +
+
diff --git a/desktop/angular/src/app/pages/dashboard/dashboard.component.scss b/desktop/angular/src/app/pages/dashboard/dashboard.component.scss new file mode 100644 index 00000000..ba37e527 --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/dashboard.component.scss @@ -0,0 +1,166 @@ +:host { + @apply flex flex-row w-full gap-3 p-4 overflow-auto; +} + +.dashboard-grid { + @apply grid gap-4; + + align-items: stretch; + justify-items: stretch; + grid-template-columns: 1fr 1fr 1fr 1fr; + grid-template-areas: + "header header header header" + "feature feature feature feature" + "feature feature feature feature" + "stats stats news news" + "stats stats news news" + "charts charts charts charts" + "charts charts charts charts" + "blocked blocked countries countries" + "map map map map" + "bwvis-bar bwvis-bar bwvis-line bwvis-line"; +} + +:host-context(.min-width-1024px) { + .dashboard-grid { + grid-template-areas: + "header header header header" + "feature feature feature news" + "feature feature feature news" + "stats stats stats news" + "stats stats stats news" + "charts charts charts charts" + "countries countries map map" + "blocked blocked map map" + "bwvis-bar bwvis-bar bwvis-line bwvis-line"; + } +} + +#header { + grid-area: header; +} + +#features { + grid-area: feature; +} + +#stats { + grid-area: stats; +} + +#charts { + grid-area: charts; +} + +#countries { + grid-area: countries; +} + +#blocked { + grid-area: blocked; +} + +#connmap { + grid-area: map; +} + +#bwvis-bar { + grid-area: bwvis-bar; +} + +#bwvis-line { + grid-area: bwvis-line; +} + +#news { + grid-area: news; +} + +.auto-grid-3 { + @apply grid gap-4; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); +} + +.auto-grid-4 { + @apply grid gap-4; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); +} + +app-dashboard-widget { + label { + @apply text-xs uppercase text-secondary font-light flex flex-row items-center gap-2 pb-2; + } + + .feature-card-list { + @apply grid gap-3 w-full; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + } + + .mini-stats-list { + @apply grid gap-3 w-full; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } + + &#news { + + h1 { + @apply text-base; + @apply font-light; + } + + ::ng-deep markdown { + @apply font-light; + + a { + @apply underline text-blue; + } + + strong { + @apply font-medium; + } + } + } + +} + +::ng-deep #dashboard-map { + #world-group { + --map-bg: #111112; + --map-country-active: #424141; + --map-country-inactive: #2a2a2a; + --map-country-border-width: 1px; + --map-country-border-color: #1e1e1e; + --map-country-border-color-selected: #858585; + --map-country-blocked-primary: #858585; + --map-country-blocked-secondary: #402323; + + path { + fill: var(--map-country-active); + stroke: var(--map-bg); + stroke-width: var(--map-country-border-width); + stroke-linejoin: round; + } + + path.active { + color: #1d3c24; + fill: currentColor; + } + + path.hover { + color: #4fae4f; + fill: currentColor; + } + } +} + +.mini-stat { + @apply flex flex-col items-center justify-center py-3 px-2 bg-gray-300 rounded shadow; + + label { + @apply font-light uppercase text-xxs text-secondary -mb-2; + } + + span { + @apply text-xl text-blue; + } +} diff --git a/desktop/angular/src/app/pages/dashboard/dashboard.component.ts b/desktop/angular/src/app/pages/dashboard/dashboard.component.ts new file mode 100644 index 00000000..a07893aa --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/dashboard.component.ts @@ -0,0 +1,481 @@ +import { KeyValue } from "@angular/common"; +import { AfterContentInit, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, OnInit, QueryList, TrackByFunction, ViewChild, ViewChildren, forwardRef, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { AppProfileService, BandwidthChartResult, ChartResult, Database, FeatureID, Netquery, PortapiService, SPNService, UserProfile, Verdict } from "@safing/portmaster-api"; +import { SfngDialogService, SfngTabGroupComponent } from "@safing/ui"; +import { Observable, catchError, filter, interval, map, repeat, retry, startWith, throwError } from "rxjs"; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; +import { DefaultBandwidthChartConfig, SfngNetqueryLineChartComponent } from "src/app/shared/netquery/line-chart/line-chart"; +import { SPNAccountDetailsComponent } from "src/app/shared/spn-account-details"; +import { MAP_HANDLER, MapRef } from "../spn/map-renderer"; +import { CircularBarChartConfig, splitQueryResult } from "src/app/shared/netquery/circular-bar-chart/circular-bar-chart.component"; +import { BytesPipe } from "src/app/shared/pipes/bytes.pipe"; +import { HttpErrorResponse } from "@angular/common/http"; + +interface BlockedProfile { + profileID: string; + count: number; +} + +interface BandwidthBarData { + profile: string; + profile_name: string; + series: 'sent' | 'received'; + value: number; + sent: number; + received: number; +} + +interface NewsCard { + title: string; + body: string; + url?: string; + footer?: string; + progress?: { + percent: number; + style: string; + } +} + +interface News { + cards: NewsCard[]; +} + +const newsResourceIdentifier = "all/intel/portmaster/news.yaml" + +@Component({ + selector: 'app-dashboard', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['./dashboard.component.scss'], + templateUrl: './dashboard.component.html', + providers: [ + { provide: MAP_HANDLER, useExisting: forwardRef(() => DashboardPageComponent), multi: true }, + ] +}) +export class DashboardPageComponent implements OnInit, AfterViewInit { + @ViewChildren(SfngNetqueryLineChartComponent) + lineCharts!: QueryList; + + @ViewChild(SfngTabGroupComponent) + carouselTabGroup?: SfngTabGroupComponent; + + private readonly destroyRef = inject(DestroyRef); + private readonly netquery = inject(Netquery); + private readonly spn = inject(SPNService); + private readonly actionIndicator = inject(ActionIndicatorService); + private readonly cdr = inject(ChangeDetectorRef); + private readonly dialog = inject(SfngDialogService); + private readonly portapi = inject(PortapiService) + + resizeObserver!: ResizeObserver; + + blockedProfiles: BlockedProfile[] = [] + + connectionsPerCountry: { + [country: string]: number + } = {}; + + get countryNames(): { [country: string]: string } { + return this.mapRef?.countryNames || {}; + } + + bandwidthLineChart: BandwidthChartResult[] = []; + + bandwidthBarData: BandwidthBarData[] = []; + + readonly bandwidthBarConfig: CircularBarChartConfig = { + stack: 'profile_name', + seriesKey: 'series', + seriesLabel: d => { + if (d === 'sent') { + return 'Bytes Sent' + } + return 'Bytes Received' + }, + value: 'value', + ticks: 3, + colorAsClass: true, + series: { + 'sent': { + color: 'text-deepPurple-500 text-opacity-50', + }, + 'received': { + color: 'text-cyan-800 text-opacity-50', + } + }, + formatTick: (tick: number) => { + return new BytesPipe().transform(tick, '1.0-0') + }, + formatValue: (stack, series, value, data) => { + const bytes = new BytesPipe().transform + return `${stack}\nSent: ${bytes(data?.sent)}\nReceived: ${bytes(data?.received)}` + }, + formatStack: (sel, data) => { + const bytes = new BytesPipe().transform + + return sel + .call(sel => { + sel.append("text") + .attr("dy", "0") + .attr("y", "0") + .text(d => d) + }) + .call(sel => { + sel.append("text") + .attr("y", 0) + .attr("dy", "0.8rem") + .style("font-size", "0.6rem") + .text(d => { + const first = data.find(result => result.profile_name === d); + return `${bytes(first?.sent)} / ${bytes(first?.received)}` + }) + }) + } + } + + bwChartConfig = DefaultBandwidthChartConfig; + + activeConnections: number = 0; + blockedConnections: number = 0; + activeProfiles: number = 0; + activeIdentities = 0; + dataIncoming = 0; + dataOutgoing = 0; + connectionChart: ChartResult[] = []; + tunneldConnectionChart: ChartResult[] = []; + + countriesPerProfile: { [profile: string]: string[] } = {} + + profile: UserProfile | null = null; + + featureBw = false; + featureSPN = false; + + hoveredCard: NewsCard | null = null; + + features$ = this.spn.watchEnabledFeatures() + .pipe(takeUntilDestroyed()); + + trackCountry: TrackByFunction> = (_, ctr) => ctr.key; + trackApp: TrackByFunction = (_, bp) => bp.profileID; + + data: any; + + news?: News | 'pending' = 'pending'; + + private mapRef: MapRef | null = null; + + registerMap(ref: MapRef): void { + this.mapRef = ref; + + this.mapRef.onMapReady(() => { + this.updateMapCountries(); + }) + } + + private updateMapCountries() { + // this check is basically to make typescript happy ... + if (!this.mapRef) { + return; + } + + this.mapRef.worldGroup + .selectAll('path') + .classed('active', (d: any) => { + return !!this.connectionsPerCountry[d.properties.iso_a2]; + }); + } + + unregisterMap(ref: MapRef): void { + this.mapRef = null; + } + + onCarouselTabHover(card: NewsCard | null) { + this.hoveredCard = card; + } + + openAccountDetails() { + this.dialog.create(SPNAccountDetailsComponent, { + autoclose: true, + backdrop: 'light' + }) + } + + onCountryHover(code: string | null) { + if (!this.mapRef) { + return + } + + this.mapRef.worldGroup + .selectAll('path') + .classed('hover', (d: any) => { + return (d.properties.iso_a2 === code); + }); + } + + onProfileHover(profile: string | null) { + if (!this.mapRef) { + return + } + + this.mapRef.worldGroup + .selectAll('path') + .classed('hover', (d: any) => { + if (!profile) { + return false; + } + + return this.countriesPerProfile[profile]?.includes(d.properties.iso_a2); + }); + } + + ngAfterViewInit(): void { + interval(15000) + .pipe( + takeUntilDestroyed(this.destroyRef), + startWith(-1), + filter(() => this.hoveredCard === null) + ) + .subscribe(() => { + if (!this.carouselTabGroup) { + return + } + + let next = this.carouselTabGroup.activeTabIndex + 1 + if (next >= this.carouselTabGroup.tabs!.length) { + next = 0 + } + + this.carouselTabGroup.activateTab(next, "left") + }) + } + + async ngOnInit() { + this.portapi.getResource(newsResourceIdentifier) + .pipe( + repeat({ delay: 60000 }), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe({ + next: response => { + this.news = response; + this.cdr.markForCheck(); + }, + error: () => { + this.news = undefined; + this.cdr.markForCheck(); + } + }); + + this.netquery + .batch({ + bwBarChart: { + query: { + internal: { $eq: false }, + }, + select: [ + 'profile', + 'profile_name', + { + $sum: { + field: 'bytes_sent', + as: 'sent' + } + }, + { + $sum: { + field: 'bytes_received', + as: 'received' + } + }, + ], + groupBy: ['profile', 'profile_name'], + }, + + profileCount: { + select: [ + 'profile', + { + $count: { + field: '*', + as: 'totalCount' + } + } + ], + query: { + verdict: { $in: [Verdict.Block, Verdict.Drop] } + }, + groupBy: ['profile'], + databases: [Database.Live] + }, + + countryStats: { + select: [ + 'country', + { $count: { field: '*', as: 'totalCount' } }, + { $sum: { field: 'bytes_sent', as: 'bwout' } }, + { $sum: { field: 'bytes_received', as: 'bwin' } }, + ], + query: { + allowed: { $eq: true }, + }, + groupBy: ['country'], + databases: [Database.Live] + }, + + perCountryConns: { + select: ['profile', 'country', 'active', { $count: { field: '*', as: 'totalCount' } }], + query: { + allowed: { $eq: true }, + }, + groupBy: ['profile', 'country', 'active'], + databases: [Database.Live], + }, + + exitNodes: { + query: { tunneled: { $eq: true }, exit_node: { $ne: "" } }, + groupBy: ['exit_node'], + select: [ + 'exit_node', + { $count: { field: '*', as: 'totalCount' } } + ], + databases: [Database.Live], + } + }) + .pipe( + repeat({ delay: 10000 }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(response => { + // bandwidth bar chart + const barChartData = response.bwBarChart + .filter(value => (value.sent + value.received) > 0) + .sort((a, b) => (b.sent + b.received) - (a.sent + a.received)) + .slice(0, 10); + this.bandwidthBarData = splitQueryResult(barChartData, ['sent', 'received']) as BandwidthBarData[] + + // profileCount + this.blockedConnections = 0; + this.blockedProfiles = []; + + response.profileCount?.forEach(row => { + this.blockedConnections += row.totalCount; + this.blockedProfiles.push({ + profileID: row.profile!, + count: row.totalCount + }) + }); + + // countryStats + this.connectionsPerCountry = {}; + this.dataIncoming = 0; + this.dataOutgoing = 0; + + response.countryStats?.forEach(row => { + this.dataIncoming += row.bwin; + this.dataOutgoing += row.bwout; + + if (row.country === '') { + return + } + + this.connectionsPerCountry[row.country!] = row.totalCount || 0; + }) + + this.updateMapCountries() + + // perCountryConns + let profiles = new Set(); + + this.activeConnections = 0; + this.countriesPerProfile = {}; + + response.perCountryConns?.forEach(row => { + profiles.add(row.profile!); + + if (row.active) { + this.activeConnections += row.totalCount; + } + + const arr = (this.countriesPerProfile[row.profile!] || []); + arr.push(row.country!) + this.countriesPerProfile[row.profile!] = arr; + }); + + this.activeProfiles = profiles.size; + + // exitNodes + this.activeIdentities = response.exitNodes?.length || 0; + this.cdr.markForCheck(); + }) + + + // Charts + + this.netquery + .activeConnectionChart({}) + .pipe( + repeat({ delay: 10000 }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(result => { + this.connectionChart = result; + this.cdr.markForCheck(); + }) + + this.netquery + .bandwidthChart({}, undefined, 60) + .pipe( + repeat({ delay: 10000 }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(bw => { + this.bandwidthLineChart = bw; + this.cdr.markForCheck(); + }) + + this.netquery + .activeConnectionChart({ tunneled: { $eq: true } }) + .pipe( + repeat({ delay: 10000 }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(result => { + this.tunneldConnectionChart = result; + this.cdr.markForCheck(); + }) + + // SPN profile and enabled/allowed features + + this.spn + .profile$ + .pipe( + takeUntilDestroyed(this.destroyRef) + ) + .subscribe({ + next: (profile) => { + this.profile = profile || null; + this.featureBw = profile?.current_plan?.feature_ids?.includes(FeatureID.Bandwidth) || false; + this.featureSPN = profile?.current_plan?.feature_ids?.includes(FeatureID.SPN) || false; + + // force a full change-detection cylce now! + this.cdr.detectChanges() + + // force re-draw of the charts after change-detection because the + // width may change now. + this.lineCharts?.forEach(chart => chart.redraw()) + + this.cdr.markForCheck(); + }, + }) + } + + /** Logs the user out of the SPN completely by purgin the user profile from the local storage */ + logoutCompletely(_: Event) { + this.spn.logout(true) + .subscribe(this.actionIndicator.httpObserver( + 'Logout', + 'You have been logged out of the SPN completely.' + )) + } +} diff --git a/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.html b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.html new file mode 100644 index 00000000..0695555e --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.html @@ -0,0 +1,61 @@ +
+ + + + + + + + + + + +
+ + + + {{ feature?.Name }} + + +
+
+ +
+ + + + BETA +
+
+
+
+ + {{ (disabled ? 'Available in ' : '') + 'Portmaster ' + feature?.InPackage?.Name}} + {{ comingSoon ? ' - coming soon' : '' }} + {{ feature?.Comment }} + +
+ +
+ +
+
+ + Active + +
+ +
+ {{ feature?.InPackage?.Name }} + +
+
diff --git a/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.scss b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.scss new file mode 100644 index 00000000..88f5fc61 --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.scss @@ -0,0 +1,60 @@ +.feature-card { + @apply flex flex-col p-4 bg-gray-300 rounded shadow w-full relative gap-2 overflow-hidden; + + .disabled-bg { + @apply absolute top-0 left-0 text-gray-500 opacity-50; + } + + &.disabled { + @apply opacity-80 shadow-inner; + } + + &.clickable { + @apply cursor-pointer; + &:hover { + @apply bg-gray-400; + } + } + + header { + @apply flex flex-row items-center justify-start gap-2 w-full; + + img { + @apply w-5 h-5; + filter: invert(1); + } + + &>span { + @apply text-base font-light; + } + } +} + +.ribbon { + width: 90px; + height: 100%; + overflow: hidden; + position: absolute; + top: 0px; + right: 0px; + z-index: 100; +} + +.ribbon__content { + left: -7px; + top: 25px; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + position: absolute; + display: block; + width: 125px; + padding: 2px 0; + text-shadow: 0 1px 0px rgba(0, 0, 0, .2); + text-transform: uppercase; + text-align: center; + border: 2px dotted #fff; + outline-color: #fff; + outline-width: 1px; + outline-style: solid; +} diff --git a/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.ts b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.ts new file mode 100644 index 00000000..8355a3ba --- /dev/null +++ b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.ts @@ -0,0 +1,128 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, SimpleChanges, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { BoolSetting, ConfigService, Feature } from '@safing/portmaster-api'; +import { Subscription } from 'rxjs'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; + +@Component({ + selector: 'app-feature-card', + templateUrl: './feature-card.component.html', + styleUrls: ['./feature-card.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FeatureCardComponent implements OnChanges, OnDestroy { + private readonly cdr = inject(ChangeDetectorRef); + private readonly configService = inject(ConfigService); + private readonly router = inject(Router); + private readonly integration = inject(INTEGRATION_SERVICE); + + private configValueSubscription = Subscription.EMPTY; + + @Input() + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v) + } + get disabled() { return this._disabled } + _disabled = false; + + get comingSoon() { return this.feature?.ComingSoon || false } + + @Input() + feature?: Feature; + + planColor: string | null = null; + + configValue: boolean | undefined = undefined; + + ngOnChanges(changes: SimpleChanges): void { + if ('feature' in changes) { + this.configValueSubscription.unsubscribe(); + this.configValueSubscription = Subscription.EMPTY; + + if (!!this.feature?.ConfigKey) { + this.configValueSubscription = + this.configService.watch(this.feature!.ConfigKey) + .subscribe(value => { + this.configValue = value; + this.cdr.markForCheck(); + }); + } + + if (this.feature?.InPackage?.HexColor) { + this.planColor = getContrastFontColor(this.feature.InPackage.HexColor); + // console.log(this.feature.InPackage.HexColor, this.planColor) + this.cdr.markForCheck(); + } + } + } + + ngOnDestroy() { + this.configValueSubscription.unsubscribe(); + } + + updateSettingsValue(newValue: boolean) { + this.configService.save(this.feature!.ConfigKey, newValue) + .subscribe() + } + + navigateToConfigScope() { + if (this.disabled) { + this.integration.openExternal("https://safing.io/pricing?source=Portmaster") + return; + } + + let key: string | undefined; + if (this.feature?.ConfigScope) { + key = 'config:' + this.feature?.ConfigScope; + } else { + key = this.feature?.ConfigKey; + } + + if (!key) { + return + } + + + this.router.navigate(['/settings'], { + queryParams: { + setting: key, + } + }) + } +} + +function parseColor(input: string): number[] { + if (input.substr(0, 1) === '#') { + const collen = (input.length - 1) / 3; + const fact = [17, 1, 0.062272][collen - 1]; + return [ + Math.round(parseInt(input.substr(1, collen), 16) * fact), + Math.round(parseInt(input.substr(1 + collen, collen), 16) * fact), + Math.round(parseInt(input.substr(1 + 2 * collen, collen), 16) * fact), + ]; + } + + return input + .split('(')[1] + .split(')')[0] + .split(',') + .map((x) => +x); +} + +function getContrastFontColor(bgColor: string): string { + // if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff + // based on https://stackoverflow.com/a/3943023 + + let col = bgColor; + if (bgColor.startsWith('#') && bgColor.length > 7) { + col = bgColor.slice(0, 7); + } + const [r, g, b] = parseColor(col); + + if (r * 0.299 + g * 0.587 + b * 0.114 > 186) { + return '#000000'; + } + + return '#ffffff'; +} diff --git a/desktop/angular/src/app/pages/monitor/index.ts b/desktop/angular/src/app/pages/monitor/index.ts new file mode 100644 index 00000000..c21908c8 --- /dev/null +++ b/desktop/angular/src/app/pages/monitor/index.ts @@ -0,0 +1 @@ +export { MonitorPageComponent } from './monitor'; diff --git a/desktop/angular/src/app/pages/monitor/monitor.html b/desktop/angular/src/app/pages/monitor/monitor.html new file mode 100644 index 00000000..1d6fb177 --- /dev/null +++ b/desktop/angular/src/app/pages/monitor/monitor.html @@ -0,0 +1,46 @@ +
+ + +
+
+ +
+

+ Network Activity + +

+ + + + + + Network history data available as of {{ data.first | date }}. ({{ data.count }} connections) + + Clear + + + + + No network history data available. + + Enable + + + Available in Portmaster Plus + + + + + + + Use the search bar and drop downs to search and filter the last 10 minutes of network traffic. + Optionally, search all network history data if enabled. + +
+ + + +
diff --git a/desktop/angular/src/app/pages/monitor/monitor.scss b/desktop/angular/src/app/pages/monitor/monitor.scss new file mode 100644 index 00000000..12345b56 --- /dev/null +++ b/desktop/angular/src/app/pages/monitor/monitor.scss @@ -0,0 +1,49 @@ +:host { + overflow: hidden; + flex-direction: row; + flex-grow: 1; + width: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + padding-left: 1.7rem; + padding-right: 0.8rem; + + .header, + .content { + padding: 0; + margin: 0; + } + + .header { + padding-top: 0.9rem; + + .breadcrumbs { + font-size: 0.715rem; + font-weight: 500; + color: #cacaca; + user-select: none; + display: flex; + + span:first-child { + opacity: .55; + font-weight: 400; + margin-right: 4px; + + &:hover { + opacity: 1; + } + } + + svg.arrow { + width: 1rem; + padding: 0; + margin: 0; + + .inner { + stroke: white; + } + } + } + } +} diff --git a/desktop/angular/src/app/pages/monitor/monitor.ts b/desktop/angular/src/app/pages/monitor/monitor.ts new file mode 100644 index 00000000..d6ce3374 --- /dev/null +++ b/desktop/angular/src/app/pages/monitor/monitor.ts @@ -0,0 +1,77 @@ +import { Component, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { BoolSetting, ConfigService, Database, FeatureID, Netquery, SPNService } from '@safing/portmaster-api'; +import { Subject, interval, map, merge, repeat } from 'rxjs'; +import { SessionDataService } from 'src/app/services'; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; +import { fadeInAnimation, moveInOutListAnimation } from 'src/app/shared/animations'; + +@Component({ + templateUrl: './monitor.html', + styleUrls: ['../page.scss', './monitor.scss'], + providers: [], + animations: [fadeInAnimation, moveInOutListAnimation], +}) +export class MonitorPageComponent { + session = inject(SessionDataService); + netquery = inject(Netquery); + reload = new Subject(); + + configService = inject(ConfigService); + uai = inject(ActionIndicatorService); + + historyEnabled = inject(ConfigService) + .watch('history/enable'); + + canUseHistory = inject(SPNService).profile$ + .pipe( + map(profile => { + return profile?.current_plan?.feature_ids?.includes(FeatureID.History) || false; + }) + ); + + history = inject(Netquery) + .query({ + select: [ + { + $min: { + field: "started", + as: "first_connection", + }, + }, + { + $count: { + field: "*", + as: "totalCount" + } + } + ], + databases: [Database.History] + }, 'monitor-get-first-history-connection') + .pipe( + repeat({ delay: () => merge(interval(10000), this.reload) }), + map(result => { + if (!result.length || result[0].totalCount === 0) { + return null + } + + return { + first: new Date(result[0].first_connection), + count: result[0].totalCount, + } + }), + takeUntilDestroyed() + ); + + enableHistory() { + this.configService.save('history/enable', true) + .subscribe(); + } + + clearHistoryData() { + this.netquery.cleanProfileHistory([]) + .subscribe(() => { + this.reload.next(); + }) + } +} diff --git a/desktop/angular/src/app/pages/page.scss b/desktop/angular/src/app/pages/page.scss new file mode 100644 index 00000000..1977b027 --- /dev/null +++ b/desktop/angular/src/app/pages/page.scss @@ -0,0 +1,6 @@ +:host { + display : flex; + flex-direction: column; + width : 100%; + height : 100%; +} diff --git a/desktop/angular/src/app/pages/settings/settings.html b/desktop/angular/src/app/pages/settings/settings.html new file mode 100644 index 00000000..f85c752b --- /dev/null +++ b/desktop/angular/src/app/pages/settings/settings.html @@ -0,0 +1,26 @@ + + +
+

+ Global Settings + +

+
+ + + diff --git a/desktop/angular/src/app/pages/settings/settings.scss b/desktop/angular/src/app/pages/settings/settings.scss new file mode 100644 index 00000000..bc178ab8 --- /dev/null +++ b/desktop/angular/src/app/pages/settings/settings.scss @@ -0,0 +1,83 @@ +.header-title { + display: flex; + width: 100%; + padding-left: 3rem; + padding-right: 1.25rem; + margin-bottom: 0.5rem; + align-items: center; + height: 3rem; + flex-shrink: 0; + + h1{ + flex-grow: unset; + } + + fa-icon[icon*="question-circle"]{ + margin-left: 0.35rem; + } +} + +.card-title.meta { + div { + display: inline-block; + @apply mr-2; + } +} + +.columns { + width : 100%; + display : flex; + flex-direction: row; +} + +.meta { + + span:first-of-type { + @apply text-secondary; + @apply mr-1; + } +} + +.col { + flex-grow: 1; +} + +.unstable { + @apply text-xs; + @apply uppercase; + color: theme('colors.info.yellow'); +} + +sfng-accordion-group { + @apply pl-12; + @apply pr-4; // align with the scroll bar on the right side + @apply my-4; +} + +div.tableFixHead { + @apply mt-4; + @apply rounded-t; + + &:not(.empty) { + @apply rounded; + } + + max-height: 16rem; +} + +.cdk-row.unused { + opacity: 0.4; +} + +.card-actions { + display : flex; + align-items: center; + + * { + @apply ml-2; + } + + app-menu-trigger { + display: inline-block; + } +} diff --git a/desktop/angular/src/app/pages/settings/settings.ts b/desktop/angular/src/app/pages/settings/settings.ts new file mode 100644 index 00000000..3f36f9bd --- /dev/null +++ b/desktop/angular/src/app/pages/settings/settings.ts @@ -0,0 +1,133 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ConfigService, Setting } from '@safing/portmaster-api'; +import { Subscription } from 'rxjs'; +import { StatusService, VersionStatus } from 'src/app/services'; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; +import { fadeInAnimation } from 'src/app/shared/animations'; +import { SaveSettingEvent } from 'src/app/shared/config/generic-setting/generic-setting'; + +@Component({ + templateUrl: './settings.html', + styleUrls: [ + '../page.scss', + './settings.scss' + ], + animations: [fadeInAnimation] +}) +export class SettingsComponent implements OnInit, OnDestroy { + /** @private The current search term for the settings. */ + searchTerm: string = ''; + + /** @private All settings currently displayed. */ + settings: Setting[] = []; + + /** @private The available and selected resource versions. */ + versions: VersionStatus | null = null; + + /** + * @private + * The key of the setting to highligh, if any ... + */ + highlightSettingKey: string | null = null; + + /** Subscription to watch all available settings. */ + private subscription = Subscription.EMPTY; + + constructor( + public configService: ConfigService, + public statusService: StatusService, + private actionIndicator: ActionIndicatorService, + private route: ActivatedRoute, + ) { } + + ngOnInit(): void { + this.subscription = new Subscription(); + + this.loadSettings(); + + // Request the current resource versions once. We add + // it to the subscription to prevent a memory leak in + // case the user leaves the page before the versions + // have been loaded. + const versionSub = this.statusService.getVersions() + .subscribe(version => this.versions = version); + + this.subscription.add(versionSub); + + const querySub = this.route.queryParamMap + .subscribe( + params => { + this.highlightSettingKey = params.get('setting'); + } + ) + this.subscription.add(querySub); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + /** + * Loads all settings from the portmaster. + */ + private loadSettings() { + const configSub = this.configService.query('') + .subscribe(settings => this.settings = settings); + this.subscription.add(configSub); + } + + /** + * @private + * SaveSettingEvent is emitted by the settings-view + * component when a value has been changed and should be saved. + * + * @param event The save-settings event + */ + saveSetting(event: SaveSettingEvent) { + let idx = this.settings.findIndex(setting => setting.Key === event.key); + if (idx < 0) { + return; + } + + const setting = { + ...this.settings[idx], + } + + if (event.isDefault) { + delete (setting['Value']); + } else { + setting.Value = event.value; + } + + this.configService.save(setting) + .subscribe({ + next: () => { + if (!!event.accepted) { + event.accepted(); + } + + this.settings[idx] = setting; + + // copy the settings into a new array so we trigger + // an input update due to changed array identity. + this.settings = [...this.settings]; + + // for the release level setting we need to + // to a page-reload since portmaster will now + // return more settings. + if (setting.Key === 'core/releaseLevel') { + this.loadSettings(); + } + }, + error: err => { + if (!!event.rejected) { + event.rejected(err); + } + + this.actionIndicator.error('Failed to save setting', err); + console.error(err); + } + }) + } +} diff --git a/desktop/angular/src/app/pages/spn/country-details/country-details.html b/desktop/angular/src/app/pages/spn/country-details/country-details.html new file mode 100644 index 00000000..f4e43963 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/country-details/country-details.html @@ -0,0 +1,154 @@ +

+ + {{ countryName }} + + + + + +

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Total Nodes + {{ totalAliveCount }}
+ + + by Safing + + {{ safingNodeCount }}
+ + + by Community + + {{ communityNodeCount }}
+ Exit Nodes + {{ exitNodeCount }}
+ + + by Safing + + {{ safingExitNodeCount }}
+ + + by Community + + {{ communityExitNodeCount }}
+ Nodes In Use + {{ activeNodeCount }}
+ + + by Safing + + {{ activeSafingNodeCount }}
+ + + by Community + + {{ activeCommunityNodeCount }}
+
+
+ + + + +
+ The following Apps have connections that are routed through the + SPN and use an + exit node in {{ countryName }} ({{ countryCode }}): + + + + + + + + +
+ + {{ app.profile.Name }} + + {{ app.count }} connections + +
+
+ + + + + + +
+ +
+ + + + +
+
+
+
+
+ +
diff --git a/desktop/angular/src/app/pages/spn/country-details/country-details.ts b/desktop/angular/src/app/pages/spn/country-details/country-details.ts new file mode 100644 index 00000000..3fa34f1c --- /dev/null +++ b/desktop/angular/src/app/pages/spn/country-details/country-details.ts @@ -0,0 +1,217 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Optional, Output, SimpleChanges, TrackByFunction } from "@angular/core"; +import { AppProfile, AppProfileService, Netquery } from '@safing/portmaster-api'; +import { SFNG_DIALOG_REF, SfngDialogRef, SfngDialogService } from "@safing/ui"; +import { Subscription, forkJoin, of, switchMap } from 'rxjs'; +import { repeat } from 'rxjs/operators'; +import { MapPin, MapService } from './../map.service'; +import { PinDetailsComponent } from './../pin-details/pin-details'; + +@Component({ + templateUrl: './country-details.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + `:host{ + display: block; + min-width: 630px; + height: 400px; + overflow: hidden; + }` + ] +}) +export class CountryDetailsComponent implements OnInit, OnChanges, OnDestroy { + /** Subscription to poll map pins and profiles. */ + private subscription = Subscription.EMPTY; + + /** The two letter ISO country code */ + @Input() + countryCode: string = ''; + + /** The name of the country */ + @Input() + countryName: string = ''; + + /** Emits the ID of the pin that is hovered in the list. null if no pin is hovered */ + @Output() + pinHover = new EventEmitter(); + + /** @private - The list of pins available in this country */ + pins: MapPin[] = []; + + /** @private - A list of app profiles that use this country as an exit node */ + profiles: { profile: AppProfile, count: number }[] = []; + + /** @private - A {@link TrackByFunction} for all profiles that use this country for exit */ + trackProfile: TrackByFunction = (_: number, profile: this['profiles'][0]) => `${profile.profile.Source}/${profile.profile.ID}`; + + /** The number of alive nodes in this country */ + totalAliveCount = 0; + + /** The number of exit nodes in this country */ + exitNodeCount = 0; + + /** The number of active (used) nodes in this country */ + activeNodeCount = 0; + + /** The number of active (used) nodes operated by safing */ + activeSafingNodeCount = 0; + + /** The number of active (used) nodes operated by the community */ + activeCommunityNodeCount = 0; + + /** The number of nodes operated by safing */ + safingNodeCount = 0; + + /** The number of exit nodes operated by safing */ + safingExitNodeCount = 0; + + /** The number of nodes operated by a community member */ + communityNodeCount = 0; + + /** The number of exit ndoes operated by the community */ + communityExitNodeCount = 0; + + /** holds the text format of a netquery search to show all connections that exit in this country */ + filterConnectionsByCountryNodes = ''; + + constructor( + private mapService: MapService, + private netquery: Netquery, + private appService: AppProfileService, + private cdr: ChangeDetectorRef, + private dialog: SfngDialogService, + @Inject(SFNG_DIALOG_REF) @Optional() public dialogRef?: SfngDialogRef, + ) { } + + openPinDetails(id: string) { + this.dialog.create(PinDetailsComponent, { + data: id, + backdrop: false, + autoclose: true, + dragable: true, + }) + } + + ngOnInit() { + // if we got opened as a dialog we get the code and name of the country + // from the dialogRef.data field. + if (!!this.dialogRef) { + this.countryCode = this.dialogRef.data.code; + this.countryName = this.dialogRef.data.name; + } + + this.subscription.unsubscribe(); + + this.subscription = + this.mapService + .pins$ + .pipe( + switchMap(pins => { + // get a list of pins in that country + const countryPins = pins.filter(pin => pin.entity.Country === this.countryCode); + + // prepare a netquery query that loads the IDs of all profiles that use one of the countries + // pins as an exit node. Then, map those IDs to the actual app profile object + const profiles = this.netquery + .query({ + select: [ + 'profile', + { $count: { field: '*', as: 'totalCount' } } + ], + groupBy: ['profile'], + query: { + 'exit_node': { + $in: countryPins.map(pin => pin.pin.ID), + } + } + }, 'get-connections-per-profile-in-country') + .pipe( + switchMap(queryResult => { + if (queryResult.length === 0) { + return of([]); + } + + return forkJoin( + queryResult.map(row => forkJoin({ + profile: this.appService.getAppProfile(row.profile!), + count: of(row.totalCount), + }) + ) + ) + }), + ); + + return forkJoin({ + pins: of(countryPins), + profiles: profiles, + }) + } + ), + repeat({ + delay: 5000 + }), + ) + .subscribe(result => { + this.pins = result.pins; + this.profiles = result.profiles + + this.activeNodeCount = 0; + this.activeCommunityNodeCount = 0; + this.activeSafingNodeCount = 0; + this.exitNodeCount = 0; + this.safingNodeCount = 0; + this.communityNodeCount = 0; + this.safingExitNodeCount = 0; + this.communityExitNodeCount = 0; + + this.pins.forEach(pin => { + if (pin.isOffline) { + return + } + this.totalAliveCount++; + + if (pin.pin.VerifiedOwner === 'Safing') { + this.safingNodeCount++; + + if (pin.isExit) { + this.exitNodeCount++; + this.safingExitNodeCount++; + } + if (pin.isActive) { + this.activeSafingNodeCount++; + this.activeNodeCount++; + } + + } else { + this.communityNodeCount++; + + if (pin.isExit) { + this.exitNodeCount++; + this.communityExitNodeCount++; + } + if (pin.isActive) { + this.activeCommunityNodeCount++; + this.activeNodeCount++; + } + } + }) + + // create a netquery text-query in the format of "exit_node: exit_node: ..." + this.filterConnectionsByCountryNodes = this.pins.map(pin => `exit_node:${pin.pin.ID}`).join(" ") + + this.cdr.markForCheck(); + }) + } + + ngOnChanges(changes: SimpleChanges): void { + // if we are rendered as a regular component (not as a dialog) we need to + // handle updates to our @Inputs(). + // just let ngOnInit() do it's thing if the countryCode changed. + if (!!changes['countryCode']) { + this.ngOnInit(); + } + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/pages/spn/country-details/index.ts b/desktop/angular/src/app/pages/spn/country-details/index.ts new file mode 100644 index 00000000..3ff2c685 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/country-details/index.ts @@ -0,0 +1 @@ +export * from './country-details'; diff --git a/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.html b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.html new file mode 100644 index 00000000..6ea2166f --- /dev/null +++ b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.html @@ -0,0 +1,25 @@ + + + + + {{ countryName }} + + + + + + Safing Nodes: + {{ safingNodes.length }} + + + + + Community Nodes: + {{ communityNodes.length }} + + + + + Click country for details + + diff --git a/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.scss b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.scss new file mode 100644 index 00000000..be98030c --- /dev/null +++ b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.scss @@ -0,0 +1,40 @@ +:host { + @apply flex flex-row items-center justify-center; +} + +.country-content-wrapper { + @apply flex flex-col gap-2 items-center justify-center bg-gray-200 border bg-opacity-50 border-gray-600 border-opacity-25; +} + +.country-name { + @apply text-sm flex flex-row gap-1 items-center justify-center bg-gray-100 bg-opacity-50 py-2 w-full; +} + +.country-stats { + @apply flex flex-col gap-2 items-start py-2 px-4; + + &>span { + @apply flex flex-row gap-1 items-center; + @apply text-xs font-light; + } + + .count { + @apply text-sm font-normal; + } +} + +.country-stats--safing { + svg polygon { + fill: #0376bb; + stroke: #0376bb; + transform: scale(1.15) + } +} + +.country-stats--community { + svg circle { + fill: #239215; + stroke: #239215; + transform: scale(1.15) + } +} diff --git a/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.ts b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.ts new file mode 100644 index 00000000..2af05a26 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.ts @@ -0,0 +1,75 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; +import { BehaviorSubject, combineLatest, map } from 'rxjs'; +import { takeWhile } from 'rxjs/operators'; +import { MapPin, MapService } from './../map.service'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'spn-map-country-overlay', + templateUrl: './country-overlay.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: [ + './country-overlay.scss' + ] +}) +export class CountryOverlayComponent implements OnInit, OnChanges, OnDestroy { + /** The two-letter ISO code of the country */ + @Input() + countryCode!: string; + + /** The (english) name of the country */ + @Input() + countryName!: string; + + /** all nodes in this country operated by Safing */ + safingNodes: MapPin[] = []; + + /** all nodes in this country operated by a community member */ + communityNodes: MapPin[] = []; + + /** used to trigger a reload onChanges */ + private reload$ = new BehaviorSubject(undefined); + + constructor( + private mapService: MapService, + private cdr: ChangeDetectorRef, + ) { } + + ngOnChanges(changes: SimpleChanges): void { + this.reload$.next(); + } + + ngOnInit(): void { + combineLatest([ + this.mapService.pins$, + this.reload$ + ]) + .pipe( + takeWhile(() => !this.reload$.closed), + map(([pins]) => pins.filter(pin => pin.entity.Country === this.countryCode)), + ) + .subscribe(pinsInCountry => { + this.safingNodes = []; + this.communityNodes = []; + + pinsInCountry.forEach(pin => { + if (pin.isOffline && !pin.isActive) { + return + } + + if (pin.pin.VerifiedOwner === 'Safing') { + this.safingNodes.push(pin) + } else { + this.communityNodes.push(pin) + } + }) + + this.cdr.markForCheck(); + }) + } + + ngOnDestroy(): void { + this.reload$.complete(); + } +} + diff --git a/desktop/angular/src/app/pages/spn/country-overlay/index.ts b/desktop/angular/src/app/pages/spn/country-overlay/index.ts new file mode 100644 index 00000000..2e61e978 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/country-overlay/index.ts @@ -0,0 +1 @@ +export * from './country-overlay'; diff --git a/desktop/angular/src/app/pages/spn/index.ts b/desktop/angular/src/app/pages/spn/index.ts new file mode 100644 index 00000000..cc24eeea --- /dev/null +++ b/desktop/angular/src/app/pages/spn/index.ts @@ -0,0 +1 @@ +export * from './spn-page'; diff --git a/desktop/angular/src/app/pages/spn/map-legend/index.ts b/desktop/angular/src/app/pages/spn/map-legend/index.ts new file mode 100644 index 00000000..111ec8c0 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/map-legend/index.ts @@ -0,0 +1 @@ +export * from './map-legend'; diff --git a/desktop/angular/src/app/pages/spn/map-legend/map-legend.html b/desktop/angular/src/app/pages/spn/map-legend/map-legend.html new file mode 100644 index 00000000..07cf2a9b --- /dev/null +++ b/desktop/angular/src/app/pages/spn/map-legend/map-legend.html @@ -0,0 +1,54 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Safing Nodes + {{ safingNodeCount }}
+ + + used as Transit + + {{ safingActiveCount }}
+ + + used as Exit + + {{ safingExitCount }}
+ + Community Nodes + {{ communityNodeCount }}
+ + + used as Transit + + {{ communityActiveCount }}
+ + + used as Exit + + {{ communityExitCount }}
+
diff --git a/desktop/angular/src/app/pages/spn/map-legend/map-legend.ts b/desktop/angular/src/app/pages/spn/map-legend/map-legend.ts new file mode 100644 index 00000000..e561111e --- /dev/null +++ b/desktop/angular/src/app/pages/spn/map-legend/map-legend.ts @@ -0,0 +1,69 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core"; +import { Subscription } from 'rxjs'; +import { MapService } from './../map.service'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'spn-map-legend', + templateUrl: './map-legend.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SpnMapLegendComponent implements OnInit, OnDestroy { + private subscription = Subscription.EMPTY; + + safingNodeCount = 0; + safingExitCount = 0; + safingActiveCount = 0; + + communityNodeCount = 0; + communityExitCount = 0; + communityActiveCount = 0; + + constructor( + private mapService: MapService, + private cdr: ChangeDetectorRef, + ) { } + + ngOnInit() { + this.subscription = this.mapService + .pins$ + .subscribe(pins => { + this.safingActiveCount = 0; + this.safingExitCount = 0; + this.safingNodeCount = 0; + this.communityActiveCount = 0; + this.communityExitCount = 0; + this.communityNodeCount = 0; + + pins.forEach(pin => { + if (pin.pin.VerifiedOwner === 'Safing') { + if (pin.isActive) { + this.safingActiveCount++; + } + + if (pin.isExit) { + this.safingExitCount++ + } + + this.safingNodeCount++ + } else { + if (pin.isActive) { + this.communityActiveCount++; + } + + if (pin.isExit) { + this.communityExitCount++; + } + + this.communityNodeCount++; + } + }) + + this.cdr.markForCheck(); + }) + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/pages/spn/map-renderer/index.ts b/desktop/angular/src/app/pages/spn/map-renderer/index.ts new file mode 100644 index 00000000..60ff8a16 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/map-renderer/index.ts @@ -0,0 +1 @@ +export * from './map-renderer'; diff --git a/desktop/angular/src/app/pages/spn/map-renderer/map-renderer.ts b/desktop/angular/src/app/pages/spn/map-renderer/map-renderer.ts new file mode 100644 index 00000000..bea5ee57 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/map-renderer/map-renderer.ts @@ -0,0 +1,383 @@ +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, Inject, InjectionToken, Input, OnDestroy, OnInit, Optional, inject } from '@angular/core'; +import { GeoPath, GeoPermissibleObjects, GeoProjection, Selection, ZoomTransform, geoMercator, geoPath, json, pointer, select, zoom, zoomIdentity } from 'd3'; +import { feature } from 'topojson-client'; + + +export type MapRoot = Selection; +export type WorldGroup = Selection + +export interface CountryEvent { + event?: MouseEvent; + countryCode: string; + countryName: string; +} + +export interface MapRef { + onMapReady(cb: () => any): void; + onZoomPan(cb: () => any): void; + onCountryHover(cb: (_: CountryEvent | null) => void): void; + onCountryClick(cb: (_: CountryEvent) => void): void; + select(selection: string): Selection | null; + + countryNames: { [key: string]: string }; + root: MapRoot; + projection: GeoProjection; + zoomScale: number; + worldGroup: WorldGroup; +} + +export interface MapHandler { + registerMap(ref: MapRef): void; + unregisterMap(ref: MapRef): void; +} + +export const MAP_HANDLER = new InjectionToken('MAP_HANDLER'); + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'spn-map-renderer', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + styleUrls: [ + './map-style.scss' + ], +}) +export class MapRendererComponent implements OnInit, AfterViewInit, OnDestroy { + static readonly Rotate = 0; // so [-0, 0] is the initial center of the projection + static readonly Maxlat = 83; // clip northern and southern pols (infinite in mercator) + static readonly MarkerSize = 4; + static readonly LineAnimationDuration = 200; + + private readonly destroyRef = inject(DestroyRef); + private destroyed = false; + + countryNames: { + [countryCode: string]: string + } = {} + + // SVG group elements + private svg: MapRoot | null = null; + worldGroup!: WorldGroup; + + // Projection and line rendering functions + projection!: GeoProjection; + zoomScale: number = 1 + + private pathFunc!: GeoPath; + + get root() { + return this.svg! + } + + @Input() + mapId: string = 'map' + + constructor( + private mapRoot: ElementRef, + private cdr: ChangeDetectorRef, + @Inject(MAP_HANDLER) @Optional() private overlays: MapHandler[], + ) { } + + ngOnInit(): void { + this.overlays?.forEach(ov => { + ov.registerMap(this) + }) + + this.cdr.detach() + } + + select(selector: string) { + if (!this.svg) { + return null + } + + return this.svg.select(selector); + } + + private _readyCb: (() => void)[] = []; + onMapReady(cb: () => void) { + this._readyCb.push(cb); + } + + private _zoomCb: (() => void)[] = []; + onZoomPan(cb: () => void) { + this._zoomCb.push(cb) + } + + private _countryHoverCb: ((e: CountryEvent | null) => void)[] = []; + onCountryHover(cb: (e: CountryEvent | null) => void) { + this._countryHoverCb.push(cb); + } + + private _countryClickCb: ((e: CountryEvent) => void)[] = []; + onCountryClick(cb: (e: CountryEvent) => void) { + this._countryClickCb.push(cb) + } + + async ngAfterViewInit() { + await this.renderMap() + + const observer = new ResizeObserver(() => { + this.renderMap() + }) + + this.destroyRef.onDestroy(() => { + observer.unobserve(this.mapRoot.nativeElement) + observer.disconnect() + }) + + observer.observe(this.mapRoot.nativeElement); + } + + async renderMap() { + if (this.destroyed) { + return; + } + + if (!!this.svg) { + this.svg.remove() + } + + const map = select(this.mapRoot.nativeElement); + + // setup the basic SVG elements + this.svg = map + .append('svg') + .attr('id', this.mapId) + .attr("xmlns", "http://www.w3.org/2000/svg") + .attr('width', '100%') + .attr('preserveAspectRation', 'none') + .attr('height', '100%') + + this.worldGroup = this.svg.append('g').attr('id', 'world-group') + + // load the world-map data and start rendering + const world = await json('/assets/world-50m.json'); + + // actually render the countries + const countries = (feature(world, world.objects.countries) as any); + + this.setupProjection(); + await this.setupZoom(countries); + + // we need to await the initial world render here because otherwise + // the initial renderPins() will not be able to update the country attributes + // and cause a delay before the state of the country (has-nodes, is-blocked, ...) + // is visible. + this.renderWorld(countries); + + this._readyCb.forEach(cb => cb()); + } + + ngOnDestroy() { + this.destroyed = true; + + this.overlays?.forEach(ov => ov.unregisterMap(this)); + + this._countryClickCb = []; + this._countryHoverCb = []; + this._readyCb = []; + this._zoomCb = []; + + if (!this.svg) { + return; + } + + this.svg.remove(); + this.svg = null; + } + + private renderWorld(countries: any) { + // actually render the countries + const data = countries.features; + const self = this; + + data.forEach((country: any) => { + this.countryNames[country.properties.iso_a2] = country.properties.name + }) + + this.worldGroup.selectAll() + .data(data) + .enter() + .append('path') + .attr('countryCode', (d: any) => d.properties.iso_a2) + .attr('name', (d: any) => d.properties.name) + .attr('d', this.pathFunc) + .on('mouseenter', function (event: MouseEvent) { + const country = select(this).datum() as any; + const countryEvent: CountryEvent = { + event: event, + countryCode: country.properties.iso_a2, + countryName: country.properties.name, + } + + self._countryHoverCb.forEach(cb => cb(countryEvent)) + }) + .on('mouseout', function (event: MouseEvent) { + self._countryHoverCb.forEach(cb => cb(null)) + }) + .on('click', function (event: MouseEvent) { + const country = select(this).datum() as any; + const countryEvent: CountryEvent = { + event: event, + countryCode: country.properties.iso_a2, + countryName: country.properties.name, + } + + const loc = self.projection.invert!([event.clientX, event.clientY]) + + console.log(loc) + + self._countryClickCb.forEach(cb => cb(countryEvent)) + }) + } + + private setupProjection() { + const size = this.mapRoot.nativeElement.getBoundingClientRect(); + + this.projection = geoMercator() + .rotate([MapRendererComponent.Rotate, 0]) + .scale(1) + .translate([size.width / 2, size.height / 2]); + + + // path is used to update the SVG path to match our mercator projection + this.pathFunc = geoPath().projection(this.projection); + } + + private async setupZoom(countries: any) { + if (!this.svg) { + return + } + + // create a copy of countries + countries = { + ...countries, + features: [...countries.features] + } + + // remove Antarctica from the feature set so projection.fitSize ignores it + // and better aligns the rest of the world :) + const aqIdx = countries.features.findIndex((p: GeoJSON.Feature) => p.properties?.iso_a2 === "AQ"); + if (aqIdx >= 0) { + countries.features.splice(aqIdx, 1) + } + + const size = this.mapRoot.nativeElement.getBoundingClientRect(); + + this.projection.fitSize([size.width, size.height], countries) + + //this.projection.fitWidth(size.width, countries) + //this.projection.fitHeight(size.height, countries) + + // returns the top-left and the bottom-right of the current projection + const mercatorBounds = () => { + const yaw = this.projection.rotate()[0]; + const xymax = this.projection([-yaw + 180 - 1e-6, -MapRendererComponent.Maxlat])!; + const xymin = this.projection([-yaw - 180 + 1e-6, MapRendererComponent.Maxlat])!; + return [xymin, xymax]; + } + + const s = this.projection.scale() + const scaleExtent = [s, s * 10] + + const transform = zoomIdentity + .scale(this.projection.scale()) + .translate(this.projection.translate()[0], this.projection.translate()[1]); + + // whenever the users zooms we need to update our groups + // individually to apply the zoom effect. + let tlast = { + x: 0, + y: 0, + k: 0, + } + + const self = this; + + let z = zoom() + .scaleExtent(scaleExtent as [number, number]) + .on('zoom', (e) => { + const t: ZoomTransform = e.transform; + + if (t.k != tlast.k) { + let p = pointer(e) + let scrollToMouse = () => { }; + + if (!!p && !!p[0]) { + const tp = this.projection.translate(); + const coords = this.projection!.invert!(p) + scrollToMouse = () => { + const newPos = this.projection(coords!)!; + const yaw = this.projection.rotate()[0]; + this.projection.translate([tp[0], tp[1] + (p[1] - newPos[1])]) + this.projection.rotate([yaw + 360.0 * (p[0] - newPos[0]) / size.width * scaleExtent[0] / t.k, 0, 0]) + } + } + + this.projection.scale(t.k); + scrollToMouse(); + + } else { + let dy = t.y - tlast.y; + const dx = t.x - tlast.x; + const yaw = this.projection.rotate()[0] + const tp = this.projection.translate(); + + // use x translation to rotate based on current scale + this.projection.rotate([yaw + 360.0 * dx / size.width * scaleExtent[0] / t.k, 0, 0]) + // use y translation to translate projection clamped to bounds + let bounds = mercatorBounds(); + if (bounds[0][1] + dy > 0) { + dy = -bounds[0][1]; + } else if (bounds[1][1] + dy < size.height) { + dy = size.height - bounds[1][1]; + } + this.projection.translate([tp[0], tp[1] + dy]); + } + + tlast = { + x: t.x, + y: t.y, + k: t.k, + } + + // finally, re-render the SVG shapes according to the new projection + this.worldGroup.selectAll('path') + .attr('d', this.pathFunc) + + + this._zoomCb.forEach(cb => cb()); + }); + + this.svg.call(z) + this.svg.call(z.transform, transform); + } + + public getCoords(lat: number, lng: number) { + const loc = this.projection([lng, lat]); + if (!loc) { + return null; + } + + const rootElem = this.mapRoot.nativeElement.getBoundingClientRect(); + const x = rootElem.x + loc[0]; + const y = rootElem.y + loc[1]; + + return [x, y]; + } + + public coordsInView(lat: number, lng: number) { + const loc = this.projection([lng, lat]); + if (!loc) { + return false + } + + const rootElem = this.mapRoot.nativeElement.getBoundingClientRect(); + const x = rootElem.x + loc[0]; + const y = rootElem.y + loc[1]; + + return x >= rootElem.left && x <= rootElem.right && y >= rootElem.top && y <= rootElem.bottom; + } + +} diff --git a/desktop/angular/src/app/pages/spn/map-renderer/map-style.scss b/desktop/angular/src/app/pages/spn/map-renderer/map-style.scss new file mode 100644 index 00000000..0f319ba7 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/map-renderer/map-style.scss @@ -0,0 +1,167 @@ +::ng-deep { + .pin { + opacity: 0; + + &.in-view { + opacity: 1; + } + } +} + +::ng-deep #spn-map { + --map-bg: #111112; + --map-country-active: #424141; + --map-country-inactive: #2a2a2a; + --map-country-border-width: 2px; + --map-country-border-color: #1e1e1e; + --map-country-border-color-selected: #858585; + --map-country-blocked-primary: #858585; + --map-country-blocked-secondary: #402323; + + .overlay { + fill: none; + pointer-events: all; + } + + g { + + circle, + polygon { + fill: #626262; + stroke: #626262; + stroke-width: 1; + stroke-linejoin: round; + transition: all 200ms linear 0s; + } + + circle:hover, + polygon:hover { + fill: theme('colors.yellow.200'); + stroke: theme('colors.yellow.300'); + stroke-width: 2; + } + } + + g[in-use=true] { + circle { + fill: #239215; + stroke: #239215; + transform: scale(1.15) + } + + polygon { + fill: #0376bb; + stroke: #0376bb; + transform: scale(1.15) + } + } + + g[is-exit=true] { + + circle, + polygon { + transform: scale(1.3); + stroke-width: 2; + } + + polygon { + stroke: #039af4; + fill: #0376bb; + } + + circle { + stroke: #30ae20; + fill: #239215; + } + } + + g[is-home=true] circle { + stroke: white; + stroke-width: 4.5; + fill: black; + transform: scale(1); + } + + g[raise=true] { + + circle, + polygon { + fill: theme('colors.yellow.200'); + stroke: theme('colors.yellow.300'); + stroke-width: 2; + transform: scale(1.8); + } + } + + .marker { + cursor: pointer; + fill: #252525; + stroke: rgba(151, 151, 151, 0.8); + transition: all 250ms 0s cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + + .marker-label { + fill: white; + } + + path.lane { + stroke: rgba(151, 151, 151, 0.2); + fill: transparent; + + &[in-use=true] { + stroke-width: 2; + stroke: #0376bb; + } + + &[is-live=true] { + stroke-width: 1; + stroke: theme('colors.red.300'); + + &[is-encrypted=true] { + stroke: theme('colors.green.200'); + } + + &:hover { + stroke-width: 3; + } + } + } + + #world-group { + path { + fill: var(--map-country-border-color); + stroke: var(--map-country-border-color); + stroke-width: var(--map-country-border-width); + stroke-linejoin: round; + } + + path[has-nodes=true] { + fill: var(--map-country-inactive); + } + + path[in-use=true] { + fill: var(--map-country-active); + } + + path:hover { + cursor: pointer; + fill: var(--map-country-active); + } + + path.selected { + stroke: var(--map-country-border-color-selected); + } + } +} + +:host-context(.disabled) { + @apply bg-white; + + #world-group { + path { + fill: #000000; + stroke: #111111; + stroke-width: .5px; + } + } +} diff --git a/desktop/angular/src/app/pages/spn/map.service.ts b/desktop/angular/src/app/pages/spn/map.service.ts new file mode 100644 index 00000000..da8041a9 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/map.service.ts @@ -0,0 +1,253 @@ +import { Injectable } from '@angular/core'; +import { AppProfile, GeoCoordinates, IntelEntity, Netquery, Pin, SPNService, UnknownLocation, getPinCoords } from '@safing/portmaster-api'; +import { BehaviorSubject, Observable, combineLatest, debounceTime, interval, of, startWith, switchMap } from 'rxjs'; +import { distinctUntilChanged, filter, map, share } from 'rxjs/operators'; +import { SPNStatus } from './../../../../projects/safing/portmaster-api/src/lib/spn.types'; + +export interface MapPin { + pin: Pin; + // location is set to the geo-coordinates that should be used + // for that pin. + location: GeoCoordinates; + // entity is set to the intel entity that should be used for + // this pin. + entity: IntelEntity; + + // whether the pin is regarded as offline / not available. + isOffline: boolean; + + // whether or not the pin is currently used as an exit node + isExit: boolean; + + // whether or not the pin is used as a transit node + isTransit: boolean; + + // whether or not the pin is currently active. + isActive: boolean; + + // whether or not the pin is used as the entry-node. + isHome: boolean; + + // whether the pin has any known issues + hasIssues: boolean; + + // FIXME: remove me + collapsed?: boolean; +} + +@Injectable({ providedIn: 'root' }) +export class MapService { + /** + * activeSince$ emits the pre-formatted duration since the SPN is active + * it formats the duration as "HH:MM:SS" or null if the SPN is not enabled. + */ + activeSince$: Observable; + + /** Emits the current status of the SPN */ + status$: Observable; + + /** Emits all map pins */ + _pins$ = new BehaviorSubject([]); + + get pins$(): Observable { + return this._pins$.asObservable(); + } + + pinsMap$ = this.pins$ + .pipe( + filter(allPins => !!allPins.length), + map(allPins => { + const lm = new Map(); + allPins.forEach(pin => lm.set(pin.pin.ID, pin)); + + return lm + }), + share(), + ) + + constructor( + private spnService: SPNService, + private netquery: Netquery, + ) { + this.status$ = this.spnService + .status$ + .pipe( + map(status => !!status ? status.Status : 'disabled'), + distinctUntilChanged() + ); + + // setup the activeSince$ observable that emits every second how long the + // SPN has been active. + this.activeSince$ = combineLatest([ + this.spnService.status$, + interval(1000).pipe(startWith(-1)) + ]).pipe( + map(([status]) => !!status.ConnectedSince ? this.formatActiveSinceDate(status.ConnectedSince) : null), + share(), + ); + + let pinMap = new Map(); + let pinResult: MapPin[] = []; + + // create a stream of pin updates from the SPN service if it is enabled. + this.status$ + .pipe( + switchMap(status => { + if (status !== 'disabled') { + return combineLatest([ + this.spnService.watchPins(), + interval(5000) + .pipe( + startWith(-1), + switchMap(() => this.getPinIDsUsedAsExit()) + ) + ]) + } + return of([[], []]); + }), + map(([pins, exitPinIDs]) => { + const exitPins = new Set(exitPinIDs); + const activePins = new Set(); + const transitPins = new Set(); + const seenPinIDs = new Set(); + + let hasChanges = false; + + pins.forEach(pin => pin.Route?.forEach((hop, index) => { + if (index < pin.Route!.length - 1) { + transitPins.add(hop) + } + + activePins.add(hop); + })); + + pins.forEach(pin => { + // Save Pin ID as seen. + seenPinIDs.add(pin.ID); + + const oldPinModel = pinMap.get(pin.ID); + + // Get states of new model. + const isOffline = pin.States.includes('Offline') || !pin.States.includes('Reachable'); + const isHome = pin.HopDistance === 1; + const isTransit = transitPins.has(pin.ID); + + const isExit = exitPins.has(pin.ID); + const isActive = activePins.has(pin.ID); + const hasIssues = pin.States.includes('ConnectivityIssues'); + + const pinHasChanged = !oldPinModel || oldPinModel.pin !== pin || + oldPinModel.isOffline !== isOffline || oldPinModel.isHome !== isHome || oldPinModel.isTransit !== isTransit || + oldPinModel.isExit !== isExit || oldPinModel.isActive !== isActive || oldPinModel.hasIssues !== hasIssues; + + if (pinHasChanged) { + const newPinModel: MapPin = { + pin: pin, + location: getPinCoords(pin) || UnknownLocation, + entity: (pin.EntityV4 || pin.EntityV6)!, + isExit, + isTransit, + isActive, + isOffline, + isHome, + hasIssues, + } + + pinMap.set(pin.ID, newPinModel); + + hasChanges = true; + } + }) + + for (let key of pinMap.keys()) { + if (!seenPinIDs.has(key)) { + // this pin has been removed + pinMap.delete(key) + hasChanges = true; + } + } + + if (hasChanges) { + pinResult = Array.from(pinMap.values()); + } + + return pinResult; + }), + debounceTime(10), + distinctUntilChanged(), + ) + .subscribe(pins => this._pins$.next(pins)) + } + + getExitPinIDsForProfile(profile: AppProfile) { + return this.netquery + .query({ + select: ['exit_node'], + groupBy: ['exit_node'], + query: { + profile: { $eq: `${profile.Source}/${profile.ID}` }, + } + }, 'map-service-get-exit-pin-ids-for-profile') + .pipe(map(result => result.map(row => row.exit_node!))) + } + + getPinIDsWithActiveSession() { + return this.pins$ + .pipe( + map(result => result.filter(pin => pin.pin.SessionActive).map(pin => pin.pin.ID)) + ) + } + + getPinIDsUsedAsExit() { + return this.netquery + .query({ + select: ['exit_node'], + groupBy: ['exit_node'] + }, 'map-service-get-pins-used-as-exit') + .pipe( + map(result => result.map(row => row.exit_node!)) + ) + } + + getPinIDsWithActiveConnections() { + return this.netquery.query({ + select: ['exit_node'], + groupBy: ['exit_node'], + query: { + active: { $eq: true } + } + }, 'map-service-get-pins-with-connections') + .pipe( + map(activeExitNodes => { + const pins = this._pins$.getValue(); + + const pinIDs = new Set(); + const pinLookupMap = new Map(); + + pins.forEach(p => pinLookupMap.set(p.pin.ID, p)) + + activeExitNodes.map(row => { + const pin = pinLookupMap.get(row.exit_node!); + if (!!pin) { + pin.pin.Route?.forEach(hop => { + pinIDs.add(hop) + }) + } + }) + + return Array.from(pinIDs); + }) + ) + } + + private formatActiveSinceDate(date: string): string { + const d = new Date(date); + const diff = Math.floor((new Date().getTime() - d.getTime()) / 1000); + const hours = Math.floor(diff / 3600); + const minutes = Math.floor((diff - (hours * 3600)) / 60); + const secs = diff - (hours * 3600) - (minutes * 60); + const pad = (d: number) => d < 10 ? `0${d}` : '' + d; + + return `${pad(hours)}:${pad(minutes)}:${pad(secs)}`; + } +} diff --git a/desktop/angular/src/app/pages/spn/node-icon/index.ts b/desktop/angular/src/app/pages/spn/node-icon/index.ts new file mode 100644 index 00000000..715f271d --- /dev/null +++ b/desktop/angular/src/app/pages/spn/node-icon/index.ts @@ -0,0 +1 @@ +export * from './node-icon'; diff --git a/desktop/angular/src/app/pages/spn/node-icon/node-icon.html b/desktop/angular/src/app/pages/spn/node-icon/node-icon.html new file mode 100644 index 00000000..faf8c684 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/node-icon/node-icon.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/desktop/angular/src/app/pages/spn/node-icon/node-icon.scss b/desktop/angular/src/app/pages/spn/node-icon/node-icon.scss new file mode 100644 index 00000000..b62c9bac --- /dev/null +++ b/desktop/angular/src/app/pages/spn/node-icon/node-icon.scss @@ -0,0 +1,38 @@ +svg { + + circle, + polygon { + fill: #626262; + stroke: #626262; + stroke-width: 1; + stroke-linejoin: round; + transition: all 200ms linear 0s; + } + + polygon.active, + polygon.exit { + fill: #0376bb; + stroke: #0376bb; + transform: scale(1.15) + } + + circle.active, + circle.exit { + fill: #239215; + stroke: #239215; + transform: scale(1.15) + } + + circle.exit, + polygon.exit { + stroke-width: 2; + } + + circle.exit { + stroke: #30ae20; + } + + polygon.exit { + stroke: #039af4; + } +} diff --git a/desktop/angular/src/app/pages/spn/node-icon/node-icon.ts b/desktop/angular/src/app/pages/spn/node-icon/node-icon.ts new file mode 100644 index 00000000..daad9a15 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/node-icon/node-icon.ts @@ -0,0 +1,44 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'spn-node-icon', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './node-icon.html', + styleUrls: ['./node-icon.scss'], +}) +export class SpnNodeIconComponent { + @Input() + set bySafing(v: any) { + this._bySafing = coerceBooleanProperty(v); + } + get bySafing() { return this._bySafing } + private _bySafing = false; + + @Input() + set isActive(v: any) { + this._isActive = coerceBooleanProperty(v); + } + get isActive() { return this._isActive } + private _isActive = false; + + @Input() + set isExit(v: any) { + this._isExit = coerceBooleanProperty(v); + } + get isExit() { return this._isExit; } + private _isExit = false; + + get nodeClass() { + if (this._isExit) { + return 'exit'; + } + + if (this.isActive) { + return 'active' + } + + return ''; + } +} diff --git a/desktop/angular/src/app/pages/spn/pin-details/index.ts b/desktop/angular/src/app/pages/spn/pin-details/index.ts new file mode 100644 index 00000000..9a9851da --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-details/index.ts @@ -0,0 +1 @@ +export * from './pin-details'; diff --git a/desktop/angular/src/app/pages/spn/pin-details/pin-details.html b/desktop/angular/src/app/pages/spn/pin-details/pin-details.html new file mode 100644 index 00000000..8e53befc --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-details/pin-details.html @@ -0,0 +1,127 @@ +

+ + {{ pin?.pin?.Name || 'N/A' }} + + + + +

+ + + This SPN Node is run by + + + + {{ pin.pin.VerifiedOwner || 'Community' }} + + +
+ Node is Offline +
+
+ Node has Issues +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID{{ pin.pin.ID }}
Verified Owner +
{{ pin.pin.VerifiedOwner }}
+
First Seen{{ pin.pin.FirstSeen | date:'medium' }}
IPv4 +
+ + + {{ entity.ASOrg }} + ({{ entity.ASN }}) + + + {{ entity.IP || 'N/A' }} + +
+
IPv6 +
+ + + {{ entity.ASOrg }} + ({{ entity.ASN }}) + + + {{ entity.IP || 'N/A' }} + +
+
States +
{{ pin.pin.States.join(", ") }}
+
SessionActive +
{{ pin.pin.SessionActive }}
+
HopDistance +
{{ pin.pin.HopDistance }}
+
Exit Connections +
+
{{ exitConnectionCount }}
+ + + + + + +
+
+
+ + +
+ +
+
+ + + + + +
diff --git a/desktop/angular/src/app/pages/spn/pin-details/pin-details.ts b/desktop/angular/src/app/pages/spn/pin-details/pin-details.ts new file mode 100644 index 00000000..f7e83fae --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-details/pin-details.ts @@ -0,0 +1,100 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, OnChanges, OnDestroy, OnInit, Optional, SimpleChanges } from '@angular/core'; +import { Netquery } from '@safing/portmaster-api'; +import { SFNG_DIALOG_REF, SfngDialogRef } from '@safing/ui'; +import { Subscription, forkJoin, map, of, switchMap } from 'rxjs'; +import { LaneModel } from '../pin-list/pin-list'; +import { MapPin, MapService } from './../map.service'; + +@Component({ + templateUrl: './pin-details.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PinDetailsComponent implements OnInit, OnChanges, OnDestroy { + private subscription = Subscription.EMPTY; + + @Input() + mapPinID!: string; + + pin: MapPin | null = null; + + /** Holds all pins this pin has a active connection to */ + connectedPins: LaneModel[] = []; + + /** The number of connections that exit at this pin */ + exitConnectionCount: number = 0; + + constructor( + private mapService: MapService, + private netquery: Netquery, + private cdr: ChangeDetectorRef, + @Optional() @Inject(SFNG_DIALOG_REF) public dialogRef?: SfngDialogRef, + ) { } + + ngOnInit(): void { + // if we got opened via a dialog we get the map pin ID from the dialog data. + if (!!this.dialogRef) { + this.mapPinID = this.dialogRef.data; + } + + this.subscription.unsubscribe(); + + this.subscription = this.mapService + .pins$ + .pipe( + map(pins => { + return [pins.find(p => p.pin.ID === this.mapPinID), pins] as [MapPin, MapPin[]]; + }), + switchMap(([pin, allPins]) => forkJoin({ + pin: of(pin), + allPins: of(allPins), + exitConnections: this.netquery.query({ + select: [ + { $count: { field: '*', as: 'totalCount', } }, + ], + query: { + exit_node: pin.pin.ID, + }, + groupBy: ['exit_node'] + }, 'pin-details-get-connections-per-exit-node') + })) + ) + .subscribe((result) => { + this.pin = result.pin || null; + + const lm = new Map(); + result.allPins.forEach(pin => lm.set(pin.pin.ID, pin)) + + const connectedTo = this.pin?.pin.ConnectedTo || {}; + this.connectedPins = Object.keys(connectedTo) + .map(pinID => { + const pin = lm.get(pinID)!; + return { + ...connectedTo[pinID], + mapPin: pin, + } + }); + + if (result.exitConnections.length) { + // we expect only one row to be returned for the above query. + this.exitConnectionCount = result.exitConnections[0].totalCount; + } else { + this.exitConnectionCount = 0; + } + + this.cdr.markForCheck(); + }) + } + + ngOnChanges(changes: SimpleChanges) { + // if we got rendered directly (without a dialog) we need to + // handle updates to the mapPinID input field by re-loading the + // pin details. We do that by simply re-running ngOnInit + if (!!changes['mapPinID']) { + this.ngOnInit() + } + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/pages/spn/pin-list/index.ts b/desktop/angular/src/app/pages/spn/pin-list/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/desktop/angular/src/app/pages/spn/pin-list/pin-list.html b/desktop/angular/src/app/pages/spn/pin-list/pin-list.html new file mode 100644 index 00000000..b21077e4 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-list/pin-list.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameOperatorUsed AsLatencyCapacityIPv4IPv6
+ + + {{ pin.pin.Name }} + +
+ + + + + {{ pin.pin.VerifiedOwner || 'Community' }} + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+ {{ val.Latency / 1000 / 1000 | number:'1.0-2' }} ms + + {{ val.Capacity / 1000 / 1000 | number:'1.0-2' }} Mbit/s + {{ pin.pin.EntityV4?.IP || 'N/A' }}{{ pin.pin.EntityV6?.IP || 'N/A' }} + + + +
diff --git a/desktop/angular/src/app/pages/spn/pin-list/pin-list.ts b/desktop/angular/src/app/pages/spn/pin-list/pin-list.ts new file mode 100644 index 00000000..6f3eeedf --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-list/pin-list.ts @@ -0,0 +1,87 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, TrackByFunction } from '@angular/core'; +import { Lane } from '@safing/portmaster-api'; +import { take } from 'rxjs/operators'; +import { MapPin } from '../map.service'; +import { MapService } from './../map.service'; + +export interface LaneModel extends Lane { + mapPin: MapPin; +} + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'spn-pin-list', + templateUrl: './pin-list.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SpnPinListComponent { + @Input() + set allowHover(v: any) { + this._allowHover = coerceBooleanProperty(v); + } + get allowHover() { return this._allowHover } + private _allowHover = true; + + @Input() + set allowClick(v: any) { + this._allowClick = coerceBooleanProperty(v); + } + get allowClick() { return this._allowClick } + private _allowClick = true; + + @Input() + set pins(pins: (string | MapPin | LaneModel)[]) { + this.mapService + .pinsMap$ + .pipe(take(1)) + .subscribe(allPins => { + this.lanes = null; + + this._pins = (pins || []).map(idOrPin => { + if (typeof idOrPin === 'string') { + return allPins.get(idOrPin)!; + } + + if ('mapPin' in idOrPin) { // LaneModel + if (this.lanes === null) { + this.lanes = new Map(); + } + + this.lanes.set(idOrPin.HubID, { + Capacity: idOrPin.Capacity, + Latency: idOrPin.Latency, + }) + + return idOrPin.mapPin; + } + + return idOrPin; // MapPin + }) + + this.cdr.markForCheck(); + }) + } + get pins(): MapPin[] { + return this._pins; + } + private _pins: MapPin[] = []; + + /** If we got LaneModel in @Input() pins than this will contain a map with the capacity/latency */ + lanes: Map> | null = null; + + /** Emits the ID of the pin that got hovered, null if the mouse left a pin */ + @Output() + pinHover = new EventEmitter(); + + @Output() + pinClick = new EventEmitter(); + + /** @private - A {@link TrackByFunction} for all pins available in this country */ + trackPin: TrackByFunction = (_: number, pin: MapPin) => pin.pin.ID; + + constructor( + private mapService: MapService, + private cdr: ChangeDetectorRef + ) { } +} diff --git a/desktop/angular/src/app/pages/spn/pin-overlay/index.ts b/desktop/angular/src/app/pages/spn/pin-overlay/index.ts new file mode 100644 index 00000000..620c76d3 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-overlay/index.ts @@ -0,0 +1 @@ +export * from './pin-overlay'; diff --git a/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.html b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.html new file mode 100644 index 00000000..4bcd2f4c --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.html @@ -0,0 +1,117 @@ +
+
+ + Show Details + Show exit connections + Copy Node ID + + + + + {{ mapPin.pin.Name }} + + + + + + + + + + + + + + + + + + +
+
+ IPv4 + {{ mapPin.pin.EntityV4?.IP || 'N/A' }} +
+
+ IPv6 + {{ mapPin.pin.EntityV6?.IP || 'N/A' }} +
+
+ Run By + + + + + + + {{ mapPin.pin.VerifiedOwner || 'Community' }} + +
+
+ Used As + +
+ + + + + + + Home Node + + + + + + + + + + Exit Node + + + + + + + + + + Transit Node + + + + +
+
+
+ + + + + + + + + diff --git a/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.scss b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.scss new file mode 100644 index 00000000..68c4ea1f --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.scss @@ -0,0 +1,4 @@ +:host { + min-width: 220px; + display: block; +} diff --git a/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.ts b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.ts new file mode 100644 index 00000000..00122703 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.ts @@ -0,0 +1,190 @@ +import { AnimationEvent, animate, keyframes, style, transition, trigger } from '@angular/animations'; +import { CdkDrag, CdkDragHandle, CdkDragRelease } from '@angular/cdk/drag-drop'; +import { Overlay, OverlayRef, PositionStrategy } from '@angular/cdk/overlay'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Inject, Input, OnInit, Output, ViewChild, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { SfngDialogService } from '@safing/ui'; +import { PinDetailsComponent } from '../pin-details'; +import { MapOverlay, Path } from '../spn-page'; +import { ActionIndicatorService } from './../../../shared/action-indicator/action-indicator.service'; +import { MapPin } from './../map.service'; +import { OVERLAY_REF } from './../utils'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; + +export interface PinOverlayHoverEvent { + type: 'enter' | 'leave'; + pinID: string; +} + +@Component({ + templateUrl: './pin-overlay.html', + styleUrls: [ + './pin-overlay.scss' + ], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('moveIn', [ + transition(':enter', [ + style({ transform: 'scale(0)', transformOrigin: 'top left' }), + animate('200ms {{ delay }}ms cubic-bezier(0, 0, 0.2, 1)', + keyframes([ + style({ transform: 'scaleX(1) scaleY(0.1)', transformOrigin: 'top left', offset: 0.3 }), + style({ transform: 'scaleX(1) scaleY(1)', transformOrigin: 'top left', offset: 0.8 }), + ]) + ) + ], { params: { delay: "0" } }), + transition(':leave', [ + style({ transform: 'scale(1)', opacity: 1, transformOrigin: 'top left' }), + animate('500ms cubic-bezier(0, 0, 0.2, 1)', + keyframes([ + style({ transform: 'scaleX(1) scaleY(0.1)', opacity: 0.5, transformOrigin: 'top left', offset: 0.3 }), + style({ transform: 'scaleX(0) scaleY(0)', opacity: 0, transformOrigin: 'top left', offset: 0.8 }), + ]) + ) + ]) + ]) + ] +}) +export class PinOverlayComponent implements OnInit { + private readonly integration = inject(INTEGRATION_SERVICE); + + @Input() + mapPin!: MapPin; + + @Input() + routeHome?: Path; + + @Input() + additionalPaths?: Path[] = []; + + @Input() + delay: number = 0; + + @Output() + afterDispose = new EventEmitter(); + + @Output() + overlayHover = new EventEmitter(); + + @ViewChild(CdkDrag) + dragContainer!: CdkDrag; + + @ViewChild(CdkDragHandle) + dragHandle!: CdkDragHandle; + + showContent = false; + + /** Indicates whether or not the pin overlay has been moved by the user */ + hasBeenMoved = false; + + private oldPositionStrategy?: PositionStrategy; + + @HostListener('mouseenter') + onHostElementMouseEnter(event: MouseEvent) { + this.overlayHover.next({ + type: 'enter', + pinID: this.mapPin.pin.ID + }) + + this.containerClass = ''; + } + + @HostListener('mouseleave') + onHostElementMouseLeave(event: MouseEvent) { + this.overlayHover.next({ + type: 'leave', + pinID: this.mapPin.pin.ID + }) + + this.containerClass = 'bg-opacity-90' + } + + /** on double-click, restore the old pin overlay position (before being initialy dragged by the user) */ + onDragDblClick() { + if (!!this.oldPositionStrategy) { + this.overlayRef.updatePositionStrategy(this.oldPositionStrategy); + this.overlayRef.updatePosition(); + this.hasBeenMoved = false; + } + } + + onDragStart() { + this.containerClass = 'outline' + } + + openPinDetails() { + this.dialog.create(PinDetailsComponent, { + data: this.mapPin.pin.ID, + autoclose: true, + backdrop: false, + dragable: true, + }) + } + + onDragRelease(event: CdkDragRelease) { + if (!this.dragContainer || !this.overlayRef.hostElement || !this.overlayRef.hostElement.parentElement) { + return; + } + + const bbox = this.dragContainer.element.nativeElement.getBoundingClientRect(); + const parent = this.overlayRef.hostElement.parentElement!.getBoundingClientRect(); + + if (!this.oldPositionStrategy) { + this.oldPositionStrategy = this.overlayRef.getConfig().positionStrategy; + } + + this.containerClass = ''; + + this.dragContainer.reset() + + this.overlayRef.updatePositionStrategy( + this.overlay.position() + .global() + .top((bbox.top - parent.top) + 'px') + .left((bbox.left - parent.left) + 'px') + ); + + this.hasBeenMoved = true; + } + + onAnimationComplete(event: AnimationEvent) { + if (event.toState === 'void') { + this.afterDispose.next(this.mapPin.pin.ID) + this.overlayRef.dispose(); + } + } + + containerClass = ''; + + constructor( + @Inject(OVERLAY_REF) public readonly overlayRef: OverlayRef, + @Inject(MapOverlay) public overlay: Overlay, + private dialog: SfngDialogService, + private actionIndicator: ActionIndicatorService, + private router: Router, + private cdr: ChangeDetectorRef, + ) { } + + ngOnInit(): void { + this.showContent = true; + this.cdr.markForCheck(); + } + + disposeOverlay() { + this.showContent = false; + this.cdr.markForCheck(); + } + + showExitConnections() { + this.router.navigate(['/monitor'], { + queryParams: { + q: 'exit_node:' + this.mapPin.pin.ID + } + }) + } + + async copyNodeID() { + await this.integration.writeToClipboard(this.mapPin?.pin.ID) + this.actionIndicator.success("Copied to Clipboard") + } +} diff --git a/desktop/angular/src/app/pages/spn/pin-route/index.ts b/desktop/angular/src/app/pages/spn/pin-route/index.ts new file mode 100644 index 00000000..f97ea758 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-route/index.ts @@ -0,0 +1 @@ +export * from './pin-route'; diff --git a/desktop/angular/src/app/pages/spn/pin-route/pin-route.html b/desktop/angular/src/app/pages/spn/pin-route/pin-route.html new file mode 100644 index 00000000..1927c5cd --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-route/pin-route.html @@ -0,0 +1,53 @@ + +
+ +
+
    +
  • + + + + + Your Device + +
  • + +
  • + + + + + + {{ node.entity.Country || 'No Location' }} + {{ node.entity.IP || '' + }} + Home + Exit +
    {{ node.pin.Name }} + + + by + + {{ node.pin.VerifiedOwner || 'Community' }} + +
    + +
    AS{{ node.entity.ASN }} - {{ node.entity.ASOrg || + 'AS Organization not in DB' + }}
    + +
    {{ node.pin.ID }}
    +
  • + +
  • + + + + + + Destination + + +
  • +
+
diff --git a/desktop/angular/src/app/pages/spn/pin-route/pin-route.scss b/desktop/angular/src/app/pages/spn/pin-route/pin-route.scss new file mode 100644 index 00000000..2c66a8ee --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-route/pin-route.scss @@ -0,0 +1,67 @@ +.tunnel-path { + position: relative; + + .line { + position: absolute; + top: 10px; + bottom: 10px; + left: 8px; + width: 1px; + background-color: rgba(255, 255, 255, 0.1); + } + + .node-tag { + border-radius: 1px solid rgba(255, 255, 255, 0.2); + background-color: rgba(255, 255, 255, 0.1); + padding: 2px; + font-size: 85%; + border-radius: 2px; + transform: scale(0.85); + display: inline-block; + } + + ul { + position: relative; + padding-left: 20px; + + li:not(:last-of-type) { + padding-bottom: 0.35rem; + } + + .ip { + margin-left: 0.35rem; + } + + .hop-icon { + display: inline-block; + margin-left: -17px; + margin-right: 4px; + font-weight: 400; + + &.country { + margin-left: -20px; + } + } + + .hop-title { + margin-right: 2px; + } + + .country { + display: inline-block; + margin-left: -20px; + margin-right: 4px; + + &.unknown { + height: 14px; + width: 16px; + position: relative; + top: 3px; + border: 1px solid rgba(0, 0, 0, 0.25); + opacity: 0.5; + border-radius: 3px; + @apply bg-buttons-icon; + } + } + } +} diff --git a/desktop/angular/src/app/pages/spn/pin-route/pin-route.ts b/desktop/angular/src/app/pages/spn/pin-route/pin-route.ts new file mode 100644 index 00000000..d862619d --- /dev/null +++ b/desktop/angular/src/app/pages/spn/pin-route/pin-route.ts @@ -0,0 +1,46 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from "@angular/core"; +import { TunnelNode } from "@safing/portmaster-api"; +import { take } from 'rxjs'; +import { MapPin, MapService } from './../map.service'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'sfng-spn-pin-route', + templateUrl: './pin-route.html', + styleUrls: ['./pin-route.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SpnPinRouteComponent { + @Input() + set route(path: (string | MapPin | TunnelNode)[] | null) { + this.mapService + .pinsMap$ + .pipe( + take(1), + ) + .subscribe(lm => { + this._route = (path || []).map(idOrPin => { + if (typeof idOrPin === 'string') { + return lm.get(idOrPin)!; + } + + if ('ID' in idOrPin) { // TunnelNode + return lm.get(idOrPin.ID)! + } + + return idOrPin; + }); + + this.cdr.markForCheck(); + }) + } + get route(): MapPin[] { + return this._route + } + private _route: MapPin[] = []; + + constructor( + private mapService: MapService, + private cdr: ChangeDetectorRef, + ) { } +} diff --git a/desktop/angular/src/app/pages/spn/spn-feature-carousel/index.ts b/desktop/angular/src/app/pages/spn/spn-feature-carousel/index.ts new file mode 100644 index 00000000..d07cc9e1 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn-feature-carousel/index.ts @@ -0,0 +1 @@ +export * from './spn-feature-carousel'; diff --git a/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.html b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.html new file mode 100644 index 00000000..b73683f4 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.html @@ -0,0 +1,274 @@ +
+ + + + + + +
+
+

Get + Multiple Identities for Each App

+ + Automatically get a vast amount of identities (IP addresses). The SPN calculates an individual path for + every + connection through the privacy network. Spread your connections across the globe, without any effort. + +
+ +
+
+ + + + +
+
+

Easily Adjust Your Privacy

+ + SPN just works and does the heavy lifting for you. But of course you can easily configure the settings, so + it fits your needs: Exclude certain apps and domains from the SPN. Or never exit in specific countries. And + so much more... + +
+ +
+
+ + +
+
+

Built from Scratch, for Your Privacy

+ + SPN is built from the ground up. Privacy is cooked right into it. Inspired by Tor, it comes with onion + routing and state of the art encryption. Fully open source so all our claims can be validated. + +
+ +
+
+ + +
+
+

Bye Bye, VPNs

+ + VPN technology was NOT built for user privacy, but for company security. Because of that, you can only trust + a VPN provider's policy - and many have been caught abusing user data. Honestly, the best way forward: just + stop paying for outdated technology. + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Most VPNs +
+ Read Comparison Blog +
+
SPNTor
Multiple Identities (simultaneous) + + + + + +
Individual Apps Settings + + + + + +
Easy Setup + + + + Browser Only
Availabilty +
+ + + + +
+
+
+ + +
+
+
+ + + + +
+
Open Source + + + + + +
Built for Privacy + + + + + +
+
+
+
+ + + + + +
+ +
+ +
diff --git a/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.scss b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.scss new file mode 100644 index 00000000..7ffa92a2 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.scss @@ -0,0 +1,62 @@ +:host { + @apply flex flex-col gap-2 justify-center items-center relative; +} + +section { + @apply flex flex-row items-start gap-4 justify-evenly text-background; + + &.reverse { + @apply flex-row-reverse + } + + &>div { + @apply flex flex-col w-1/3 gap-6; + + span { + @apply text-base break-normal text-background text-opacity-80; + } + + h1, + h1>span { + @apply text-2xl font-semibold break-normal md:text-3xl lg:text-4xl xl:text-5xl text-background; + + } + + h1>span { + &.text-blue { + color: theme('colors.blue.DEFAULT') !important; + } + } + } + + img { + position: relative; + max-width: 50%; + } + + table { + @apply mb-12; + + th { + @apply text-base; + } + + td { + @apply text-center p-2 leading-6; + } + + tr>td:first-of-type { + @apply text-left p-2 font-semibold text-base whitespace-nowrap; + } + } +} + +::ng-deep { + spn-feature-carousel { + sfng-tab-outlet { + &>div { + overflow: visible !important; + } + } + } +} diff --git a/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.ts b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.ts new file mode 100644 index 00000000..e68edbb3 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.ts @@ -0,0 +1,83 @@ +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnDestroy, QueryList, ViewChild, ViewChildren } from "@angular/core"; +import { SfngTabComponent, SfngTabGroupComponent } from '@safing/ui'; +import { filter, interval, startWith, Subscription } from 'rxjs'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'spn-feature-carousel', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './spn-feature-carousel.html', + styleUrls: [ + './spn-feature-carousel.scss' + ] +}) +export class SPNFeatureCarouselComponent implements AfterViewInit, OnDestroy { + private sub: Subscription = Subscription.EMPTY; + + pause = false; + currentIndex = -1; + + @HostListener('mouseenter') + onMouseEnter() { + this.pause = true + } + + @HostListener('mouseleave') + onMouseLeave() { + this.pause = false; + } + + /** A list of all carousel templates */ + @ViewChildren(SfngTabComponent) + carousel!: QueryList; + + @ViewChild(SfngTabGroupComponent) + tabGroup!: SfngTabGroupComponent; + + constructor( + private cdr: ChangeDetectorRef + ) { } + + ngAfterViewInit(): void { + this.sub = interval(5000) + .pipe( + startWith(-1), + filter(() => !this.pause), + ) + .subscribe(() => { + this.openTab(this.currentIndex + 1, 'left') + }) + } + + ngOnDestroy(): void { + this.sub.unsubscribe() + } + + openTab(idx: number, direction?: 'left' | 'right') { + // force animation to circle if we go before the first + // or after the last one. + if (idx < 0) { + idx = this.carousel.length - 1; + direction = 'right' + } + if (idx >= this.carousel.length) { + direction = 'left' + } + + this.currentIndex = idx % this.carousel.length; + this.tabGroup.activateTab(this.currentIndex, direction)!; + this.cdr.markForCheck(); + } + + showNext() { + this.sub.unsubscribe() + + this.openTab(this.currentIndex + 1) + } + + showPrev() { + this.sub.unsubscribe() + + this.openTab(this.currentIndex - 1) + } +} diff --git a/desktop/angular/src/app/pages/spn/spn-page.html b/desktop/angular/src/app/pages/spn/spn-page.html new file mode 100644 index 00000000..28a61450 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn-page.html @@ -0,0 +1,102 @@ + +
+
+ +
+ + Loading data, please wait ... +
+
+
+ +
+
+ + + + +
+ + + + + + Pricing + +
+
+
+
+ + +
+ + +
+ +
+ + + + + + + +
+ + + + Pro Tip: + +
+ + +
+
+ + + + Hold +
CTRL
key and click a node on the map to immediately open the node details dialog. +
+ + + Hold +
SHIFT
key to open more than one node overlay when clicking the node icon. +
+ + + To keep node overlays open move them using + + + . Double click to revert the overlay position on the map. + + + + Click on a country to get more information about all nodes in that country and a list of Apps that use nodes in the + country as an identity. + +
diff --git a/desktop/angular/src/app/pages/spn/spn-page.scss b/desktop/angular/src/app/pages/spn/spn-page.scss new file mode 100644 index 00000000..40441ef4 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn-page.scss @@ -0,0 +1,143 @@ +:host { + @apply flex flex-row w-full h-full justify-items-stretch items-stretch relative; +} + +.text-info-red { + color: theme("colors.info.red"); +} + +.network-status-dialog { + width: 50vw; + height: 50vh; + min-height: 300px; + min-width: 400px; + padding: 12px; + overflow: auto; + display: flex; + flex-direction: column; + + .issue { + flex-grow: 1; + } + + .issue-list { + width: 100% !important; + flex-grow: 1; + + ul { + overflow: auto; + } + } + + .issue.expanded { + background-color: var(--button-light) !important; + } + + .body { + background-color: var(--cards-primary) !important; + } +} + +.connect-button { + + &.spn-connected { + @apply bg-info-blue; + } + + &.spn-connecting { + @apply bg-info-blue; + } + + &.spn-failed { + @apply bg-info-red; + } + + &:hover { + @apply bg-info-blue opacity-75; + } +} + +.table { + @apply w-full font-normal; + + &>div { + @apply text-xs border-buttons-dark flex flex-row justify-between py-1; + + &:not(:last-child) { + @apply border-b; + } + + span:first-child { + @apply text-tertiary; + } + + span:last-child { + @apply text-primary; + } + } +} + + +table tr:nth-child(odd) { + background: none; +} + + +.tunnel-path { + position: relative; + + .line { + position: absolute; + top: 10px; + bottom: 10px; + left: 8px; + width: 1px; + background-color: rgba(255, 255, 255, 0.1); + } + + + ul { + position: relative; + padding-left: 20px; + + li:not(:last-of-type) { + padding-bottom: 0.35rem; + } + + .ip { + margin-left: 0.35rem; + } + + .hop-icon { + display: inline-block; + margin-left: -17px; + margin-right: 4px; + font-weight: 400; + + &.country { + margin-left: -20px; + } + } + + .hop-title { + margin-right: 2px; + } + + .country { + display: inline-block; + margin-left: -20px; + margin-right: 4px; + + &.unknown { + height: 14px; + width: 16px; + position: relative; + top: 3px; + border: 1px solid rgba(0, 0, 0, 0.25); + opacity: 0.5; + border-radius: 3px; + @apply bg-buttons-icon; + } + } + } +} diff --git a/desktop/angular/src/app/pages/spn/spn-page.ts b/desktop/angular/src/app/pages/spn/spn-page.ts new file mode 100644 index 00000000..992dbe19 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn-page.ts @@ -0,0 +1,1012 @@ +import { coerceElement } from "@angular/cdk/coercion"; +import { Overlay, OverlayContainer } from "@angular/cdk/overlay"; +import { ComponentPortal } from '@angular/cdk/portal'; +import { HttpClient } from '@angular/common/http'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, DestroyRef, ElementRef, Inject, Injectable, InjectionToken, Injector, OnDestroy, OnInit, QueryList, TemplateRef, ViewChild, ViewChildren, forwardRef, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute, ParamMap, Router } from "@angular/router"; +import { AppProfile, ConfigService, Connection, ExpertiseLevel, FeatureID, Netquery, PORTMASTER_HTTP_API_ENDPOINT, PortapiService, SPNService, SPNStatus, UserProfile } from "@safing/portmaster-api"; +import { SfngDialogService } from "@safing/ui"; +import { Line as D3Line, Selection, interpolateString, line, select } from 'd3'; +import { BehaviorSubject, Observable, Subscription, combineLatest, interval, of } from "rxjs"; +import { catchError, debounceTime, map, mergeMap, share, startWith, switchMap, take, takeUntil, withLatestFrom } from "rxjs/operators"; +import { fadeInAnimation, fadeInListAnimation, fadeOutAnimation } from "src/app/shared/animations"; +import { ExpertiseService } from "src/app/shared/expertise/expertise.service"; +import { SPNAccountDetailsComponent } from "src/app/shared/spn-account-details"; +import { CountryDetailsComponent } from "./country-details"; +import { CountryEvent, MAP_HANDLER, MapRef, MapRendererComponent } from "./map-renderer/map-renderer"; +import { MapPin, MapService } from "./map.service"; +import { PinDetailsComponent } from "./pin-details"; +import { PinOverlayComponent } from "./pin-overlay"; +import { OVERLAY_REF } from './utils'; + +export const MapOverlay = new InjectionToken('MAP_OVERLAY') + +export type PinGroup = Selection; +export type LaneGroup = Selection; + +export interface Path { + id: string; + points: (MapPin | [number, number])[]; + attributes?: { + [key: string]: string; + } +} + +export interface PinEvent { + event?: MouseEvent; + mapPin: MapPin; +} + + +/** + * A custom class that implements the OverlayContainer interface of CDK. This + * is used so we can configure a custom container element that will hold all overlays created + * by the map component. This way the overlays will be bound to the map container and not overflow + * the sidebar or other overlays that are created by the "root" app. + */ +@Injectable() +class MapOverlayContainer { + private _overlayContainer?: HTMLElement; + + setOverlayContainer(element: ElementRef | HTMLElement) { + this._overlayContainer = coerceElement(element); + } + + getContainerElement(): HTMLElement { + if (!this._overlayContainer) { + throw new Error("Overlay container element not initialized. Call setOverlayContainer first.") + } + + return this._overlayContainer; + } +} + +@Component({ + templateUrl: './spn-page.html', + styleUrls: ['./spn-page.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + MapOverlayContainer, + { provide: MapOverlay, useClass: Overlay }, + { provide: OverlayContainer, useExisting: MapOverlayContainer }, + { provide: MAP_HANDLER, useExisting: forwardRef(() => SpnPageComponent), multi: true } + ], + animations: [ + fadeInListAnimation, + fadeInAnimation, + fadeOutAnimation + ] +}) +export class SpnPageComponent implements OnInit, OnDestroy, AfterViewInit { + private destroyRef = inject(DestroyRef); + + private countryDebounceTimer: any | null = null; + + /** a list of opened country details. required to close them on destry */ + private openedCountryDetails: CountryDetailsComponent[] = []; + + readonly featureID = FeatureID.SPN; + + paths: Path[] = []; + + @ViewChild('overlayContainer', { static: true, read: ElementRef }) + overlayContainer!: ElementRef; + + @ViewChild(MapRendererComponent, { static: true }) + mapRenderer!: MapRendererComponent; + + @ViewChild('accountDetails', { read: TemplateRef, static: true }) + accountDetails: TemplateRef | null = null; + + /** A list of pro-tip templates in our view */ + @ViewChildren('proTip', { read: TemplateRef }) + proTipTemplates!: QueryList>; + + /** The selected pro-tip template */ + proTipTemplate: TemplateRef | null = null; + + /** currentUser holds the current SPN user profile if any */ + currentUser: UserProfile | null = null; + + /** An observable that emits all active processes. */ + activeProfiles$: Observable; + + /** Whether or not we are still waiting for all data in order to satisfy a "show process/pin" request by query-params */ + loading = true; + + /** a list of currently selected pins */ + selectedPins: PinOverlayComponent[] = []; + + /** the currently hovered country, if any */ + hoveredCountry: { + countryName: string; + countryCode: string; + } | null = null; + + liveMode = false; + liveModePaths: Path[] = []; + + private liveModeSubscription = Subscription.EMPTY; + + /** + * spnStatusTranslation translates the spn status to the text that is displayed + * at the view + */ + readonly spnStatusTranslation: Readonly> = { + connected: 'Connected', + connecting: 'Connecting', + disabled: 'Disabled', + failed: 'Failure' + } + + + private mapRef: MapRef | null = null; + private lineFunc: D3Line<(MapPin | [number, number])> | null = null; + private highlightedPins = new Set(); + + registerMap(ref: MapRef) { + this.mapRef = ref; + + ref.onMapReady(() => { + // we want to have straight lines between our hubs so we use a custom + // path function that updates x and y coordinates based on the mercator projection + // without, points will no be at the correct geo-coordinates. + this.lineFunc = line() + .x(d => { + if (Array.isArray(d)) { + return this.mapRef!.projection([d[0], d[1]])![0]; + } + return this.mapRef!.projection([d.location.Longitude, d.location.Latitude])![0]; + }) + .y(d => { + if (Array.isArray(d)) { + return this.mapRef!.projection([d[0], d[1]])![1]; + } + return this.mapRef!.projection([d.location.Longitude, d.location.Latitude])![1]; + }) + + this.mapRef!.root.append('g').attr('id', 'line-group') + this.mapRef!.root.append('g').attr('id', 'pin-group') + + if (this.mapService._pins$.getValue().length > 0) { + this.renderPins(this.mapService._pins$.getValue()) + } + }) + + ref.onCountryClick(event => this.onCountryClick(event)) + ref.onCountryHover(event => this.onCountryHover(event)) + ref.onZoomPan(() => this.onZoomAndPan()) + } + + unregisterMap(ref: MapRef) { + this.mapRef = null; + this.lineFunc = null; + } + + constructor( + private configService: ConfigService, + private spnService: SPNService, + private netquery: Netquery, + private expertiseService: ExpertiseService, + private router: Router, + private route: ActivatedRoute, + private portapi: PortapiService, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string, + private http: HttpClient, + public mapService: MapService, + @Inject(MapOverlay) private mapOverlay: Overlay, + private dialog: SfngDialogService, + private overlayContainerService: MapOverlayContainer, + private cdr: ChangeDetectorRef, + private injector: Injector, + ) { + this.activeProfiles$ = interval(5000) + .pipe( + startWith(-1), + switchMap(() => this.netquery.getActiveProfiles()), + share({ connector: () => new BehaviorSubject([]) }) + ) + } + + ngAfterViewInit() { + // configure our custom overlay container + this.overlayContainerService.setOverlayContainer(this.overlayContainer); + + // Select a random "Pro-Tip" template and run change detection + this.proTipTemplate = this.proTipTemplates.get(Math.floor(Math.random() * this.proTipTemplates.length)) || null; + this.cdr.detectChanges(); + } + + openAccountDetails() { + this.dialog.create(SPNAccountDetailsComponent, { + autoclose: true, + backdrop: 'light' + }) + } + + ngOnInit() { + this.spnService + .profile$ + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(() => of(null)) + ) + .subscribe((user: UserProfile | null) => { + if (user?.state !== '') { + this.currentUser = user || null; + } else { + this.currentUser = null; + } + + this.cdr.markForCheck(); + }) + + let previousQueryMap: ParamMap | null = null; + + combineLatest([ + this.route.queryParamMap, + this.mapService.pins$, + this.activeProfiles$, + ]) + .pipe( + takeUntilDestroyed(this.destroyRef), + ).subscribe(([params, pins, profiles]) => { + if (params !== previousQueryMap) { + const app = params.get("app") + if (!!app) { + const profile = profiles.find(p => `${p.Source}/${p.ID}` === app); + if (!!profile) { + const pinID = params.get("pin") + const pin = pins.find(p => p.pin.ID === pinID); + + this.selectGroup(profile, pin) + } + } + + previousQueryMap = params; + } + + this.renderPins(pins); + + // we're done with everything now. + this.loading = false; + }) + + } + + toggleLiveMode(enabled: boolean) { + this.liveMode = enabled; + + if (!enabled) { + this.liveModeSubscription.unsubscribe(); + this.liveModePaths = []; + this.updatePaths([]); + this.cdr.markForCheck(); + + return; + } + + this.liveModeSubscription = this.portapi.watchAll("network:tree") + .pipe( + withLatestFrom(this.mapService.pinsMap$), + takeUntilDestroyed(this.destroyRef), + debounceTime(100), + ) + .subscribe(([connections, mapPins]) => { + connections = connections.filter(conn => conn.Ended === 0 && !!conn.TunnelContext); + + this.liveModePaths = connections.map(conn => { + const points: (MapPin | [number, number])[] = conn.TunnelContext!.Path.map(hop => mapPins.get(hop.ID)!) + + if (!!conn.Entity.Coordinates) { + points.push([conn.Entity.Coordinates.Longitude, conn.Entity.Coordinates.Latitude]) + } + + return { + id: conn.Entity.Domain || conn.ID, + points: points, + attributes: { + 'is-live': 'true', + 'is-encrypted': `${conn.Encrypted}` + } + } + }) + + this.updatePaths([]) + this.cdr.markForCheck(); + }) + } + + /** + * Toggle the spn/enable setting. This does NOT update the view as that + * will happen as soon as we get an update from the db qsub. + * + * @private - template only + */ + toggleSPN() { + this.configService.get('spn/enable') + .pipe( + map(setting => setting.Value ?? setting.DefaultValue), + mergeMap(active => this.configService.save('spn/enable', !active)) + ) + .subscribe() + } + + /** + * Select one or more pins by ID. If shift key is hold then all currently + * selected pin overlays will be cleared before selecting the new ones. + */ + private selectPins(event: MouseEvent | undefined, pinIDs: Observable) { + combineLatest([ + this.mapService.pins$, + pinIDs, + ]) + .pipe(take(1)) + .subscribe(([allPins, pinIDs]) => { + if (event?.shiftKey !== true) { + this.selectedPins + .filter(overlay => !overlay.hasBeenMoved) + .forEach(selected => selected.disposeOverlay()) + } + + pinIDs + .filter(id => !this.selectedPins.find(selectedPin => selectedPin.mapPin.pin.ID === id)) + .map(id => allPins.find(pin => pin.pin.ID === id)) + .filter(mapPin => !!mapPin) + .forEach(mapPin => this.onPinClick({ + mapPin: mapPin!, + })); + }) + } + + /** + * Select all pins that are used for transit. + * + * @private - template only + */ + selectTransitNodes(event: MouseEvent) { + this.selectPins(event, this.mapService.getPinIDsWithActiveSession()) + } + + /** + * Select all pins that are used as an exit hub. + * + * @private - template only + */ + selectExitNodes(event: MouseEvent) { + this.selectPins(event, this.mapService.getPinIDsUsedAsExit()) + } + + /** + * Select all pins that currently host alive connections. + * + * @private - template only + */ + selectNodesWithAliveConnections(event: MouseEvent) { + this.selectPins(event, this.mapService.getPinIDsWithActiveConnections()) + } + + navigateToMonitor(process: AppProfile) { + this.router.navigate(['/app', process.Source, process.ID]) + } + + ngOnDestroy() { + this.openedCountryDetails.forEach(cmp => cmp.dialogRef!.close()); + } + + onZoomAndPan() { + this.updateOverlayPositions(); + + if (this.mapRef) { + this.mapRef.root + .select('#lines-group') + .selectAll('path') + .attr('d', d => this.lineFunc!(d.points)) + + this.mapRef.root + .select("#pin-group") + .selectAll('g') + .attr('transform', d => `translate(${this.mapRef!.projection([d.location.Longitude, d.location.Latitude])})`) + } + + this.cdr.markForCheck(); + } + + private createPinOverlay(pinEvent: PinEvent, lm: Map): PinOverlayComponent { + const paths = this.getRouteHome(pinEvent.mapPin, lm, false) + const overlayBoundingRect = this.overlayContainer.nativeElement.getBoundingClientRect(); + const target = pinEvent.event?.target || this.getPinElem(pinEvent.mapPin.pin.ID)?.children[0]; + let delay = 0; + if (paths.length > 0) { + delay = paths[0].points.length * MapRendererComponent.LineAnimationDuration; + } + + const overlayRef = this.mapOverlay.create({ + positionStrategy: this.mapOverlay.position() + .flexibleConnectedTo(new ElementRef(target)) + .withDefaultOffsetY(-overlayBoundingRect.y - 10) + .withDefaultOffsetX(-overlayBoundingRect.x + 20) + .withPositions([ + { + overlayX: 'start', + overlayY: 'top', + originX: 'start', + originY: 'top' + } + ]), + scrollStrategy: this.mapOverlay.scrollStrategies.reposition(), + }) + + const injector = Injector.create({ + providers: [ + { + provide: OVERLAY_REF, + useValue: overlayRef, + } + ], + parent: this.injector + }) + + + const pinOverlay = overlayRef.attach( + new ComponentPortal(PinOverlayComponent, undefined, injector) + ).instance; + + pinOverlay.delay = delay; + pinOverlay.mapPin = pinEvent.mapPin; + if (paths.length > 0) { + pinOverlay.routeHome = { + ...(paths[0]), + } + pinOverlay.additionalPaths = paths.slice(1); + } + + return pinOverlay; + } + + + private openPinDetails(id: string) { + this.dialog.create(PinDetailsComponent, { + data: id, + backdrop: false, + autoclose: true, + dragable: true, + }) + } + + private openCountryDetails(event: CountryEvent) { + // abort if we already have the country details open. + if (this.openedCountryDetails.find(cmp => cmp.countryCode === event.countryCode)) { + return; + } + + const ref = this.dialog.create(CountryDetailsComponent, { + data: { + name: event.countryName, + code: event.countryCode, + }, + autoclose: false, + dragable: true, + backdrop: false, + }) + const component = (ref.contentRef() as ComponentRef).instance; + + // used to track whether we highlighted a map pin + let hasPinHighlightActive = false; + + combineLatest([ + component.pinHover, + this.mapService.pins$, + ]) + .pipe( + takeUntil(ref.onClose), + ) + .subscribe(([hovered, pins]) => { + hasPinHighlightActive = hovered !== null; + + if (hovered !== null) { + this.onPinHover({ + mapPin: pins.find(p => p.pin.ID === hovered)!, + }) + this.highlightPin(hovered, true) + } else { + this.onPinHover(null); + this.clearPinHighlights(); + } + + + this.cdr.markForCheck(); + }) + + ref.onClose + .subscribe(() => { + if (hasPinHighlightActive) { + this.clearPinHighlights(); + } + + const index = this.openedCountryDetails.findIndex(cmp => cmp === component); + if (index >= 0) { + this.openedCountryDetails.splice(index, 1); + } + }) + + this.openedCountryDetails.push(component); + } + + private updateOverlayPositions() { + this.mapService.pinsMap$ + .pipe(take(1)) + .subscribe(allPins => { + this.selectedPins.forEach(pin => { + const pinObj = allPins.get(pin.mapPin.pin.ID); + if (!pinObj) { + return; + } + + pin.overlayRef.updatePosition(); + }) + }) + } + + onCountryClick(countryEvent: CountryEvent) { + this.openCountryDetails(countryEvent); + } + + onCountryHover(countryEvent: CountryEvent | null) { + if (this.countryDebounceTimer !== null) { + clearTimeout(this.countryDebounceTimer); + } + + if (!!countryEvent) { + this.hoveredCountry = { + countryCode: countryEvent.countryCode, + countryName: countryEvent.countryName, + } + this.cdr.markForCheck(); + + return; + } + + this.countryDebounceTimer = setTimeout(() => { + this.hoveredCountry = null; + this.countryDebounceTimer = null; + this.cdr.markForCheck(); + }, 200) + } + + onPinClick(pinEvent: PinEvent) { + // if the control key hold when clicking a map pin, we immediately open the + // pin details instead of the overlay. + if (pinEvent.event?.ctrlKey) { + this.openPinDetails(pinEvent.mapPin.pin.ID); + } + + const overlay = this.selectedPins.find(por => por.mapPin.pin.ID === pinEvent.mapPin.pin.ID); + if (!!overlay) { + overlay.disposeOverlay() + return; + } + + // if shiftKey was not pressed during the pinClick we dispose all active overlays that have not been + // moved by the user + if (!pinEvent.event?.shiftKey) { + this.selectedPins + .filter(overlay => !overlay.hasBeenMoved) + .forEach(selected => selected.disposeOverlay()) + } + + this.mapService.pinsMap$ + .pipe(take(1)) + .subscribe(async lm => { + const overlayComp = this.createPinOverlay(pinEvent, lm); + + // when the user wants to dispose a pin overlay (by clicking the X) we + // - make sure the pin is not highlighted anymore + // - remove the pin from the selectedPins list + // - remove lines showing the route to the home hub + overlayComp.afterDispose + .subscribe(pinID => { + this.highlightPin(pinID, false); + + const overlayIdx = this.selectedPins.findIndex(por => por.mapPin.pin.ID === pinEvent.mapPin.pin.ID); + this.selectedPins.splice(overlayIdx, 1) + + this.updatePaths() + this.cdr.markForCheck(); + }) + + // when the user hovers/leaves a pin overlay, we: + // - move the pin-overlay to the top when the user hovers it so stacking order is correct + // - (un)hightlight the pin element on the map + overlayComp.overlayHover + .subscribe(evt => { + this.highlightPin(evt.pinID, evt.type === 'enter') + + // over the overlay component to the top + if (evt.type === 'enter') { + this.selectedPins.forEach(ref => { + if (ref !== overlayComp && ref.overlayRef.hostElement) { + ref.overlayRef.hostElement.style.zIndex = '0'; + } + }) + + overlayComp.overlayRef.hostElement.style.zIndex = ''; + } + }) + + this.selectedPins.push(overlayComp) + + this.updatePaths([]); + this.cdr.markForCheck(); + }) + } + + private updatePaths(additional: Path[] = []) { + const paths = [ + ...(this.selectedPins + .reduce((list, pin) => { + if (pin.routeHome) { + list.push(pin.routeHome) + } + + return [ + ...list, + ...(pin.additionalPaths || []) + ] + }, [] as Path[])), + ...this.liveModePaths, + ...additional + ] + + this.paths = paths.map(p => { + return { + ...p, + attributes: { + class: 'lane', + ...(p.attributes || {}) + } + } + }); + + this.renderPaths(this.paths) + } + + onPinHover(pinEvent: PinEvent | null) { + if (!pinEvent) { + this.updatePaths([]); + this.onCountryHover(null); + + return; + } + + // we also emit a country hover event here to keep the country + // overlay open. + const countryName = this.mapRenderer.countryNames[pinEvent.mapPin.entity.Country] + this.onCountryHover({ + event: pinEvent.event, + countryCode: pinEvent.mapPin.entity.Country, + countryName: countryName!, + }) + + // in developer mode, we show all connected lanes of the hovered pin. + if (this.expertiseService.currentLevel === ExpertiseLevel.Developer) { + this.mapService.pinsMap$ + .pipe(take(1)) + .subscribe(lm => { + const lanes = this.getConnectedLanes(pinEvent?.mapPin, lm) + this.updatePaths(lanes); + this.cdr.markForCheck(); + }) + } + } + + /** + * Marks a process group as selected and either selects one or all exit pins + * of that group. If shiftKey is pressed during click, the ID(s) will be added + * to the list of selected pins instead of replacing it. If shiftKey is pressed + * the process group itself will NOT be displayed as selected. + * + * @private - template only + */ + selectGroup(grp: AppProfile, pin?: MapPin | null, event?: MouseEvent) { + if (!!pin) { + this.selectPins(event, of([pin.pin.ID])) + return; + } + + this.selectPins(event, this.mapService.getExitPinIDsForProfile(grp)) + } + + /** Returns a list of lines that represent the route from pin to home. */ + private getRouteHome(pin: MapPin, lm: Map, includeAllRoutes = false): Path[] { + let pinsToEval: MapPin[] = [pin]; + + // decide whether to draw all connection routes that travel through pin. + if (includeAllRoutes) { + pinsToEval = [ + ...pinsToEval, + ...Array.from(lm.values()) + .filter(p => p.pin.Route?.includes(pin.pin.ID)) + ] + } + + return pinsToEval.map(pin => ({ + id: `route-home-from-${pin.pin.ID}`, + points: (pin.pin.Route || []).map(hop => lm.get(hop)!), + attributes: { + 'in-use': 'true' + } + })); + } + + /** Returns a list of lines the represent all lanes to connected pins of pin */ + private getConnectedLanes(pin: MapPin, lm: Map): Path[] { + let result: Path[] = []; + + // add all lanes for connected hubs + Object.keys(pin.pin.ConnectedTo).forEach(target => { + const p = lm.get(target); + if (!!p) { + result.push({ + id: lineID([pin, p]), + points: [ + pin, + p + ] + }) + } + }); + + return result; + + } + + private async renderPaths(paths: Path[]) { + if (!this.mapRef) { + return; + } + + const ref = this.mapRef! + + const linesGroup: LaneGroup = this.mapRef.select("#line-group")! + + const self = this; + const renderedPaths = linesGroup.selectAll('path') + .data(paths, p => p.id); + + renderedPaths + .enter() + .append('path') + .attr('d', path => { + return self.lineFunc!(path.points) + }) + .attr("stroke-width", d => { + if (d.attributes) { + if (d.attributes['in-use']) { + return 2 / ref.zoomScale + } + } + + return 1 / ref.zoomScale; + }) + .call(sel => { + if (sel.empty()) { + return; + } + const data = sel.datum()?.attributes || {}; + Object.keys(data) + .forEach(key => { + sel.attr(key, data[key]) + }) + }) + .transition("enter-lane") + .duration(d => d.points.length * MapRendererComponent.LineAnimationDuration) + .attrTween('stroke-dasharray', tweenDashEnter) + + renderedPaths.exit() + .interrupt("enter-lane") + .transition("leave-lane") + .duration(200) + .attrTween('stroke-dasharray', tweenDashExit) + .remove(); + } + + private async renderPins(pins: MapPin[]) { + pins = pins.filter(pin => !pin.isOffline || pin.isActive); + + if (!this.mapRef) { + return + } + + const ref = this.mapRef!; + + const countriesWithNodes = new Set(); + + pins.forEach(pin => { + countriesWithNodes.add(pin.entity.Country) + }) + + const pinsGroup = ref.select('#pin-group')! + + const pinElements = pinsGroup + .selectAll('g') + .data(pins, pin => pin.pin.ID) + + const self = this; + + // add new pins + pinElements + .enter() + .append('g') + .append(d => { + const val = MapRendererComponent.MarkerSize / ref.zoomScale; + + if (d.isHome) { + const homeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'circle') + homeIcon.setAttribute('r', `${val * 1.25}`) + + return homeIcon; + } + + if (d.pin.VerifiedOwner === 'Safing') { + const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon') + polygon.setAttribute('points', `0,-${val} -${val},${val} ${val},${val}`) + + return polygon; + } + + const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle') + circle.setAttribute('r', `${val}`) + + return circle; + }) + .attr("stroke-width", d => { + if (d.isExit || self.highlightedPins.has(d.pin.ID)) { + return 2 / ref.zoomScale + } + + if (d.isHome) { + return 4.5 / ref.zoomScale + } + + return 1 / ref.zoomScale + }) + .call(selection => { + selection + .style('opacity', 0) + .attr('transform', d => 'scale(0)') + .transition('enter-marker') + /**/.duration(1000) + /**/.attr('transform', d => `scale(1)`) + /**/.style('opacity', 1) + }) + .on('click', function (e: MouseEvent) { + const pin = select(this).datum() as MapPin; + self.onPinClick({ + event: e, + mapPin: pin + }); + }) + .on('mouseenter', function (e: MouseEvent) { + const pin = select(this).datum() as MapPin; + self.onPinHover({ + event: e, + mapPin: pin, + }) + }) + .on('mouseout', function (e: MouseEvent) { + self.onPinHover(null); + }) + + // remove pins from the map that disappeared + pinElements + .exit() + .remove() + + // update all pins to their correct position and update their attributes + pinsGroup.selectAll('g') + .attr('hub-id', d => d.pin.ID) + .attr('is-home', d => d.isHome) + .attr('transform', d => `translate(${ref.projection([d.location.Longitude, d.location.Latitude])})`) + .attr('in-use', d => d.isTransit) + .attr('is-exit', d => d.isExit) + .attr('raise', d => this.highlightedPins.has(d.pin.ID)) + + // update the attributes of the country shapes + ref.worldGroup.selectAll('path') + .attr('has-nodes', d => countriesWithNodes.has(d.properties.iso_a2)) + + // get all in-use pins and raise them to the top + pinsGroup.selectAll('g[in-use=true]') + .raise() + + // finally, re-raise all pins that are highlighted + pinsGroup.selectAll('g[raise=true]') + .raise() + + const activeCountrySet = new Set(); + pins.forEach(pin => { + if (pin.isTransit) { + activeCountrySet.add(pin.pin.ID) + } + }) + + // update the in-use attributes of the country shapes + ref.worldGroup.selectAll('path') + .attr('in-use', d => activeCountrySet.has(d.properties.iso_a2)) + + this.cdr.detectChanges(); + } + + public getPinElem(pinID: string) { + if (!this.mapRef) { + return + } + + return this.mapRef.root + .select("#pin-group") + .select(`g[hub-id=${pinID}]`) + .node() + } + + public clearPinHighlights() { + if (!this.mapRef) { + return + } + + this.mapRef.root + .select('#pin-group') + .select(`g[raise=true]`) + .attr('raise', false) + + this.highlightedPins.clear(); + } + + public highlightPin(pinID: string, highlight: boolean) { + if (highlight) { + this.highlightedPins.add(pinID) + } else { + this.highlightedPins.delete(pinID); + } + + if (!this.mapRef) { + return + } + const pinElemn = this.mapRef!.root + .select("#pin-group") + .select(`g[hub-id=${pinID}]`) + .attr('raise', highlight) + + if (highlight) { + pinElemn + .raise() + } + } +} + +function lineID(l: [MapPin, MapPin]): string { + return [l[0].pin.ID, l[1].pin.ID].sort().join("-") +} + +const tweenDashEnter = function (this: SVGPathElement) { + const len = this.getTotalLength(); + const interpolate = interpolateString(`0, ${len}`, `${len}, ${len}`); + return (t: number) => { + if (t === 1) { + return '0'; + } + return interpolate(t); + } +} + +const tweenDashExit = function (this: SVGPathElement) { + const len = this.getTotalLength(); + const interpolate = interpolateString(`${len}, ${len}`, `0, ${len}`); + return (t: number) => { + if (t === 1) { + return `${len}`; + } + return interpolate(t); + } +} diff --git a/desktop/angular/src/app/pages/spn/spn.module.ts b/desktop/angular/src/app/pages/spn/spn.module.ts new file mode 100644 index 00000000..737ae25f --- /dev/null +++ b/desktop/angular/src/app/pages/spn/spn.module.ts @@ -0,0 +1,69 @@ +import { A11yModule } from '@angular/cdk/a11y'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { SfngToggleSwitchModule, SfngTooltipModule, TabModule } from '@safing/ui'; +import { SfngAppIconModule } from 'src/app/shared/app-icon'; +import { CountIndicatorModule } from 'src/app/shared/count-indicator'; +import { CountryFlagModule } from 'src/app/shared/country-flag'; +import { ExpertiseModule } from 'src/app/shared/expertise/expertise.module'; +import { SfngFocusModule } from 'src/app/shared/focus'; +import { SfngMenuModule } from 'src/app/shared/menu'; +import { CommonPipesModule } from 'src/app/shared/pipes'; +import { SpnPageComponent } from './'; +import { CountryDetailsComponent } from './country-details'; +import { CountryOverlayComponent } from './country-overlay'; +import { SpnMapLegendComponent } from './map-legend'; +import { MapRendererComponent } from './map-renderer'; +import { SpnNodeIconComponent } from './node-icon'; +import { PinDetailsComponent } from './pin-details'; +import { SpnPinListComponent } from './pin-list/pin-list'; +import { PinOverlayComponent } from './pin-overlay'; +import { SpnPinRouteComponent } from './pin-route'; +import { SPNFeatureCarouselComponent } from './spn-feature-carousel'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + CountryFlagModule, + SfngTooltipModule, + SfngMenuModule, + SfngFocusModule, + SfngAppIconModule, + SfngToggleSwitchModule, + TabModule, + A11yModule, + ExpertiseModule, + OverlayModule, + CountIndicatorModule, + FontAwesomeModule, + CommonPipesModule, + DragDropModule, + RouterModule, + ], + declarations: [ + MapRendererComponent, + PinOverlayComponent, + CountryOverlayComponent, + CountryDetailsComponent, + SpnNodeIconComponent, + SpnMapLegendComponent, + PinDetailsComponent, + SpnPinRouteComponent, + SPNFeatureCarouselComponent, + SpnPageComponent, + SpnPinListComponent, + ], + exports: [ + SpnPageComponent, + SpnPinRouteComponent, + SpnNodeIconComponent, + MapRendererComponent, + ] +}) +export class SPNModule { } diff --git a/desktop/angular/src/app/pages/spn/utils.ts b/desktop/angular/src/app/pages/spn/utils.ts new file mode 100644 index 00000000..eaeefe49 --- /dev/null +++ b/desktop/angular/src/app/pages/spn/utils.ts @@ -0,0 +1,4 @@ +import { OverlayRef } from '@angular/cdk/overlay'; +import { InjectionToken } from '@angular/core'; + +export const OVERLAY_REF = new InjectionToken('OVERLAY_REF'); diff --git a/desktop/angular/src/app/pages/support/form/index.ts b/desktop/angular/src/app/pages/support/form/index.ts new file mode 100644 index 00000000..b28d7e24 --- /dev/null +++ b/desktop/angular/src/app/pages/support/form/index.ts @@ -0,0 +1 @@ +export * from './support-form'; diff --git a/desktop/angular/src/app/pages/support/form/support-form.html b/desktop/angular/src/app/pages/support/form/support-form.html new file mode 100644 index 00000000..10685d99 --- /dev/null +++ b/desktop/angular/src/app/pages/support/form/support-form.html @@ -0,0 +1,107 @@ +
+ + +
+ +
+
+
+

{{ page?.title }}

+
+ +

+ {{ page?.prologue || page?.shortHelp }} +

+ +
+

{{ page?.repoHelp }}

+ +
+ +

Title

+
+ + Copy +
+ +
+

{{section.title}}

+
+ + Copy +
+ +
+ + +
+

Included Debug Info

+
+ +

+ The following debug information will be sent together with your report. Please check it and remove potentially sensitive + information. The debug information sent with your reports will be saved on Safing's self-hosted pastebin server + and is viewable via its created url. The data is automatically destroyed after one month. +

+
+
+ Portmaster Version: {{version}} + built on {{buildDate}} +
+ Copy +
+ +
+ +
+ + +
+
+ +
+
+

+ Related Issues + +

+
+

+ Public issues related to your title: +

+ +

+ No related issues were found. +

+ +
    +
  • + {{ issue.title }} + {{ issue.closed ? 'closed' : 'opened'}} in {{ repos[issue.repository] || issue.repository + }} by {{ issue.user }} + {{ + issue.createdAt | timeAgo + }} + +
  • +
+
+
diff --git a/desktop/angular/src/app/pages/support/form/support-form.scss b/desktop/angular/src/app/pages/support/form/support-form.scss new file mode 100644 index 00000000..7555aa08 --- /dev/null +++ b/desktop/angular/src/app/pages/support/form/support-form.scss @@ -0,0 +1,253 @@ +:host { + width: 100%; + display: flex; + flex-grow: 1; + flex-direction: column; + height: 100%; +} + +.scroll-container { + overflow: auto; + margin-right: 1rem; + display: flex; + flex-direction: row; + justify-content: center; + flex-grow: 1; + + @apply p-8; + + h3 { + opacity: .9; + font-size: 0.95rem; + } +} + +.form-wrapper { + flex-grow: 2; + + @media (min-width: 1250px) { + max-width: 800px; + } + +} + +.issue-list { + width: 400px; + + margin-left: 2rem; + + &, + ul { + overflow-y: hidden; + } + + .issue { + @apply px-4; + @apply pr-8; + @apply py-4; + @apply rounded; + @apply bg-cards-secondary; + + span { + word-break: keep-all; + } + + display : flex; + flex-direction: column; + position : relative; + cursor : pointer; + + &:not(:last-child) { + margin-bottom: 0.5rem; + } + + .meta { + @apply text-tertiary; + @apply font-normal; + opacity: .7; + font-size: 95%; + } + + &:hover { + @apply bg-cards-tertiary; + } + + fa-icon { + position: absolute; + right: calc((2rem - 12px) / 2); + top: calc(50% - 8px); // actually the half height is 6px but that looks off for the icon we're using + opacity: .3; + } + } +} + +p.prologue { + @apply mb-8; +} + +.page-title { + margin-top: 20px; + margin-bottom: 40px; + position: relative; + border-bottom: 1px solid rgba(255, 255, 255, .2); + + h1 { + position: absolute; + top: -1rem; + background-color: var(--background); + @apply pr-8; + } +} + +.repo-list { + @apply mb-8; + +} + +button { + @apply p-2; + @apply bg-buttons-dark; + @apply border; + @apply border-buttons-dark; + opacity: .4; + + &:not(:last-child) { + @apply mr-1; + } + + &:hover { + @apply bg-buttons-light; + @apply border-buttons-light; + } + + &.selected { + @apply bg-buttons-dark; + @apply border-buttons-light; + opacity: 1; + } +} + +.actions { + @apply mt-8; + @apply pb-16; + + button { + opacity: 1; + @apply bg-transparent; + + &.primary { + @apply bg-buttons-dark; + opacity: 1; + } + + &:hover { + @apply bg-buttons-light; + } + } + +} + +.debug-header { + height: 32px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + position: relative; + @apply bg-cards-primary; + @apply rounded-t; + top: 2px; +} + +textarea { + @apply px-4; + @apply py-2; + min-height: 40px; +} + +textarea, +input[type="text"].title { + @apply font-medium; + @apply border; + @apply border-cards-secondary; + @apply bg-cards-secondary; + padding-right: 4.5rem; // copy button width + + &:hover, + &:active, + &:focus { + @apply border-cards-primary; + } +} + +input[type="text"].title { + padding-left: 1rem; +} + +section { + @apply py-8; + + &:not(:first-of-type) { + @apply pt-0; + } +} + +.input-wrapper { + position: relative; + display: flex; +} + +.copy-button { + user-select: none; + position: absolute; + top: 1px; + right: 0px; + width: 4rem; + height: 31px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + @apply bg-buttons-dark; + @apply rounded-sm; + opacity: .5; + + &:hover { + opacity: .9; + } +} + +.section-help { + @apply bg-cards-primary; + @apply border-t; + @apply border-dashed; + @apply border-buttons-light; + @apply p-2; + @apply px-4; + @apply rounded-sm; + color: rgba(255, 255, 255, .6); + font-size: 0.7rem; + position: relative; + width: 100%; + display: flex; + flex-direction: column; +} + +.gh-author { + @apply mt-8; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + + .input-wrapper { + padding-top: 2px; + @apply pr-2; + } +} + +input[type="text"].missing, +textarea.missing { + @apply border-info-red; +} diff --git a/desktop/angular/src/app/pages/support/form/support-form.ts b/desktop/angular/src/app/pages/support/form/support-form.ts new file mode 100644 index 00000000..1b2e8ed1 --- /dev/null +++ b/desktop/angular/src/app/pages/support/form/support-form.ts @@ -0,0 +1,258 @@ +import { CdkScrollable } from '@angular/cdk/scrolling'; +import { Component, DestroyRef, OnInit, TrackByFunction, ViewChild, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, Router } from '@angular/router'; +import { DebugAPI } from '@safing/portmaster-api'; +import { ConfirmDialogConfig, SfngDialogService } from '@safing/ui'; +import { BehaviorSubject, Observable, of } from 'rxjs'; +import { debounceTime, mergeMap } from 'rxjs/operators'; +import { SessionDataService, StatusService } from 'src/app/services'; +import { Issue, SupportHubService } from 'src/app/services/supporthub.service'; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; +import { fadeInAnimation, fadeInListAnimation, moveInOutAnimation } from 'src/app/shared/animations'; +import { FuzzySearchService } from 'src/app/shared/fuzzySearch'; +import { SupportPage, supportTypes } from '../pages'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; +import { SupportProgressDialogComponent, TicketData, TicketInfo } from '../progress-dialog'; + +@Component({ + templateUrl: './support-form.html', + styleUrls: ['./support-form.scss'], + animations: [fadeInAnimation, moveInOutAnimation, fadeInListAnimation] +}) +export class SupportFormComponent implements OnInit { + private readonly destroyRef = inject(DestroyRef); + private readonly search$ = new BehaviorSubject(''); + private readonly integration = inject(INTEGRATION_SERVICE); + + page: SupportPage | null = null; + + debugData: string = ''; + title: string = ''; + form: { [key: string]: string } = {} + selectedRepo: string = ''; + haveGhAccount = false; + version: string = ''; + buildDate: string = ''; + titleMissing = false; + + relatedIssues: Issue[] = []; + allIssues: Issue[] = []; + repos: { [repo: string]: string } = {}; + + @ViewChild(CdkScrollable) + scrollContainer: CdkScrollable | null = null; + + trackIssue: TrackByFunction = (_: number, issue: Issue) => issue.url; + + constructor( + private route: ActivatedRoute, + private router: Router, + private uai: ActionIndicatorService, + private debugapi: DebugAPI, + private statusService: StatusService, + private dialog: SfngDialogService, + private supporthub: SupportHubService, + private searchService: FuzzySearchService, + private sessionService: SessionDataService, + ) { } + + ngOnInit() { + this.supporthub.loadIssues().subscribe(issues => { + issues = issues.reverse(); + this.allIssues = issues; + this.relatedIssues = issues; + }) + + this.search$.pipe( + takeUntilDestroyed(this.destroyRef), + debounceTime(200), + ) + .subscribe((text) => { + this.relatedIssues = this.searchService.searchList(this.allIssues, text, { + disableHighlight: true, + shouldSort: true, + isCaseSensitive: false, + minMatchCharLength: 4, + keys: [ + 'title', + 'body', + ], + }).map(res => res.item) + }) + + this.statusService.getVersions() + .subscribe(status => { + this.version = status.Core.Version; + this.buildDate = status.Core.BuildDate; + }) + + this.route.paramMap + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(params => { + const id = params.get("id") + for (let pIdx = 0; pIdx < supportTypes.length; pIdx++) { + const pageSection = supportTypes[pIdx]; + const page = pageSection.choices.find(choice => choice.type !== 'link' && choice.id === id); + if (!!page) { + this.page = page as SupportPage; + break; + } + } + + if (!this.page) { + this.router.navigate(['..']); + return; + } + this.title = ''; + this.form = {}; + this.selectedRepo = 'portmaster'; + this.debugData = ''; + this.repos = {}; + this.page.sections.forEach(section => this.form[section.title] = ''); + this.page.repositories?.forEach(repo => this.repos[repo.repo] = repo.name) + + // try to restore from session service + this.sessionService.restore(this.page.id, this); + + if (this.page.includeDebugData) { + this.debugapi.getCoreDebugInfo('github') + .subscribe({ + next: data => this.debugData = data, + error: err => this.uai.error('Failed to get Debug Data', this.uai.getErrorMessgae(err)) + }) + } + }) + } + + onModelChange() { + if (!this.page) { + return; + } + this.sessionService.save(this.page.id, this, ['title', 'form', 'selectedRepo', 'haveGhAccount']); + } + + selectRepo(repo: string) { + this.selectedRepo = repo; + this.onModelChange(); + } + + searchIssues(text: string) { + this.onModelChange(); + this.search$.next(text); + } + + copyToClipboard(what: string) { + this.integration.writeToClipboard(what) + .then(() => this.uai.success("Copied to Clipboard")) + .catch(() => this.uai.error('Failed to Copy to Clipboard')); + } + + validate(): boolean { + this.titleMissing = this.title === ''; + const valid = !this.titleMissing; + if (!valid) { + this.scrollContainer?.scrollTo({ top: 0, behavior: 'smooth' }) + } + return valid; + } + + createIssue(type: 'github' | 'private', genUrl?: boolean, email?: string) { + const ticketData: TicketData = { + repo: this.selectedRepo || '', + title: this.title, + debugInfo: this.debugData, + sections: this.page?.sections.map(section => ({ + title: section.title, + body: this.form[section.title], + })) || [], + } + + let issue: TicketInfo; + + switch (type) { + case 'github': + issue = { + type: 'github', + generateUrl: genUrl || false, + preset: this.page!.ghIssuePreset || '', + ...ticketData + }; + + break; + + case 'private': + issue = { + type: 'private', + email: email, + ...ticketData + } + + break; + } + + SupportProgressDialogComponent.open(this.dialog, issue) + .subscribe(() => { + this.sessionService.delete(this.page?.id || ''); + }); + } + + createOnGithub(genUrl?: boolean) { + if (!this.validate()) { + return; + } + + if (genUrl === undefined && this.haveGhAccount) { + genUrl = true; + } + + if (genUrl === undefined) { + this.dialog.confirm({ + canCancel: true, + caption: 'Caution', + header: 'Create Issue on GitHub', + message: 'You can easily create the issue with your own GitHub account. Or create the GitHub issue privately, but then we will have no way to communicate with you for further information.', + buttons: [ + { id: 'createWithout', text: 'Create Without Account', class: 'outline' }, + { id: 'openGithub', text: 'Use My Account' }, + ] + }) + .onAction('openGithub', () => { + this.createIssue('github', true) + }) + .onAction('createWithout', () => { + this.createIssue('github', false) + }) + return; + } + } + + openIssue(issue: Issue) { + this.integration.openExternal(issue.url); + } + + createPrivateTicket() { + if (!this.validate()) { + return; + } + + const opts: ConfirmDialogConfig = { + caption: 'Info', + canCancel: true, + header: 'How should we stay in touch?', + message: 'Please enter your email address so we can write back and forth until the issue is concluded.', + inputModel: '', + inputPlaceholder: 'Optional Email', + inputType: 'text', + buttons: [ + { id: '', class: 'outline', text: 'Cancel' }, + { id: 'create', text: 'Create Ticket' }, + ], + } + this.dialog.confirm(opts) + .onAction('create', () => { + this.createIssue('private', undefined, opts.inputModel); + }); + } + +} diff --git a/desktop/angular/src/app/pages/support/index.ts b/desktop/angular/src/app/pages/support/index.ts new file mode 100644 index 00000000..5f9360ad --- /dev/null +++ b/desktop/angular/src/app/pages/support/index.ts @@ -0,0 +1 @@ +export * from './support'; diff --git a/desktop/angular/src/app/pages/support/pages.ts b/desktop/angular/src/app/pages/support/pages.ts new file mode 100644 index 00000000..83cdcc67 --- /dev/null +++ b/desktop/angular/src/app/pages/support/pages.ts @@ -0,0 +1,175 @@ +export interface PageSections { + title?: string; + choices: SupportType[]; + style?: 'small'; +} + +export interface QuestionSection { + title: string; + help?: string; +} + +export interface SupportPage { + type?: undefined; + id: string; + title: string; + shortHelp: string; + repoHelp?: string; + prologue?: string; + epilogue?: string; + sections: QuestionSection[]; + privateTicket?: boolean; + ghIssuePreset?: string; + includeDebugData?: boolean; + repositories?: { repo: string, name: string }[]; +} + +export interface ExternalLink { + type: 'link', + url: string; + title: string; + shortHelp: string; +} + +export type SupportType = SupportPage | ExternalLink; + +export const supportTypes: PageSections[] = [ + { + title: "Resources", + choices: [ + { + type: 'link', + title: '📘 Portmaster Wiki & FAQ', + url: 'https://wiki.safing.io/?source=Portmaster', + shortHelp: 'Search the Portmaster knowledge base and FAQ.', + }, + { + type: 'link', + title: '🔖 Settings Handbook', + url: 'https://docs.safing.io/portmaster/settings?source=Portmaster', + shortHelp: 'A reference document of all Portmaster settings.' + }, + { + type: 'link', + title: '📑 Safing Blog', + url: 'https://safing.io/blog?source=Portmaster', + shortHelp: 'Read our blog posts and announcements.', + } + ] + }, + { + title: "Communities & Support", + style: 'small', + choices: [ + { + type: 'link', + title: 'Join us on Discord', + url: 'https://discord.gg/safing', + shortHelp: 'Get help from the community and our AI bot on Discord.' + }, + { + type: 'link', + title: 'Follow us on Mastodon', + url: 'https://fosstodon.org/@safing', + shortHelp: 'Get updates and privacy jokes on Mastodon.' + }, + { + type: 'link', + title: 'Follow us on Twitter', + url: 'https://twitter.com/SafingIO', + shortHelp: 'Get updates and privacy jokes on Twitter.' + }, + { + type: 'link', + title: 'Safing Support via Email', + url: 'mailto:support@safing.io', + shortHelp: 'As a subscriber, reach out to the Safing team directly.' + } + ] + }, + { + title: "Make a Report", + style: 'small', + choices: [ + { + id: "report-bug", + title: "🐞 Report a Bug", + shortHelp: "Found a bug? Report your discovery and make the Portmaster better for everyone.", + repoHelp: "Where did the bug take place?", + sections: [ + { + title: "What happened?", + help: "Describe what happened in detail" + }, + { + title: "What did you expect to happen?", + help: "Describe what you expected to happen instead" + }, + { + title: "How did you reproduce it?", + help: "Describe how to reproduce the issue" + }, + { + title: "Additional information", + help: "Provide extra details if needed" + }, + ], + includeDebugData: true, + privateTicket: true, + ghIssuePreset: "report-bug.md", + repositories: [ + { repo: 'portmaster', name: 'Portmaster Core' }, + { repo: 'portmaster-ui', name: 'User Interface' }, + { repo: 'portmaster-packaging', name: 'Packaging & Installers' }, + { repo: 'spn', name: 'SPN' }, + ] + }, + { + id: "give-feedback", + title: "💡 Suggest an Improvement", + shortHelp: "Suggest an enhancement or a new feature for Portmaster.", + repoHelp: "What would you would like to improve?", + sections: [ + { + title: "What would you like to add or change?", + }, + { + title: "Why do you and others need this?" + } + ], + includeDebugData: false, + privateTicket: true, + ghIssuePreset: "suggest-feature.md", + repositories: [ + { repo: 'portmaster', name: 'Portmaster Core' }, + { repo: 'portmaster-ui', name: 'User Interface' }, + { repo: 'portmaster-packaging', name: 'Packaging & Installers' }, + { repo: 'spn', name: 'SPN' }, + ] + }, + { + id: "compatibility-report", + title: "📝 Make a Compatibility Report", + shortHelp: "Report Portmaster in/compatibility with Linux Distros, VPN Clients or general Software.", + sections: [ + { + title: "What worked?", + help: "Describe what worked" + }, + { + title: "What did not work?", + help: "Describe what did not work in detail" + }, + { + title: "Additional information", + help: "Provide extra details if needed" + }, + ], + includeDebugData: true, + privateTicket: true, + ghIssuePreset: "report-compatibility.md", + repositories: [] // not needed with the default being "portmaster" + }, + ], + } +] diff --git a/desktop/angular/src/app/pages/support/progress-dialog/index.ts b/desktop/angular/src/app/pages/support/progress-dialog/index.ts new file mode 100644 index 00000000..0dfbf366 --- /dev/null +++ b/desktop/angular/src/app/pages/support/progress-dialog/index.ts @@ -0,0 +1 @@ +export * from './progress-dialog'; diff --git a/desktop/angular/src/app/pages/support/progress-dialog/progress-dialog.html b/desktop/angular/src/app/pages/support/progress-dialog/progress-dialog.html new file mode 100644 index 00000000..2504a56e --- /dev/null +++ b/desktop/angular/src/app/pages/support/progress-dialog/progress-dialog.html @@ -0,0 +1,114 @@ + +
+ + Status + +
+ + + + +
+ + + Uploading debug data .... + + + + + Creating GitHub issue ... + + + + + Creating private support ticket ... + +
+
+
+ + + + + + + + + + + + Ticket prepared successfully + + + + Ticket created successfully! + + + + +
+ Use the following button to open the pre-filled GitHub issue form: + +
+ +
+ +
+
+ +
+ + We successfully create the issue on GitHub for you. +
+ Use the following link to check for updates: +
+ + {{ url }} +
+ + + We will contact you as soon as possbile. + +
+ + +
+ + + + + + + + Failed to create Support Ticket + + + + + + An error occured while creating your support ticket: + + + + {{ error || 'Unknown Error' }} + +
+ + +
+ + + + + + +
diff --git a/desktop/angular/src/app/pages/support/progress-dialog/progress-dialog.ts b/desktop/angular/src/app/pages/support/progress-dialog/progress-dialog.ts new file mode 100644 index 00000000..32deb205 --- /dev/null +++ b/desktop/angular/src/app/pages/support/progress-dialog/progress-dialog.ts @@ -0,0 +1,173 @@ +import { ComponentPortal } from "@angular/cdk/portal"; +import { HttpErrorResponse } from "@angular/common/http"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, EventEmitter, OnInit, inject } from "@angular/core"; +import { SFNG_DIALOG_REF, SfngDialogRef, SfngDialogService } from "@safing/ui"; +import { Observable, map, mergeMap, of } from "rxjs"; +import { INTEGRATION_SERVICE } from "src/app/integration"; +import { SupportHubService, SupportSection } from "src/app/services"; +import { ActionIndicatorService } from "src/app/shared/action-indicator"; + +export interface TicketData { + debugInfo: string; + repo: string; + title: string; + sections: SupportSection[]; +} + +export interface GithubIssue extends TicketData { + type: 'github', + generateUrl?: boolean; + preset?: string; +} + +export interface PrivateTicket extends TicketData { + type: 'private', + email?: string, +} + +export type TicketInfo = GithubIssue | PrivateTicket; + + +@Component({ + templateUrl: './progress-dialog.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + :host { + @apply block flex flex-col gap-8 relative; + } + `, + ] +}) +export class SupportProgressDialogComponent implements OnInit { + + /** Static method to open the support-progress dialog. */ + static open(dialog: SfngDialogService, data: TicketInfo): Observable { + const ref = dialog.create(SupportProgressDialogComponent, { + data, + dragable: true, + backdrop: false, + autoclose: false, + }); + + return (ref.contentRef() as ComponentRef) + .instance + .done; + } + + + private readonly cdr = inject(ChangeDetectorRef); + private readonly supporthub = inject(SupportHubService); + private readonly uai = inject(ActionIndicatorService); + + readonly integration = inject(INTEGRATION_SERVICE); + + readonly dialogRef: SfngDialogRef = inject(SFNG_DIALOG_REF); + + /** Holds the current state of the issue-creation */ + state: '' | 'debug-info' | 'create-issue' | 'create-ticket' | 'done' | 'error' = ''; + + /** The URL to the github issue once it was created. */ + url: string = ''; + + /** The error message if one occured */ + error: string = ''; + + /** Emits once the issue has been created successfully */ + done = new EventEmitter; + + ngOnInit(): void { + this.createSupportRequest(); + } + + setState(state: typeof this['state']) { + this.state = state; + this.cdr.detectChanges(); + } + + createSupportRequest(): void { + const data = this.dialogRef.data; + let stream = of('') + + // Upload debug info + if (data.debugInfo) { + stream = new Observable((observer) => { + this.state = 'debug-info'; + this.cdr.detectChanges(); + + this.supporthub.uploadText('debug-info', data.debugInfo) + .subscribe(observer); + }) + } + + // either create on github or create a private ticket through support-hub + if (data.type === 'github') { + stream = stream.pipe( + mergeMap((url) => { + this.state = 'create-issue'; + this.cdr.detectChanges(); + + return this.supporthub.createIssue( + data.repo, + data.preset || '', + data.title, + data.sections, + url, + { + generateUrl: data.generateUrl || false + }, + ); + }) + ) + } else { + stream = stream.pipe( + mergeMap((url) => { + this.state = 'create-ticket'; + this.cdr.markForCheck(); + + return this.supporthub.createTicket( + data.repo, + data.title, + data.email || '', + data.sections, + url + ) + }), + map(() => '') + ) + } + + stream.subscribe({ + next: (url) => { + this.state = 'done'; + this.url = url; + this.cdr.markForCheck(); + + this.done.next(); + }, + + error: (err) => { + console.error("error", err); + + this.state = 'error'; + if (err instanceof HttpErrorResponse && err.error instanceof ProgressEvent) { + this.error = err.statusText; + } else { + this.error = this.uai.getErrorMessage(err); + } + + this.cdr.markForCheck(); + } + }); + } + + copyUrl() { + if (!this.url) { + return + } + + this.integration.writeToClipboard(this.url) + .then(() => this.uai.success('URL Copied To Clipboard')) + .catch(err => this.uai.error('Failed to Copy To Clipboard', this.uai.getErrorMessage(err))) + } +} diff --git a/desktop/angular/src/app/pages/support/support.html b/desktop/angular/src/app/pages/support/support.html new file mode 100644 index 00000000..2ad9eca2 --- /dev/null +++ b/desktop/angular/src/app/pages/support/support.html @@ -0,0 +1,50 @@ +
+ + +
+ +
+
+
+

{{section.title}}

+
+ +
+
+ + +

{{item.title}}

+ + +
+
+
+
+ + + +
diff --git a/desktop/angular/src/app/pages/support/support.scss b/desktop/angular/src/app/pages/support/support.scss new file mode 100644 index 00000000..fd3ddd50 --- /dev/null +++ b/desktop/angular/src/app/pages/support/support.scss @@ -0,0 +1,77 @@ +:host { + width: 100%; + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; +} + +.list-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + + .section-title { + margin-top: 20px; + margin-bottom: 40px; + position: relative; + border-bottom: 1px solid rgba(255, 255, 255, .2); + + h4 { + position: absolute; + top: -0.5rem; + background-color: var(--background); + @apply pr-8; + } + } + + .page-section { + width: 100%; + display: flex; + justify-content: stretch; + align-items: stretch; + flex-direction: column; + @apply px-4; + + @media (min-width: 1250px) { + max-width: 800px; + } + } + + .option-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-gap: 20px; + grid-auto-rows: 1fr; + + width: 100%; + margin-bottom: 20px; + + section { + @apply bg-cards-secondary; + @apply p-8; + @apply rounded; + transition: all 250ms ease-in-out; + position: relative; + cursor: pointer; + + &:hover { + @apply bg-cards-tertiary; + } + + fa-icon { + position: absolute; + top: 1rem; + right: 1rem; + opacity: .4; + } + } + + + } + + .small .option-list section { + @apply p-4; + } +} diff --git a/desktop/angular/src/app/pages/support/support.ts b/desktop/angular/src/app/pages/support/support.ts new file mode 100644 index 00000000..4bd89940 --- /dev/null +++ b/desktop/angular/src/app/pages/support/support.ts @@ -0,0 +1,97 @@ +import { Component, DestroyRef, OnInit, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Router } from '@angular/router'; +import { BehaviorSubject, combineLatest, debounceTime } from 'rxjs'; +import { Issue, SupportHubService } from 'src/app/services'; +import { fadeInAnimation, fadeInListAnimation } from 'src/app/shared/animations'; +import { FuzzySearchService } from 'src/app/shared/fuzzySearch'; +import { SupportType, supportTypes } from './pages'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; + +@Component({ + templateUrl: './support.html', + styleUrls: ['./support.scss'], + animations: [ + fadeInListAnimation, + fadeInAnimation, + ] +}) +export class SupportPageComponent implements OnInit { + // make supportTypes available in the page template. + readonly supportTypes = supportTypes; + + private readonly destroyRef = inject(DestroyRef); + private readonly integration = inject(INTEGRATION_SERVICE); + + /** @private The current search term for the FAQ entries. */ + searchFaqs = new BehaviorSubject(''); + + searchTerm: string = ''; + + /** A list of all faq entries loaded from the Support Hub */ + allFaqEntries: Issue[] = []; + + /** A list of faq entries to show */ + faqEntries: Issue[] = []; + + constructor( + private router: Router, + private searchService: FuzzySearchService, + private supportHub: SupportHubService, + ) { } + + ngOnInit(): void { + combineLatest([ + this.searchFaqs, + this.supportHub.loadIssues() + ]) + .pipe( + takeUntilDestroyed(this.destroyRef), + debounceTime(200), + ) + .subscribe(([searchTerm, allFaqEntries]) => { + this.allFaqEntries = allFaqEntries + .filter(issue => issue.labels?.includes("faq")) + .map(issue => { + return { + ...issue, + + title: issue.title.replace("FAQ: ", "") + } + }) + + if (searchTerm === '') { + this.faqEntries = [ + ...this.allFaqEntries + ] + + return; + } + + this.faqEntries = this.searchService.searchList(this.allFaqEntries, searchTerm, { + disableHighlight: true, + shouldSort: true, + isCaseSensitive: false, + minMatchCharLength: 3, + keys: [ + 'title', + 'body', + ], + }).map(res => res.item) + }) + } + + openIssue(issue: Issue) { + this.integration.openExternal(issue.url); + } + + openPage(item: SupportType) { + if (item.type === 'link') { + this.integration.openExternal(item.url); + return; + } + + this.router.navigate(['/support', item.id]); + } +} + diff --git a/desktop/angular/src/app/prompt-entrypoint/prompt-entrypoint.ts b/desktop/angular/src/app/prompt-entrypoint/prompt-entrypoint.ts new file mode 100644 index 00000000..23bf9f01 --- /dev/null +++ b/desktop/angular/src/app/prompt-entrypoint/prompt-entrypoint.ts @@ -0,0 +1,78 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnInit, TrackByFunction, inject } from "@angular/core"; +import { AppProfile, AppProfileService, PortapiService } from "@safing/portmaster-api"; +import { combineLatest, combineLatestAll, forkJoin, map, merge, mergeAll, of, switchMap } from "rxjs"; +import { ConnectionPrompt, NotificationType, NotificationsService } from "../services"; +import { SfngAppIconModule } from "../shared/app-icon"; +import { getCurrent } from '@tauri-apps/api/window'; +import { CountryFlagModule } from "../shared/country-flag"; + +interface Prompt { + prompts: ConnectionPrompt[]; + profile: AppProfile; +} + +@Component({ + standalone: true, + selector: 'app-root', + templateUrl: './prompt.html', + imports: [ + CommonModule, + SfngAppIconModule, + CountryFlagModule + ] +}) +export class PromptEntryPointComponent implements OnInit { + private readonly notificationService = inject(NotificationsService); + private readonly portapi = inject(PortapiService); + private readonly profileService = inject(AppProfileService); + + prompts: Prompt[] = []; + + trackPrompt: TrackByFunction = (_, p) => p.EventID; + trackProfile: TrackByFunction = (_, p) => p.profile._meta!.Key; + + ngOnInit(): void { + + this.notificationService + .new$ + .pipe( + map(notifs => { + return notifs.filter(n => n.Type === NotificationType.Prompt && n.EventID.startsWith("filter:prompt")) + }), + switchMap(notifications => { + const distictProfiles = new Map(); + notifications.forEach(n => { + const key = `${n.EventData!.Profile.Source}/${n.EventData!.Profile.ID}` + const arr = distictProfiles.get(key) || []; + arr.push(n); + distictProfiles.set(key, arr); + }); + + if (distictProfiles.size === 0) { + return of([]); + } + + return combineLatest(Array.from(distictProfiles.entries()).map(([key, prompts]) => forkJoin({ + profile: this.profileService.getAppProfile(key), + prompts: of(Array.from(prompts)) + }))); + }) + ) + .subscribe(result => { + this.prompts = result; + + // show the prompt now since we're ready + if (this.prompts.length) { + getCurrent()!.show(); + } + }) + } + + selectAction(prompt: ConnectionPrompt, action: string) { + prompt.SelectedActionID = action; + + this.portapi.update(prompt._meta!.Key, prompt) + .subscribe(); + } +} diff --git a/desktop/angular/src/app/prompt-entrypoint/prompt.html b/desktop/angular/src/app/prompt-entrypoint/prompt.html new file mode 100644 index 00000000..8667398e --- /dev/null +++ b/desktop/angular/src/app/prompt-entrypoint/prompt.html @@ -0,0 +1,65 @@ +
+ +
+ +

Portmaster

+
+ +
+ + + + +
+
+ + + {{ prompt.profile.Name }} + {{ prompt.profile.LinkedPath }} + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
Domain: + + + {{ prompt.EventData?.Entity?.Domain || 'N/A' }} + + +
+ +
+
IP:{{ prompt.EventData?.Entity?.IP || 'N/A' }}
+
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/desktop/angular/src/app/services/index.ts b/desktop/angular/src/app/services/index.ts new file mode 100644 index 00000000..d4b95f1d --- /dev/null +++ b/desktop/angular/src/app/services/index.ts @@ -0,0 +1,8 @@ +export { NotificationsService } from './notifications.service'; +export * from './notifications.types'; +export * from './session-data.service'; +export { StatusService } from './status.service'; +export * from './status.types'; +export * from './supporthub.service'; +export * from './ui-state.service'; + diff --git a/desktop/angular/src/app/services/notifications.service.spec.ts b/desktop/angular/src/app/services/notifications.service.spec.ts new file mode 100644 index 00000000..8789bf32 --- /dev/null +++ b/desktop/angular/src/app/services/notifications.service.spec.ts @@ -0,0 +1,354 @@ +import { TestBed } from '@angular/core/testing'; +import { WebsocketService } from '@safing/portmaster-api'; +import { MockWebSocketSubject } from '@safing/portmaster-api/testing'; +import { PartialObserver } from 'rxjs'; +import { NotificationsService } from './notifications.service'; +import { Notification, NotificationType } from './notifications.types'; + +describe('NotificationsService', () => { + let service: NotificationsService; + let mock: MockWebSocketSubject; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { + provide: WebsocketService, + useValue: MockWebSocketSubject, + } + ] + }); + service = TestBed.inject(NotificationsService); + mock = MockWebSocketSubject.lastMock!; + }); + + afterEach(() => { + mock.close(); + }) + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should allow to query for notifications', () => { + const observer = createSpyObserver(); + service.query("updates:").subscribe(observer); + + mock.expectLastMessage() + mock.expectLastMessage('type').toBe('query') + mock.expectLastMessage('query').toBe('notifications:all/updates:') + + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + type: 'ok', + data: { + ID: 'updates:core-update-available', + Message: 'Update available', + }, + key: 'notifications:all/updates:core-update-available' + }) + + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + type: 'ok', + data: { + ID: 'updates:ui-reload-required', + Message: 'UI reload required', + }, + key: 'notifications:all/updates:ui-reload-required' + }) + + // query collects all notifications using toArray + // so nothing should be nexted yet. + expect(observer.next).not.toHaveBeenCalled() + expect(observer.error).not.toHaveBeenCalled() + expect(observer.complete).not.toHaveBeenCalled() + + // finish the strea + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + type: 'done' + }) + + expect(observer.next).toHaveBeenCalledWith([ + { + ID: 'updates:core-update-available', + Message: 'Update available', + }, + { + ID: 'updates:ui-reload-required', + Message: 'UI reload required', + } + ]) + expect(observer.error).not.toHaveBeenCalled() + expect(observer.complete).toHaveBeenCalled() + }); + + describe('execute notification actions', () => { + it('should work using a notif object', () => { + let observer = createSpyObserver(); + let notif: any = { + ID: 'updates:core-update-available', + Message: 'An update is available', + Type: NotificationType.Info, + AvailableActions: [{ ID: "restart", Text: "Restart" }], + } + + service.execute(notif, "restart").subscribe(observer); + + expect(observer.error).not.toHaveBeenCalled() + + mock.expectLastMessage('type').toBe('update'); + mock.expectLastMessage('key').toBe('notifications:all/updates:core-update-available'); + mock.expectLastMessage('data').toEqual({ + ID: 'updates:core-update-available', + SelectedActionID: 'restart', + }); + + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + type: 'success' + }) + + expect(observer.next).toHaveBeenCalledWith(undefined); + expect(observer.error).not.toHaveBeenCalled(); + expect(observer.complete).toHaveBeenCalled(); + }); + + it('should throw when executing an unknown action using a notif object', () => { + let observer = createSpyObserver(); + let notif: any = { + ID: 'updates:core-update-available', + Message: 'An update is available', + Type: NotificationType.Info, + AvailableActions: [{ ID: "restart", Text: "Restart" }], + } + + service.execute(notif, "restart-with-typo").subscribe(observer); + + expect(observer.error).toHaveBeenCalled() + expect(mock.lastMessageSent).toBeUndefined(); + }); + + it('should work using a key', () => { + let observer = createSpyObserver(); + service.execute("updates:core-update-available", "restart").subscribe(observer); + + expect(observer.error).not.toHaveBeenCalled() + + mock.expectLastMessage('type').toBe('update'); + mock.expectLastMessage('key').toBe('notifications:all/updates:core-update-available'); + mock.expectLastMessage('data').toEqual({ + ID: 'updates:core-update-available', + SelectedActionID: 'restart', + }); + + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + type: 'success' + }) + + expect(observer.next).toHaveBeenCalledWith(undefined); + expect(observer.error).not.toHaveBeenCalled(); + expect(observer.complete).toHaveBeenCalled(); + }); + }) + + describe('resolving pending actions', () => { + it('should work using a notif object', () => { + let observer = createSpyObserver(); + let notif: any = { + ID: 'updates:core-update-available', + Message: 'An update is available', + Type: NotificationType.Info, + Responded: Math.round(Date.now() / 1000), + SelectedActionID: "restart", + } + + service.resolvePending(notif, 100).subscribe(observer) + + expect(observer.error).not.toHaveBeenCalled() + + mock.expectLastMessage('type').toBe('update'); + mock.expectLastMessage('key').toBe('notifications:all/updates:core-update-available'); + mock.expectLastMessage('data').toEqual({ + ID: 'updates:core-update-available', + Executed: 100, + }); + + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + type: 'success' + }) + + expect(observer.next).toHaveBeenCalledWith(undefined); + expect(observer.error).not.toHaveBeenCalled(); + expect(observer.complete).toHaveBeenCalled(); + }); + + it('should throw on an executed notification using a notif object', () => { + let observer = createSpyObserver(); + let notif: any = { + ID: 'updates:core-update-available', + Message: 'An update is available', + Type: NotificationType.Info, + SelectedActionID: 'restart', + Responded: Math.round(Date.now() / 1000), + Executed: Math.round(Date.now() / 1000), + } + + service.resolvePending(notif).subscribe(observer); + + expect(observer.error).toHaveBeenCalled() + expect(mock.lastMessageSent).toBeUndefined(); + }); + + it('should work using a key', () => { + let observer = createSpyObserver(); + service.resolvePending("updates:core-update-available", 100).subscribe(observer); + + expect(observer.error).not.toHaveBeenCalled() + + mock.expectLastMessage('type').toBe('update'); + mock.expectLastMessage('key').toBe('notifications:all/updates:core-update-available'); + mock.expectLastMessage('data').toEqual({ + ID: 'updates:core-update-available', + Executed: 100, + }); + + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + type: 'success' + }) + + expect(observer.next).toHaveBeenCalledWith(undefined); + expect(observer.error).not.toHaveBeenCalled(); + expect(observer.complete).toHaveBeenCalled(); + }); + }); + + describe('watching notifications', () => { + it('should be possible to watch for new and action-required notifs only', () => { + const observer = createSpyObserver(); + service.new$.subscribe(observer); + + let send = (msg: any) => { + mock.lastMultiplex!.next({ + id: mock.lastRequestId!, + data: msg, + type: 'ok', + key: "notifications:all/" + msg.ID, + }) + } + + let n1 = { + ID: "new-notif-1", + Message: "a new notification", + Responded: 0, + Executed: 0, + Expires: Math.round(Date.now() / 1000) + 60 * 60, + } + let n2 = { + ID: "new-notif-2", + Message: "a new notification", + Responded: 0, + Executed: 0, + Expires: 0, + AvailableActions: [{ ID: "action-id", Text: "some action" }], + } + let expired = { + ID: "new-notif-3", + Message: "a new notification", + Responded: 0, + Executed: 0, + Expires: 100, + } + let pending = { + ID: "new-notif-4", + Message: "a new notification", + Responded: Math.round(Date.now() / 1000), + Executed: 0, + SelectedActionID: "test", + } + + send(n1) + send(expired) + send(n2) + send(pending) + + expect(observer.complete).not.toHaveBeenCalled() + expect(observer.error).not.toHaveBeenCalled() + expect(observer.next).toHaveBeenCalledTimes(2) + expect(observer.next).toHaveBeenCalledWith(n1) + expect(observer.next).toHaveBeenCalledWith(n2) + }) + }) + + describe('creating notifications', () => { + it('should be possible using an object', () => { + let notification: Partial> = { + ID: 'my-awesome-notification', + AvailableActions: [ + { ID: 'action-no', Text: 'No' }, + { ID: 'force-no', Text: 'Hell No' } + ], + Message: 'Update complete, do you want to reboot?', + Persistent: true, + Type: NotificationType.Warning, + } + + let observer = createSpyObserver(); + service.create(notification).subscribe(observer); + + expect(observer.error).not.toHaveBeenCalled(); + + mock.expectLastMessage('type').toBe('create') + mock.expectLastMessage('key').toBe('notifications:all/my-awesome-notification') + mock.expectLastMessage('data').toEqual(notification); + expect(notification.Created).toBeTruthy(); + + mock.lastMultiplex!.next({ + type: 'success', + id: mock.lastRequestId!, + }) + + expect(observer.complete).toHaveBeenCalled() + expect(observer.error).not.toHaveBeenCalled() + expect(observer.next).toHaveBeenCalledWith(undefined) + }) + + it('should be possible using parameters', () => { + let observer = createSpyObserver(); + service.create('my-param-notification', 'message', NotificationType.Prompt, { + Persistent: true, + Created: 100, + }).subscribe(observer); + + expect(observer.error).not.toHaveBeenCalled(); + + mock.expectLastMessage('type').toBe('create') + mock.expectLastMessage('key').toBe('notifications:all/my-param-notification') + mock.expectLastMessage('data').toEqual({ + Type: NotificationType.Prompt, + ID: 'my-param-notification', + Message: 'message', + Created: 100, + Persistent: true, + }); + + mock.lastMultiplex!.next({ + type: 'success', + id: mock.lastRequestId!, + }) + + expect(observer.complete).toHaveBeenCalled() + expect(observer.error).not.toHaveBeenCalled() + expect(observer.next).toHaveBeenCalledWith(undefined) + + }) + }) +}); + +function createSpyObserver(): PartialObserver { + return jasmine.createSpyObj("observer", ["next", "error", "complete"]) +} diff --git a/desktop/angular/src/app/services/notifications.service.ts b/desktop/angular/src/app/services/notifications.service.ts new file mode 100644 index 00000000..b15949f2 --- /dev/null +++ b/desktop/angular/src/app/services/notifications.service.ts @@ -0,0 +1,395 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable, TrackByFunction, inject } from '@angular/core'; +import { Params, Router } from '@angular/router'; +import { PortapiService, RetryableOpts } from '@safing/portmaster-api'; +import { BehaviorSubject, Observable, combineLatest, defer, throwError } from 'rxjs'; +import { map, share, toArray } from 'rxjs/operators'; +import { environment } from 'src/environments/environment'; +import { ActionIndicatorService } from '../shared/action-indicator'; +import { Action, ActionHandler, NetqueryAction, Notification, NotificationState, NotificationType, OpenPageAction, OpenProfileAction, OpenSettingAction, OpenURLAction, PageIDs, WebhookAction } from './notifications.types'; +import { VirtualNotification } from './virtual-notification'; +import { INTEGRATION_SERVICE } from '../integration'; + +@Injectable({ + providedIn: 'root' +}) +export class NotificationsService { + private readonly integration = inject(INTEGRATION_SERVICE); + + /** + * A {@link TrackByFunction} from tracking notifications. + */ + static trackBy: TrackByFunction> = function (_: number, n: Notification) { + return n.EventID; + }; + + /** + * This object contains handler methods for all + * notification action types we currently support. + */ + private actionHandler: { + [key in Action['Type']]: (a: any) => Promise; + } = { + '': async () => { }, + 'open-url': async (a: OpenURLAction) => { + await this.integration.openExternal(a.Payload); + }, + 'open-profile': (a: OpenProfileAction) => this.router.navigate([ + '/app', ...a.Payload.split('/') + ]), + 'open-setting': (a: OpenSettingAction) => { + if (a.Payload.Profile) { + return this.router.navigate(['/app', ...a.Payload.Profile.split('/')], { + queryParams: { + setting: a.Payload.Key, + tab: 'settings' + } + }) + } + return this.router.navigate(['/settings'], { + queryParams: { + setting: a.Payload.Key + } + }) + }, + "open-page": (a: OpenPageAction) => { + let pageID: keyof typeof PageIDs | null = null; + let queryParams: Params | null = null; + + if (typeof a.Payload === 'string') { + pageID = a.Payload; + queryParams = {}; + } else { + pageID = a.Payload.id; + queryParams = a.Payload.query; + } + + const url = PageIDs[pageID]; + if (!!url) { + return this.router.navigate([url], { + queryParams, + }) + } + return Promise.reject('not yet supported'); + }, + "ui": (a: ActionHandler) => { + return a.Run(a); + }, + "netquery": (a: NetqueryAction) => { + return this.router.navigate(['/monitor'], { + queryParams: { + q: a.Payload, + } + }) + }, + "call-webhook": (a: WebhookAction) => { + let method = a.Payload.Method; + if (method === '') { + if (a.Payload.Payload !== undefined && a.Payload.Payload !== null) { + method = 'PUT' + } else { + method = 'POST' + } + } + let req = this.http.request( + method, + `${environment.httpAPI}/v1/${a.Payload.URL}`, + { + body: a.Payload.Payload, + observe: 'response', + responseType: 'arraybuffer', + } + ) + return new Promise((resolve, reject) => { + const observer = this.actionIndicator.httpObserver(); + req.subscribe({ + next: res => { + if (a.Payload.ResultAction === 'display') { + if (!!observer?.next) { + observer.next(res) + } + } + resolve(res); + }, + error: err => { + if (!!observer?.error) { + observer.error(err); + } + reject(err); + }, + }) + }) + } + }; + + // For testing purposes only + VirtualNotification = VirtualNotification; + + /** A map of virtual notifications */ + private _virtualNotifications = new Map>(); + + /* Emits all virtual notifications whenever they change */ + private _virtualNotificationChange = new BehaviorSubject[]>([]); + + /* A copy of the static trackBy function. */ + trackBy = NotificationsService.trackBy; + + /** The prefix that all notifications have */ + readonly notificationPrefix = "notifications:all/"; + + /** new$ emits new (active) notifications as they arrive */ + readonly new$: Observable[]>; + + constructor( + private portapi: PortapiService, + private router: Router, + private http: HttpClient, + private actionIndicator: ActionIndicatorService, + ) { + this.new$ = this.watchAll().pipe( + src => this.injectVirtual(src), + map(msgs => { + return msgs.filter(msg => msg.State === NotificationState.Active || !msg.State) + }), + share({ connector: () => new BehaviorSubject[]>([]) }) + ); + } + + /** + * Inject a new virtual notification. If not configured otherwise, + * the notification is automatically removed when executed. + */ + inject(notif: VirtualNotification, { autoRemove } = { autoRemove: true }) { + this._virtualNotifications.set(notif.EventID, notif); + this._virtualNotificationChange.next( + Array.from(this._virtualNotifications.values()) + ) + + if (autoRemove) { + notif.executed.subscribe({ complete: () => this.deject(notif) }); + } + } + + /** Deject (remove) a virtual notification. */ + deject(notif: VirtualNotification) { + this._virtualNotifications.delete(notif.EventID); + + this._virtualNotificationChange.next( + Array.from(this._virtualNotifications.values()) + ) + } + + /** A {@link MonoOperatorFunction} that injects all virtual observables into the source. */ + private injectVirtual(obs: Observable[]>): Observable { + return combineLatest([ + obs, + this._virtualNotificationChange, + ]).pipe( + map(([real, virtual]) => { + return [ + ...real, + ...virtual, + ] + }) + ) + } + + /** + * Watch all notifications that match a query. + * + * + * @param query The query to watch. Defaulta to all notifcations + * @param opts Optional retry configuration options. + */ + watchAll(query: string = '', opts?: RetryableOpts): Observable[]> { + return this.portapi.watchAll>(this.notificationPrefix + query, opts); + } + + /** + * Query the backend for a list of notifications. In contrast + * to {@class PortAPI} query collects all results into an array + * first which makes it convenient to be used in *ngFor and + * friends. See {@function trackNotification} for a suitable track-by + * function. + * + * @param query The search query. + */ + query(query: string): Observable[]> { + return this.portapi.query>(this.notificationPrefix + query) + .pipe( + map(value => value.data), + toArray() + ) + } + + /** + * Returns the notification by ID. + * + * @param id The ID of the notification + */ + get(id: string): Observable> { + return this.portapi.get(this.notificationPrefix + id) + } + + /** + * Execute an action attached to a notification. + * + * @param n The notification object. + * @param actionId The ID of the action to execute. + */ + execute(n: Notification, action: Action): Observable; + + /** + * Execute an action attached to a notification. + * + * @param notificationId The ID of the notification. + * @param actionId The ID of the action to execute. + */ + execute(notificationId: string, action: Action): Observable; + + // overloaded implementation of execute + execute(notifOrId: Notification | string, action: Action): Observable { + const payload: Partial> = {}; + if (typeof notifOrId === 'string') { + payload.EventID = notifOrId; + } else { + payload.EventID = notifOrId.EventID; + } + + // if it's a virtual notification we should let it handle the action + // on it's own. + if (!!this._virtualNotifications.get(payload.EventID)) { + return defer(async () => { + const notif = this._virtualNotifications.get(payload.EventID!); + if (!!notif) { + notif.selectAction(action.ID); + } + }) + } + + return defer(async () => { + try { + await this.performAction(action); + + // finally, if there's an action ID, mark the notification as resolved. + if (!!action.ID) { + payload.SelectedActionID = action.ID; + const key = this.notificationPrefix + payload.EventID; + await this.portapi.update(key, payload).toPromise(); + } + } catch (err: any) { + const msg = this.actionIndicator.getErrorMessgae(err); + this.actionIndicator.error('Internal Error', 'Failed to perform action: ' + msg) + } + }) + } + + async performAction(action: Action) { + // if there's an action type defined execute the handler. + if (!!action.Type) { + const handler = this.actionHandler[action.Type] as (a: Action) => Promise; + if (!!handler) { + console.log(action); + await handler(action); + } else { + this.actionIndicator.error('Internal Error', 'Cannot handle action type ' + action.Type) + } + } + } + + /** + * Resolve a pending notification execution. + * + * @param n The notification object to resolve the pending execution. + * @param time optional The time at which the pending execution took place + */ + resolvePending(n: Notification, time?: number): Observable; + + /** + * Resolve a pending notification execution. + * + * @param n The notification ID to resolve the pending execution. + * @param time optional The time at which the pending execution took place + */ + resolvePending(n: string, time?: number): Observable; + + // overloaded implementation of resolvePending. + resolvePending(notifOrID: Notification | string, time: number = (Math.round(Date.now() / 1000))): Observable { + const payload: Partial> = {}; + if (typeof notifOrID === 'string') { + payload.EventID = notifOrID; + } else { + payload.EventID = notifOrID.EventID; + if (notifOrID.State === NotificationState.Executed) { + return throwError(`Notification ${notifOrID.EventID} already executed`); + } + } + + payload.State = NotificationState.Responded; + const key = this.notificationPrefix + payload.EventID + return this.portapi.update(key, payload); + } + + /** + * Delete a notification. + * + * @param n The notification to delete. + */ + delete(n: Notification): Observable; + + /** + * Delete a notification. + * + * @param n The notification to delete. + */ + delete(id: string): Observable; + + // overloaded implementation of delete. + delete(notifOrId: Notification | string): Observable { + return this.portapi.delete(typeof notifOrId === 'string' ? notifOrId : notifOrId.EventID); + } + + /** + * Create a new notification. + * + * @param n The notification to create. + */ + create(n: Partial>): Observable; + + /** + * Create a new notification. + * + * @param id The ID of the notificaiton. + * @param message The default message of the notificaiton. + * @param type The notification type + * @param args Additional arguments for the notification. + */ + create(id: string, message: string, type: NotificationType, args?: Partial>): Observable; + + // overloaded implementation of create. + create(notifOrId: Partial> | string, message?: string, type?: NotificationType, args?: Partial>): Observable { + if (typeof notifOrId === 'string') { + notifOrId = { + ...args, + EventID: notifOrId, + State: NotificationState.Active, + Message: message, + Type: type, + } as Notification; // it's actual Partial but that's fine. + } + + if (!notifOrId.EventID) { + return throwError(`Notification ID is required`); + } + + if (!notifOrId.Message) { + return throwError(`Notification message is required`); + } + + if (typeof notifOrId.Type !== 'number') { + return throwError(`Notification type is required`); + } + + return this.portapi.create(this.notificationPrefix + notifOrId.EventID, notifOrId); + } +} diff --git a/desktop/angular/src/app/services/notifications.types.ts b/desktop/angular/src/app/services/notifications.types.ts new file mode 100644 index 00000000..7ddf7029 --- /dev/null +++ b/desktop/angular/src/app/services/notifications.types.ts @@ -0,0 +1,205 @@ +import { getEnumKey, IntelEntity, Record } from '@safing/portmaster-api'; + +/** + * BaseAction defines a user selectable action and can + * be attached to a notification. Once selected, + * the action's ID is set as the SelectedActionID + * of the notification. + */ +export interface BaseAction { + // ID uniquely identifies the action. It's safe to + // use ID to select a localizable template to use + // instead of the Text property. If Type is set + // to None the ID may be empty, signifying that this + // action is merely to dismiss the notification. + ID: string; + // Text is the (default) text for the action label. + Text: string; +} + +export interface GenericAction extends BaseAction { + Type: ''; +} + +export interface OpenURLAction extends BaseAction { + Type: 'open-url'; + Payload: string; +} + +export interface OpenPageAction extends BaseAction { + Type: 'open-page'; + Payload: keyof typeof PageIDs | { + id: keyof typeof PageIDs, + query: { + [key: string]: string, + } + }; +} + +export interface NetqueryAction extends BaseAction { + Type: 'netquery'; + Payload: string; +} + +/** + * PageIDs holds a list of pages that can be opened using + * the OpenPageAction. + */ +export const PageIDs = { + 'monitor': '/monitor', + 'support': '/support', + 'settings': '/settings', + 'apps': '/app/overview', + 'spn': '/spn', +} + +export interface OpenSettingAction extends BaseAction { + Type: 'open-setting'; + Payload: { + Key: string; + Profile?: string; + } +} + +export interface OpenProfileAction extends BaseAction { + Type: 'open-profile'; + Payload: string; +} + +export interface WebhookAction extends BaseAction { + Type: 'call-webhook'; + Payload: { + Method: string; + URL: string; + Payload: any; + ResultAction: 'ignore' | 'display'; + } +} + +export interface ActionHandler extends BaseAction { + Type: 'ui' + Run: (vn: T) => Promise; + Payload: T; +} + +export type Action = GenericAction + | OpenURLAction + | OpenPageAction + | OpenSettingAction + | OpenProfileAction + | WebhookAction + | NetqueryAction + | ActionHandler; + +/** All action types that perform in-application routing. */ +export const routingActions = new Set([ + 'open-page', + 'open-profile', + 'open-setting' +]) + +/** + * Available types of notifications. Notification + * types are mainly for filtering and style related + * decisions. + */ +export enum NotificationType { + // Info is an informational message only. + Info = 0, + // Warning is a warning message. + Warning = 1, + // Prompt asks the user for a decision. + Prompt = 2, + // Error is for error notifications and module + // failure status. + Error = 3, +} + +export interface ConnectionPromptData { + Profile: { + ID: string; + LinkedPath: string; + Source: 'local'; + }; + Entity: IntelEntity; +} + +/** + * Returns a string representation of the notifcation type. + * + * @param val The notifcation type + */ +export function getNotificationTypeString(val: NotificationType): string { + return getEnumKey(NotificationType, val) +} + +/** + * Each notification can be in one of six different states + * that inform the client on how to handle the notification. + */ +export enum NotificationState { + // Active describes a notification that is active, no expired and, + // if actions are available, still waits for the user to select an + // action. + Active = "active", + // Responded describes a notification where the user has already + // selected which action to take but that action is still to be + // performed. + Responded = "responded", + // Responded describes a notification where the user has already + // selected which action to take but that action is still to be + // performed. + Executed = "executed", + // Invalid is a UI-only state that is used when the state of a + // notification is unknown. + Invalid = "invalid", +} + +export interface Notification extends Record { + // EventID is used to identify a specific notification. It consists of + // the module name and a per-module unique event id. + // The following format is recommended: + // : + EventID: string; + // GUID is a unique identifier for each notification instance. That is + // two notifications with the same EventID must still have unique GUIDs. + // The GUID is mainly used for system (Windows) integration and is + // automatically populated by the notification package. Average users + // don't need to care about this field. + GUID: string; + // Type is the notification type. It can be one of Info, Warning or Prompt. + Type: NotificationType; + // Message is the default message shown to the user if no localized version + // of the notification is available. Note that the message should already + // have any paramerized values replaced. Message may be formatted using + // markdown. + Message: string; + // Title holds a short notification title that quickly informs the user + // about the type of notification. + Title: string; + // Category holds an informative category for the notification and is mainly + // used for presentation purposes. + Category: string; + // EventData contains an additional payload for the notification. This payload + // may contain contextual data and may be used by a localization framework + // to populate the notification message template. + // If EventData implements sync.Locker it will be locked and unlocked together with the + // notification. Otherwise, EventData is expected to be immutable once the + // notification has been saved and handed over to the notification or database package. + EventData: T | null; + // Expires holds the unix epoch timestamp at which the notification expires + // and can be cleaned up. + // Users can safely ignore expired notifications and should handle expiry the + // same as deletion. + Expires: number; + // State describes the current state of a notification. See State for + // a list of available values and their meaning. + State: NotificationState; + // AvailableActions defines a list of actions that a user can choose from. + AvailableActions: Action[]; + // SelectedActionID is updated to match the ID of one of the AvailableActions + // based on the user selection. + SelectedActionID: string; +} + +export type ConnectionPrompt = Notification; diff --git a/desktop/angular/src/app/services/package.json b/desktop/angular/src/app/services/package.json new file mode 100644 index 00000000..a4382915 --- /dev/null +++ b/desktop/angular/src/app/services/package.json @@ -0,0 +1,3 @@ +{ + "sideEffects": false +} diff --git a/desktop/angular/src/app/services/session-data.service.ts b/desktop/angular/src/app/services/session-data.service.ts new file mode 100644 index 00000000..cb88ea91 --- /dev/null +++ b/desktop/angular/src/app/services/session-data.service.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; + +/** + * SessionDataService is used to store transient data + * that are only important as long as the application is + * being used. Those data are not presisted and are + * removed once the application is restarted. + */ +@Injectable({ + providedIn: 'root' +}) +export class SessionDataService { + private data = new Map(); + private stream = new BehaviorSubject(undefined); + + /** Set sets a value in the session data service */ + set(key: string, value: T): void { + this.data.set(key, value); + } + + get(key: string): T | null; + get(key: string, def: T): T; + + /** Get retrieves a value from the session data service */ + get(key: string, def?: any): any { + const value = this.data.get(key); + if (value !== undefined) { + return value; + } + + if (def !== undefined) { + return def; + } + return null; + } + + watch(key: string): Observable; + watch(key: string, def: T): Observable; + + /** Watch a key for changes to it's identity. */ + watch(key: string, def?: any): Observable { + return this.stream + .pipe( + map(() => this.get(key, def)), + distinctUntilChanged() + ); + } + + delete(key: string): T | null { + let value = this.get(key); + if (value !== null) { + this.data.delete(key); + } + return value; + } + + save(id: string, model: M, keys: K[]) { + let copy: Partial = {}; + keys.forEach(key => copy[key] = model[key]); + this.set(id, copy); + } + + restore(id: string, model: M) { + let copy: Partial | null = this.get(id); + if (copy === null) { + return; + } + Object.assign(model, copy); + } +} diff --git a/desktop/angular/src/app/services/status.service.spec.ts b/desktop/angular/src/app/services/status.service.spec.ts new file mode 100644 index 00000000..34ce8a6f --- /dev/null +++ b/desktop/angular/src/app/services/status.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { StatusService } from './status.service'; + +describe('StatusService', () => { + let service: StatusService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(StatusService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/desktop/angular/src/app/services/status.service.ts b/desktop/angular/src/app/services/status.service.ts new file mode 100644 index 00000000..c52b73c7 --- /dev/null +++ b/desktop/angular/src/app/services/status.service.ts @@ -0,0 +1,95 @@ +import { Injectable, TrackByFunction } from '@angular/core'; +import { PortapiService, RetryableOpts, SecurityLevel, WatchOpts, trackById } from '@safing/portmaster-api'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { filter, map, repeat, share, toArray } from 'rxjs/operators'; +import { CoreStatus, Subsystem, VersionStatus } from './status.types'; + +@Injectable({ + providedIn: 'root' +}) +export class StatusService { + /** + * A {@link TrackByFunction} from tracking subsystems. + */ + static trackSubsystem: TrackByFunction = trackById; + readonly trackSubsystem = StatusService.trackSubsystem; + + readonly statusPrefix = "runtime:" + readonly subsystemPrefix = this.statusPrefix + "subsystems/" + + /** + * status$ watches the global core status. It's mutlicasted using a BehaviorSubject so new + * subscribers will automatically get the latest version while only one subscription + * to the backend is held. + */ + readonly status$: Observable = this.portapi.qsub(`runtime:system/status`) + .pipe( + repeat({ delay: 2000 }), + map(reply => reply.data), + share({ connector: () => new BehaviorSubject(null) }), + filter(value => value !== null), + ) as Observable; // we filtered out the null values but we cannot make that typed with RxJS. + + constructor(private portapi: PortapiService) { } + + /** Returns the currently available versions for all resources. */ + getVersions(): Observable { + return this.portapi.get('core:status/versions') + } + + /** + * Selectes a new security level. SecurityLevel.Off means that + * the auto-pilot should take over. + * + * @param securityLevel The security level to select + */ + selectLevel(securityLevel: SecurityLevel): Observable { + return this.portapi.update(`runtime:system/security-level`, { + SelectedSecurityLevel: securityLevel, + }); + } + + + /** + * Loads the current status of a subsystem. + * + * @param name The ID of the subsystem + */ + getSubsystemStatus(id: string): Observable { + return this.portapi.get(this.subsystemPrefix + id); + } + + /** + * Loads the current status of all subsystems matching idPrefix. + * If idPrefix is an empty string all subsystems are returned. + * + * @param idPrefix An optional ID prefix to limit the returned subsystems + */ + querySubsystem(idPrefix: string = ''): Observable { + return this.portapi.query(this.subsystemPrefix + idPrefix) + .pipe( + map(reply => reply.data), + toArray(), + ) + } + + /** + * Watch a subsystem for changes. Completes when the subsystem is + * deleted. See {@method PortAPI.watch} for more information. + * + * @param id The ID of the subsystem to watch. + * @param opts Additional options for portapi.watch(). + */ + watchSubsystem(id: string, opts?: WatchOpts): Observable { + return this.portapi.watch(this.subsystemPrefix + id, opts); + } + + /** + * Watch for subsystem changes + * + * @param opts Additional options for portapi.sub(). + */ + watchSubsystems(opts?: RetryableOpts): Observable { + return this.portapi.watchAll(this.subsystemPrefix, opts); + } +} diff --git a/desktop/angular/src/app/services/status.types.ts b/desktop/angular/src/app/services/status.types.ts new file mode 100644 index 00000000..f5188366 --- /dev/null +++ b/desktop/angular/src/app/services/status.types.ts @@ -0,0 +1,132 @@ +import { getEnumKey, Record, ReleaseLevel, SecurityLevel } from '@safing/portmaster-api'; + +export interface CaptivePortal { + URL: string; + IP: string; + Domain: string; +} + +export enum ModuleStatus { + Off = 0, + Error = 1, + Warning = 2, + Operational = 3 +} + +/** + * Returns a string represetnation of the module status. + * + * @param stat The module status to translate + */ +export function getModuleStatusString(stat: ModuleStatus): string { + return getEnumKey(ModuleStatus, stat) +} + +export enum OnlineStatus { + Unknown = 0, + Offline = 1, + Limited = 2, // local network only, + Portal = 3, + SemiOnline = 4, + Online = 5, +} + +/** + * Converts a online status value to a string. + * + * @param stat The online status value to convert + */ +export function getOnlineStatusString(stat: OnlineStatus): string { + return getEnumKey(OnlineStatus, stat) +} + +export interface Threat { + ID: string; + Name: string; + Description: string; + AdditionalData: T; + MitigationLevel: SecurityLevel; + Started: number; + Ended: number; +} + +export interface CoreStatus extends Record { + ActiveSecurityLevel: SecurityLevel; + SelectedSecurityLevel: SecurityLevel; + ThreatMitigationLevel: SecurityLevel; + OnlineStatus: OnlineStatus; + Threats: Threat[]; + CaptivePortal: CaptivePortal; +} + +export enum FailureStatus { + Operational = 0, + Hint = 1, + Warning = 2, + Error = 3 +} + +/** + * Returns a string representation of a failure status value. + * + * @param stat The failure status value. + */ +export function getFailureStatusString(stat: FailureStatus): string { + return getEnumKey(FailureStatus, stat) +} + +export interface Module { + Enabled: boolean; + FailureID: string; + FailureMsg: string; + FailureStatus: FailureStatus; + Name: string; + Status: ModuleStatus; +} + +export interface Subsystem extends Record { + ConfigKeySpace: string; + Description: string; + ExpertiseLevel: string; + FailureStatus: FailureStatus; + ID: string; + Modules: Module[]; + Name: string; + ReleaseLevel: ReleaseLevel; + ToggleOptionKey: string; +} + +export interface CoreVersion { + BuildDate: string; + BuildHost: string; + BuildOptions: string; + BuildSource: string; + BuildUser: string; + Commit: string; + License: string; + Name: string; + Version: string; +} + +export interface ResourceVersion { + Available: boolean; + BetaRelease: boolean; + Blacklisted: boolean; + StableRelease: boolean; + VersionNumber: string; +} + +export interface Resource { + ActiveVersion: ResourceVersion | null; + Identifier: string; + SelectedVersion: ResourceVersion; + Versions: ResourceVersion[]; +} + +export interface VersionStatus extends Record { + Channel: string; + Core: CoreVersion; + Resources: { + [key: string]: Resource + } +} diff --git a/desktop/angular/src/app/services/supporthub.service.ts b/desktop/angular/src/app/services/supporthub.service.ts new file mode 100644 index 00000000..9b8cfd7c --- /dev/null +++ b/desktop/angular/src/app/services/supporthub.service.ts @@ -0,0 +1,82 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { environment } from 'src/environments/environment'; + +export interface SupportSection { + title: string; + body: string; +} + +export interface Issue { + title: string; + body: string; + createdAt: CreatedAt; + repository: string; + url: string; + user: string; + closed?: boolean; + labels: string[]; +} + +@Injectable({ providedIn: 'root' }) +export class SupportHubService { + constructor(private http: HttpClient) { } + + loadIssues(): Observable { + interface LoadIssuesResponse { + issues: Issue[]; + } + return this.http.get(`${environment.supportHub}/api/v1/issues`) + .pipe(map(res => res.issues.map(issue => ({ + ...issue, + createdAt: new Date(issue.createdAt), + })).reverse())); + } + + /** Uploads content under name */ + uploadText(name: string, content: string): Observable { + interface UploadResponse { + urls: { + [key: string]: string[]; + } + } + const blob = new Blob([content], { type: 'text/plain' }); + const data = new FormData(); + data.set("file", blob, name); + + return this.http.post(`${environment.supportHub}/api/v1/upload`, data) + .pipe(map(res => res.urls['file'][0])); + } + + /** Create github issue */ + createIssue(repo: string, preset: string, title: string, sections: SupportSection[], debugInfoUrl?: string, opts?: { + generateUrl: boolean, + }): Observable { + interface CreateIssueResponse { + url: string; + } + const req = { + title, + sections, + debugInfoUrl + } + let params = new HttpParams(); + if (!!opts?.generateUrl) { + params = params.set('generate-url', '') + } + return this.http.post(`${environment.supportHub}/api/v1/issues/${repo}/${preset}`, req, { params }).pipe(map(r => r.url)) + } + + createTicket(repoName: string, title: string, email: string, sections: SupportSection[], debugInfoUrl?: string): Observable { + const req = { + title, + sections, + debugInfoUrl, + email, + repoName, + } + return this.http.post(`${environment.supportHub}/api/v1/ticket`, req) + } +} diff --git a/desktop/angular/src/app/services/ui-state.service.ts b/desktop/angular/src/app/services/ui-state.service.ts new file mode 100644 index 00000000..25ad4d09 --- /dev/null +++ b/desktop/angular/src/app/services/ui-state.service.ts @@ -0,0 +1,57 @@ +import { Injectable } from "@angular/core"; +import { PortapiService, Record } from '@safing/portmaster-api'; +import { Observable, of } from "rxjs"; +import { catchError, map, switchMap } from "rxjs/operators"; +import { SortTypes } from './../shared/network-scout/network-scout'; + +export interface UIState extends Record { + hideExitScreen?: boolean; + introScreenFinished?: boolean; + netscoutSortOrder: SortTypes; +} + +const defaultState: UIState = { + hideExitScreen: false, + introScreenFinished: false, + netscoutSortOrder: SortTypes.static +} + +@Injectable({ providedIn: 'root' }) +export class UIStateService { + constructor(private portapi: PortapiService) { } + + uiState(): Observable { + const key = 'core:ui/v1'; + return this.portapi.get(key) + .pipe( + catchError(err => of(defaultState)), + map(state => { + (Object.keys(defaultState) as (keyof UIState)[]) + .forEach(key => { + if (state[key] === undefined) { + (state as any)[key] = defaultState[key]! + } + }) + + return state + }) + ) + } + + saveState(state: UIState): Observable { + const key = 'core:ui/v1'; + return this.portapi.create(key, state); + } + + set(key: K, value: V): Observable { + return this.uiState() + .pipe( + map(state => { + state[key] = value + + return state; + }), + switchMap(newState => this.saveState(newState)) + ); + } +} diff --git a/desktop/angular/src/app/services/virtual-notification.ts b/desktop/angular/src/app/services/virtual-notification.ts new file mode 100644 index 00000000..592d886a --- /dev/null +++ b/desktop/angular/src/app/services/virtual-notification.ts @@ -0,0 +1,85 @@ +import { RecordMeta } from '@safing/portmaster-api'; +import { BehaviorSubject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { ActionHandler, Notification, NotificationState, NotificationType } from './notifications.types'; + +export class VirtualNotification implements Notification { + readonly AvailableActions: ActionHandler[]; + readonly Category: string; + readonly EventData: T | null; + readonly GUID: string = ''; // TODO(ppacher): should we fake it? + readonly Expires: number; + readonly _meta: RecordMeta; + + get State() { + if (this.SelectedActionID === '') { + return NotificationState.Active + } + + return NotificationState.Executed + } + + get SelectedActionID() { + return this._selectedAction.getValue(); + } + + /** Emits as soon as the user selects one of the notification actions. */ + get executed() { + return this._selectedAction.pipe( + filter(action => action !== '') + ); + } + + /* Used to emit the selected action */ + private _selectedAction = new BehaviorSubject(''); + + /** + * Select and execute the action by ID. + * + * @param aid The ID of the action to execute. + */ + selectAction(aid: string) { + this._selectedAction.next(aid); + this._meta.Modified = new Date().valueOf() / 1000; + + const action = this.AvailableActions.find(a => a.ID === aid); + if (!!action) { + action.Run(action.Payload); + } + } + + constructor( + public readonly EventID: string, + public readonly Type: NotificationType, + public readonly Title: string, + public readonly Message: string, + { + AvailableActions, + EventData, + Category, + Expires, + }: { + AvailableActions?: ActionHandler[]; + EventData?: T | null; + Category?: string, + Expires?: number, + } = {} + ) { + this.AvailableActions = AvailableActions || []; + this.EventData = EventData || null; + this.Category = Category || ''; + this.Expires = Expires || 0; + + this._meta = { + Created: new Date().valueOf() / 1000, + Deleted: 0, + Expires: this.Expires, + Modified: new Date().valueOf() / 1000, + Key: `notifications:all/${EventID}`, + } + } + + dispose() { + this._selectedAction.complete(); + } +} diff --git a/desktop/angular/src/app/shared/action-indicator/action-indicator.module.ts b/desktop/angular/src/app/shared/action-indicator/action-indicator.module.ts new file mode 100644 index 00000000..2f5dafcd --- /dev/null +++ b/desktop/angular/src/app/shared/action-indicator/action-indicator.module.ts @@ -0,0 +1,13 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { IndicatorComponent } from "./indicator"; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + IndicatorComponent, + ] +}) +export class ActionIndicatorModule { } diff --git a/desktop/angular/src/app/shared/action-indicator/action-indicator.service.ts b/desktop/angular/src/app/shared/action-indicator/action-indicator.service.ts new file mode 100644 index 00000000..143168e1 --- /dev/null +++ b/desktop/angular/src/app/shared/action-indicator/action-indicator.service.ts @@ -0,0 +1,284 @@ +import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { Injectable, InjectionToken, Injector, isDevMode } from '@angular/core'; +import { interval, PartialObserver, Subject } from 'rxjs'; +import { take, takeUntil } from 'rxjs/operators'; +import { IndicatorComponent } from './indicator'; + +export interface ActionIndicator { + title: string; + message?: string; + status: 'info' | 'success' | 'error'; + timeout?: number; +} + +export const ACTION_REF = new InjectionToken('ActionIndicatorRef') +export class ActionIndicatorRef implements ActionIndicator { + title: string; + message?: string; + status: 'info' | 'success' | 'error'; + timeout?: number; + + onClose = new Subject(); + onCloseReplace = new Subject(); + + constructor(opts: ActionIndicator, private _overlayRef: OverlayRef) { + this.title = opts.title; + this.message = opts.message; + this.status = opts.status; + this.timeout = opts.timeout; + } + + close() { + this._overlayRef.detach(); + this.onClose.next(); + this.onClose.complete(); + } +} + +@Injectable({ providedIn: 'root' }) +export class ActionIndicatorService { + private _activeIndicatorRef: ActionIndicatorRef | null = null; + + constructor( + private _injector: Injector, + private overlay: Overlay, + ) { } + + /** + * Returns an observer that parses the HTTP API response + * and shows a success/error action indicator. + */ + httpObserver(successTitle?: string, errorTitle?: string): PartialObserver> { + return { + next: resp => { + let msg = this.getErrorMessgae(resp) + if (!successTitle) { + successTitle = msg; + msg = ''; + } + this.success(successTitle || '', msg) + }, + error: err => { + let msg = this.getErrorMessgae(err); + if (!errorTitle) { + errorTitle = msg; + msg = ''; + } + this.error(errorTitle || '', msg); + } + } + } + + info(title: string, message?: string, timeout?: number) { + this.create({ + title, + message: this.ensureMessage(message), + timeout, + status: 'info' + }) + } + + error(title: string, message?: string | any, timeout?: number) { + this.create({ + title, + message: this.ensureMessage(message), + timeout, + status: 'error' + }) + } + + success(title: string, message?: string, timeout?: number) { + this.create({ + title, + message: this.ensureMessage(message), + timeout, + status: 'success' + }) + } + + /** + * Creates a new user action indicator. + * + * @param msg The action indicator message to show + */ + async create(msg: ActionIndicator) { + if (!!this._activeIndicatorRef) { + this._activeIndicatorRef.onCloseReplace.next(); + await this._activeIndicatorRef.onClose.toPromise(); + } + + const cfg = new OverlayConfig({ + scrollStrategy: this.overlay + .scrollStrategies.noop(), + positionStrategy: this.overlay + .position() + .global() + .bottom('2rem') + .left('5rem'), + }); + const overlayRef = this.overlay.create(cfg); + + const ref = new ActionIndicatorRef(msg, overlayRef); + ref.onClose.pipe(take(1)).subscribe(() => { + if (ref === this._activeIndicatorRef) { + this._activeIndicatorRef = null; + } + }) + + // close after the specified time our (or 5000 seconds). + const timeout = msg.timeout || 5000; + interval(timeout).pipe( + takeUntil(ref.onClose), + take(1), + ).subscribe(() => { + ref.close(); + }) + + const injector = this.createInjector(ref); + const portal = new ComponentPortal( + IndicatorComponent, + undefined, + injector + ); + this._activeIndicatorRef = ref; + overlayRef.attach(portal); + } + + /** + * Creates a new dependency injector that provides msg as + * ACTION_MESSAGE. + */ + private createInjector(ref: ActionIndicatorRef): Injector { + return Injector.create({ + providers: [ + { + provide: ACTION_REF, + useValue: ref, + } + ], + parent: this._injector, + }) + } + + /** + * Tries to extract a meaningful error message from msg. + */ + private ensureMessage(msg: string | any): string | undefined { + if (msg === undefined || msg === null) { + return undefined; + } + + if (msg instanceof HttpErrorResponse) { + return msg.message; + } + + if (typeof msg === 'string') { + return msg; + } + + if (typeof msg === 'object') { + if ('message' in msg) { + return msg.message; + } + if ('error' in msg) { + return this.ensureMessage(msg.error); + } + if ('toString' in msg) { + return msg.toString(); + } + } + + return JSON.stringify(msg); + } + + /** + * Coverts an untyped body received by the HTTP API to a string. + */ + private stringifyBody(body: any): string { + if (typeof body === 'string') { + return body; + } + + if (body instanceof ArrayBuffer) { + return new TextDecoder('utf-8').decode(body); + } + + if (typeof body === 'object') { + return this.ensureMessage(body) || ''; + } + console.error('unsupported body', body); + + return ''; + } + + /** + * @deprecated use the version without a typo ... + */ + getErrorMessgae(resp: HttpResponse | HttpErrorResponse | Error): string { + return this.getErrorMessage(resp) + } + + /** + * Parses a HTTP or HTTP Error response and returns a + * message that can be displayed to the user. + */ + getErrorMessage(resp: HttpResponse | HttpErrorResponse | Error): string { + try { + let body: string | null = null; + + if (typeof resp === 'string') { + return resp + } + + if (resp instanceof Error) { + return resp.message; + } + + if (resp instanceof HttpErrorResponse) { + // A client-side or network error occured. + if (resp.error instanceof Error) { + body = resp.error.message; + } else { + body = this.stringifyBody(resp.error); + } + + if (!!body) { + body = body[0].toLocaleUpperCase() + body.slice(1) + return body + } + } + + + if (resp instanceof HttpResponse) { + let msg = ''; + const ct = resp.headers.get('content-type') || ''; + + body = this.stringifyBody(resp.body); + + if (/application\/json/.test(ct)) { + if (!!body) { + msg = body; + } + } else if (/text\/plain/.test(ct)) { + msg = body; + } + + // Make the first letter uppercase + if (!!msg) { + msg = msg[0].toLocaleUpperCase() + msg.slice(1) + return msg; + } + } + + console.error(`Unexpected error type`, resp) + + return `Unknown error: ${resp}` + + } catch (err: any) { + console.error(err) + return `Unknown error: ${resp}` + } + } +} diff --git a/desktop/angular/src/app/shared/action-indicator/index.ts b/desktop/angular/src/app/shared/action-indicator/index.ts new file mode 100644 index 00000000..e243b7a0 --- /dev/null +++ b/desktop/angular/src/app/shared/action-indicator/index.ts @@ -0,0 +1,2 @@ +export * from './action-indicator.service'; +export * from './action-indicator.module'; diff --git a/desktop/angular/src/app/shared/action-indicator/indicator.html b/desktop/angular/src/app/shared/action-indicator/indicator.html new file mode 100644 index 00000000..5691cd1e --- /dev/null +++ b/desktop/angular/src/app/shared/action-indicator/indicator.html @@ -0,0 +1,30 @@ + +
+ + + +
+
+ + + +
+
+
+

{{ ref.title }}

+ + + + + +
+ + {{ ref.message }} + +
+
diff --git a/desktop/angular/src/app/shared/action-indicator/indicator.scss b/desktop/angular/src/app/shared/action-indicator/indicator.scss new file mode 100644 index 00000000..6189798c --- /dev/null +++ b/desktop/angular/src/app/shared/action-indicator/indicator.scss @@ -0,0 +1,74 @@ +:host { + box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.75); + @apply bg-gray-200; + @apply p-4; + @apply rounded; + position: relative; + width: 20rem; + display: flex; + cursor: pointer; + border-left: 2px solid transparent; + + + .icon { + display: flex; + align-items: flex-start; + flex-shrink: 1; + margin-right: 1rem; + padding-top: 2px; + } + + &.error { + @apply border-yellow; + + .icon { + @apply text-yellow + } + } + + .indicator-content { + display: flex; + flex-direction: column; + align-items: flex-start; + + h1 { + font-size: 0.85rem; + font-weight: 500; + margin-bottom: 0; + } + + .message, + h1 { + flex-shrink: 0; + text-overflow: ellipsis; + } + + .message { + font-size: 0.7rem; + flex-grow: 1; + opacity: .5; + + span { + display: block; + height: 100%; + word-break: keep-all; + } + } + + .close-icon { + position: absolute; + top: 1rem; + right: 1rem; + opacity: .7; + cursor: pointer; + + &:hover { + opacity: 1; + } + } + + h1~.message { + margin-top: .5rem; + } + } +} diff --git a/desktop/angular/src/app/shared/action-indicator/indicator.ts b/desktop/angular/src/app/shared/action-indicator/indicator.ts new file mode 100644 index 00000000..2f41d2c6 --- /dev/null +++ b/desktop/angular/src/app/shared/action-indicator/indicator.ts @@ -0,0 +1,78 @@ +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, HostListener, Inject, OnInit } from '@angular/core'; +import { takeUntil } from 'rxjs/operators'; +import { ActionIndicatorRef, ACTION_REF } from './action-indicator.service'; + +@Component({ + templateUrl: './indicator.html', + styleUrls: ['./indicator.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('slideIn', [ + state('void', style({ + opacity: 0, + transform: 'translateY(32px)' + })), + + state('showing', style({ + opacity: 1, + transform: 'translateY(0px)' + })), + + state('replace', style({ + transform: 'translateY(0px) rotate(-3deg)', + zIndex: -100, + })), + + transition('showing => replace', animate('10ms cubic-bezier(0, 0, 0.2, 1)')), + transition('void => *', animate('220ms cubic-bezier(0, 0, 0.2, 1)')), + + transition('showing => void', animate('220ms cubic-bezier(0, 0, 0.2, 1)', style({ + opacity: 0, + transform: 'translateX(-100%)' + }))), + + transition('replace => void', animate('220ms cubic-bezier(0, 0, 0.2, 1)', style({ + opacity: 0, + transform: 'translateY(-64px) rotate(-3deg)' + }))) + ]) + ] +}) +export class IndicatorComponent implements OnInit { + constructor( + @Inject(ACTION_REF) + public ref: ActionIndicatorRef, + public cdr: ChangeDetectorRef, + ) { } + + @HostBinding('@slideIn') + state = 'showing'; + + @HostBinding('class.error') + isError = this.ref.status === 'error'; + + @HostListener('click') + closeIndicator() { + this.ref.close(); + } + + @HostListener('@slideIn.done', ['$event']) + onAnimationDone() { + if (this.state === 'replace') { + this.ref.close(); + } + } + + ngOnInit() { + this.ref.onCloseReplace + .pipe( + takeUntil(this.ref.onClose), + ) + .subscribe(state => { + this.state = 'replace'; + this.cdr.detectChanges(); + }) + } +} + diff --git a/desktop/angular/src/app/shared/animations.ts b/desktop/angular/src/app/shared/animations.ts new file mode 100644 index 00000000..32989217 --- /dev/null +++ b/desktop/angular/src/app/shared/animations.ts @@ -0,0 +1,111 @@ +import { animate, query, stagger, style, transition, trigger } from '@angular/animations'; + +export const fadeInAnimation = trigger( + 'fadeIn', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateY(-5px)' }), + animate('120ms cubic-bezier(0, 0, 0.2, 1)', + style({ opacity: 1, transform: 'translateY(0px)' })) + ] + ), + ] +); + +export const fadeOutAnimation = trigger( + 'fadeOut', + [ + transition( + ':leave', + [ + style({ opacity: 1, transform: 'translateY(0px)' }), + animate('120ms cubic-bezier(0, 0, 0.2, 1)', + style({ opacity: 0, transform: 'translateY(-5px)' })) + ] + ), + ] +); + +export const fadeInListAnimation = trigger( + 'fadeInList', + [ + transition(':enter, * => 0, * => -1', []), + transition(':increment', [ + query(':enter', [ + style({ opacity: 0 }), + stagger(5, [ + animate('300ms ease-out', style({ opacity: 1 })), + ]), + ], { optional: true }) + ]), + ] +) + +export const moveInOutLeftAnimation = trigger( + 'moveInOutLeft', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateX(-100%)' }), + animate('.1s ease-in', + style({ opacity: 1, transform: 'translateX(0%)' })) + ] + ), + transition( + ':leave', + [ + style({ opacity: 1, 'z-index': -100 }), + animate('.1s ease-out', + style({ opacity: 0, transform: 'translateX(-100%)' })) + ] + ) + ] +) + + +export const moveInOutAnimation = trigger( + 'moveInOut', + [ + transition( + ':enter', + [ + style({ opacity: 0, transform: 'translateX(100%)' }), + animate('.2s ease-in', + style({ opacity: 1, transform: 'translateX(0%)' })) + ] + ), + transition( + ':leave', + [ + style({ opacity: 1 }), + animate('.2s ease-out', + style({ opacity: 0, transform: 'translateX(100%)' })) + ] + ) + ] +) + +export const moveInOutListAnimation = trigger( + 'moveInOutList', + [ + transition(':enter, * => 0, * => -1', []), + transition(':increment', [ + query(':enter', [ + style({ opacity: 0, transform: 'translateX(100%)' }), + stagger(50, [ + animate('200ms ease-out', style({ opacity: 1, transform: 'translateX(0%)' })), + ]), + ], { optional: true }) + ]), + transition(':decrement', [ + query(':leave', [ + stagger(-50, [ + animate('200ms ease-out', style({ opacity: 0, transform: 'translateX(100%)' })), + ]), + ], { optional: true }) + ]), + ] +) diff --git a/desktop/angular/src/app/shared/app-icon/app-icon-resolver.ts b/desktop/angular/src/app/shared/app-icon/app-icon-resolver.ts new file mode 100644 index 00000000..0a7547ad --- /dev/null +++ b/desktop/angular/src/app/shared/app-icon/app-icon-resolver.ts @@ -0,0 +1,118 @@ +import { Injectable, inject, isDevMode } from "@angular/core"; +import { AppProfile, AppProfileService, deepClone } from "@safing/portmaster-api"; +import { firstValueFrom, map, switchMap } from "rxjs"; +import { INTEGRATION_SERVICE, ProcessInfo } from "src/app/integration"; +import * as parseDataURL from 'data-urls'; + +export abstract class AppIconResolver { + abstract resolveIcon(profile: AppProfile): void; +} + +@Injectable() +export class DefaultIconResolver extends AppIconResolver { + private integration = inject(INTEGRATION_SERVICE); + private profileService = inject(AppProfileService); + + private pendingResolvers = new Map>(); + + resolveIcon(profile: AppProfile): void { + const key = `${profile.Source}/${profile.ID}`; + + // if there's already a promise in flight, abort. + if (this.pendingResolvers.has(key)) { + if (isDevMode()) { + console.log(`[icon:${profile.Name}] loading icon already in progress ...`) + } + + return; + } + + let promise = new Promise((resolve) => { + this.profileService + .getProcessesByProfile(profile) + .pipe( + map(processes => { + // if we there are no running processes for this profile, + // we try to find the icon based on the information stored in + // the profile. + let info: ProcessInfo[] = [{ + execPath: profile.LinkedPath, + cmdline: profile.PresentationPath, + pid: -1, + matchingPath: profile.PresentationPath, + }] + + processes?.forEach(process => { + // BUG: Portmaster sometimes runs a null entry, skip it here. + if (!process) { + return; + } + + // insert at the beginning since the process data might reveal + // better results than the profile one. + info.splice(0, 0, { + execPath: process.Path, + cmdline: process.CmdLine, + pid: process.Pid, + matchingPath: process.MatchingPath, + }) + }) + + return info; + }) + ).subscribe(async (processInfos) => { + for (const info of processInfos) { + try { + await this.loadAndSaveIcon(info, profile); + + // success, abort now + resolve(); + return; + } catch (err) { + // continue using the next one + } + } + + // we failed to find an icon, still resolve the promise here + // because nobody actually cares .... + resolve(); + }) + }); + this.pendingResolvers.set(key, promise); + + promise.finally(() => this.pendingResolvers.delete(key)); + } + + private async loadAndSaveIcon(info: ProcessInfo, profile: AppProfile): Promise { + const icon = await this.integration.getAppIcon(info); + + const dataURL = parseDataURL(icon); + if (!dataURL) { + throw new Error("invalid data url"); + } + const blob = new Blob([dataURL.body], { + type: dataURL.mimeType.essence, + }) + + const body = await blob.arrayBuffer(); + + const save$ = this.profileService + .setProfileIcon(body, blob.type) + .pipe(switchMap(result => { + // save the profile icon + profile = deepClone(profile); + profile.Icons = [ + ...(profile.Icons || []), + { + Value: result.filename, + Type: 'api', + Source: 'ui' + } + ]; + + return this.profileService.saveProfile(profile) + })); + + await firstValueFrom(save$); + } +} diff --git a/desktop/angular/src/app/shared/app-icon/app-icon.html b/desktop/angular/src/app/shared/app-icon/app-icon.html new file mode 100644 index 00000000..bc81164d --- /dev/null +++ b/desktop/angular/src/app/shared/app-icon/app-icon.html @@ -0,0 +1,9 @@ + + {{letter}} + + + + + diff --git a/desktop/angular/src/app/shared/app-icon/app-icon.module.ts b/desktop/angular/src/app/shared/app-icon/app-icon.module.ts new file mode 100644 index 00000000..939cac43 --- /dev/null +++ b/desktop/angular/src/app/shared/app-icon/app-icon.module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { AppIconComponent } from "./app-icon"; +import { AppIconResolver, DefaultIconResolver } from "./app-icon-resolver"; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: [ + AppIconComponent, + ], + exports: [ + AppIconComponent, + ], + providers: [ + { + provide: AppIconResolver, + useClass: DefaultIconResolver, + } + ] +}) +export class SfngAppIconModule { } diff --git a/desktop/angular/src/app/shared/app-icon/app-icon.scss b/desktop/angular/src/app/shared/app-icon/app-icon.scss new file mode 100644 index 00000000..159cce02 --- /dev/null +++ b/desktop/angular/src/app/shared/app-icon/app-icon.scss @@ -0,0 +1,28 @@ +:host { + border-radius: 50%; + user-select: none; + + height: var(--app-icon-size, 25px); + width: var(--app-icon-size, 25px); + flex-shrink: 0; + @apply mr-2; + + display: inline-flex; + justify-content: center; + align-items: center; +} + +span, +img { + @apply text-primary; + @apply font-medium; + @apply rounded-full; + text-shadow: rgba(0, 0, 0, .8) 0px 0px 1px; + + font-size: calc(var(--app-icon-size, 25px) / 6 * 4); +} + +img { + width: 100%; + height: 100%; +} diff --git a/desktop/angular/src/app/shared/app-icon/app-icon.ts b/desktop/angular/src/app/shared/app-icon/app-icon.ts new file mode 100644 index 00000000..f013f4e8 --- /dev/null +++ b/desktop/angular/src/app/shared/app-icon/app-icon.ts @@ -0,0 +1,312 @@ +import { Min } from './../../../../dist-lib/safing/portmaster-api/lib/netquery.service.d'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + HostBinding, + Inject, + Input, + OnDestroy, + OnInit, + SkipSelf, + inject, +} from '@angular/core'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { + AppProfileService, + PORTMASTER_HTTP_API_ENDPOINT, + PortapiService, + Record, + deepClone, +} from '@safing/portmaster-api'; +import { Subscription, map, of, throwError } from 'rxjs'; +import { catchError, switchMap } from 'rxjs/operators'; +import { INTEGRATION_SERVICE, ProcessInfo } from 'src/app/integration'; +import { AppIconResolver } from './app-icon-resolver'; + +// Interface that must be satisfied for the profile-input +// of app-icon. +export interface IDandName { + // ID of the profile. + ID?: string; + + // Source is the source of the profile. + Source?: string; + + // Name of the profile. + Name: string; +} + +// Some icons we don't want to show on the UI. +// Note that this works on a best effort basis and might +// start breaking with updates to the built-in icons... +const iconsToIngore = [ + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABU0lEQVRYhe2WTUrEQBCF36i4ctm4FsdTKF5AEFxL0knuILgQXAy4ELxDfgTXguAFRG/hDXKCAbtcOB3aSVenMjPRTb5NvdCE97oq3QQYGflnJlbc3T/QXxrfXF9NAGBraKPTk2Nvtey4D1l8OUiIo8ODX/Xt/cMfQCk1SAAi8upWgLquWy8rpbB7+yk2m8+mYvNWAAB4fnlt9MX5WaP397ZhCPgygCFa1IUmwJifCgB5nrMBtdbhAK6pi9QcALIs8+5c1AEOqTmwZge4EUjNiQhpmjbarcvaG4AbgcTcUhSFfwFAHMfhABxScwBIkgRA9wnwBgiOQGBORCjLkl2PoigcgB2BwNzifmi97wEOqTkRoaoqdr2zA9wIJOYWrTW785VPQR+WO2B3vdYIpBBRc9Qkp2Cw/4GVR+BjPpt23u19tUXUgU2aBzuQPz5J8oyMjGyUb9+FOUOmulVPAAAAAElFTkSuQmCC', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAACLElEQVR4nO2av07DMBDGP1DFxtaFmbeg6gtUqtQZtU3yDkgMSAxIDEi8Q/8gMVdC4m1YYO0TMNQspErdOG3Md25c7rc0st3E353v7EsLKIqiKIqiKMq/5MRueHx6NoeYSCjubm82NJ8eaiISdDtX6HauKq9tWsFmF4DPr6+d1zalBshG18RpNYfJy+tW21GFgA+lK6DdboeeBwVjyvO3qx1wGGC5XO71wCYZykc8QEqCZ/cfjNs4+X64rOz3FQ/sMMDi7R2Dfg+Lt/eN9kG/tzX24rwFA8AYYGXM+nr9aQADs9mG37FWW3HsqqBhMpnsFFRGkiTOvkoD5ELLBNtIiLcdmGXZ5jP/4Pkc2i4gIb5KRl3xrnbaQSiEeN8QGI/Hzj5aDgjh+SzLaJ7P4eWAiJZ9EVoIhBA/nU695uYdAnUI4fk0TUvbXeP3gZcDhMS7CLIL1DsHyIv3DYHRaOTs44YAZD2fpik9EfIOQohn2Rch5wBZ8bPZzOObfwiBurWAtOftoqaO511jaSEgJd4FQzwgmAQlxPuGwHA4dPbJ1QICnk+ShOb5HJlaoOHLvgi/FhAUP5/P9xpbteRtyDlA1vN2UVPH8+K7gJR45/MI4gHyK7HYxANsA7BuVvkcnniAXAtIwxYPRPTboIR4IBIDMMSL7wIhYZbF0RmgsS9EQtDY1+L5r7esCUrGvA3xHBCfeIBkgBjEi+0CMYsHHDmg7N9UiqIoiqIoiqIcFT++NKIXgDvowAAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAABqUlEQVRYhe2XP2rDMBSHfymhU0dDD5BbJOQCgUDmEv+7Q6FDoUOgQ6F3cJxC50Agt+nSrD5BBr8OqVyrtfWkl8ShoG+SjJE+/95DwoDH4/nf9NTg+eWVLinym8eH+x4AXF1i8/FoiPFoaBwr+p3bAfjc7dixQhNMw7szatmTvb1XY00wCILOZYjIONcEi6JoXSgIAlw/fYhF9ouBsxzQ0IPrzRaz6QTrzbZ6NptOqvHtTR8EQklAWQIl4WdOQEkEqsaHefm9b5Zl7IfEcWwWVDJ1Ke0rHeXqmaRpeljDIrlWQQ5XufreNglGUWQW5EoslQOAJEm0uagHuRJL5YgIy+Wycc06bIIcEjmFStCUnPGYASxKLJQDYJVgGIZmQZsSS+SAv0eIKblWQQ6pHBEhz3N2fTZBrsQSOYVK0JQc24N2JXaXA2CV4Hw+NwtySOUA/QixvU1kPSiQIyKsViv2vaMTlMgpoihik2N7kEMqB6AxwXpiVlfduSAi7Qix7cGL/DS5XHWdC7rIAY4l3i8GTk1+zLsKpwS7lnMS7ErOeMzU/0c9Ho/nNHwBdUH2gB9vJRsAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAByElEQVRYhe1WQUoDQRCsmSh4CAreo3/w4CdE8JirLzCKGhRERPBqfISQx3j0BcaDJxHNRWS7PWRmtmdmJ9mNiSuYOmyYbOiqruoeAizw36G6p0e3WulOHeTE1NO/Qb6zu1f4qZXuqLPuMV9d38xbQyEuL86ha2EWWJKHfr+P4XAIAGg2m2i32wCA7fsXPH9kABjMgHkADP87cW6tNvCwvzG2biRAvpAYvH+54mCAmUcvmI0Yq4nM74DBG02sGwlIgqigS/ZEgdkcrSAuVbpUBEyjTiP7JSkDzKZrdo+xdSMBKas4y4K8befSiVxcLnR83UhACtYBV9TOgbBbOX4TF2YZQZY5Yi9/MYwkXQjy/3EEtjp7LgQzAeOUVSo0zCACcgOnwjUEC2LE7kxApS0AGFRgP4vZ8M5VBaQjoNGKuQ20Q2ney8Gr0H0kIAU7hK4zYiPCJxtFZYRMIyAdAQWrFgyicMSfj4oCkheRmQFyIoq2IRcy9T2QhNmCfN/FVcwMBSWu4XlsQUZe5tZmZW0HBXGU4o4FpCJorS3j6fXTEOVdUrgNApvrK9UFpPB4vlWq2DSo/S+Z6p4c9rRuHNRBTsR3dfAu8LfwDdGgu25Uax8RAAAAAElFTkSuQmCC', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAByUlEQVRYhe1WQUoDQRCs2UTwEBS8R//gwU+I4DFXX2AENRgQEcGr8RFCHuPRFxgPnkQ0F9Ht9rAzsz0zO8luTFzB1GHDZENXdVX3EGCJ/w7VO+3eJKrZrYOc+GuQ/Ab57t5+4Weiml111jvmy6vrRWsoxMV5H0ktzAJNeRgOhxiPxwCAVquFTqcDANi5e8bTewqAwQzoB8BwvxPn9loD9webE+sGAuQLidHbpy0OBpg5e8GsxRhNpH8HjF5pat1AQBREBV2yIwrM+mgEcanSpSJgyjoN7JekDDDrrtk+JtYNBMSs4jT18jadSydycbnQyXUDATEYB2xRMwfCbmX5dVyYZwRpaomd/MUwknTBy//HEZjq7LjgzQS0U0ap0DCHCMgOnPLXECyIEbozBZW2AGBQgf0sZsM5VxUQj4CyFbMbaIZSv5eDV6H7QEAMZghtZ8RahEuWRaWFzCIgHgF5q+YNonDEnY+KAqIXkZ4BsiKKtiEXMvM9EIXegnzfxVXMDAUlruFFbEFKTubGZmVsB3lxlOIOBcQiaK+v4PHlQxPlXZK/DQJbG6vVBcTw0N8uVWwW1P6XTPVOjgZJ0jisg5yIb+vgXeJv4RvrxrtwzfCUqAAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVRYhe2Wu28cVRTGf+fcuzZeY0NCUqTgD3C8mzRU0KDQEgmqoBSGhoKWAlFEKYyQAAkrTRRqOpCQkOhAkUCio4D4IYSAzkkKB+wQbLy7c8+hmNmd2WecDQoFfNJodGfn7vc4Z84M/I9/GfLeB1cutdqH7zxSUli9fOntd4EsmrVXL1xcodVqAf6PEl37+AveWDk/dP78s08vA1eBvSgSDnd3bs49DJGKICIg+dod3J3XXn6Ogz9+49WXnu07F1gA9mOWJRqNBrNRcJ8mAQF8ZHYyuBYhI/DlV9cBAqARnBAj2agdjwARoBaETnK+/eY7NMwfaaPZPueefwaA73+4MfKeM80GAC+8+QkA19cukCQOC+ga1zDPR1//jIgjWhzBEQWNBupoNESdldNn2dm5w/FjT/SIpkEcvLAwX0PUQRwNXQGOBCvXoVpxZ31jc2ICEwWY+1y19AvzEQr3GgAtiLUUo8F690tB5DhC3sgiw800f2p/fAJ/tTtoyMOo1yOqnscdnINOIqNDO+vQbrdwMTRWEnBhfXNyAvOn9qmfOBgvwKxwC9TnAskTN3f32PnzHi1robEbv6HFUVGQJ+AOIvkQgL4U6icOqC9OSKCKu4cH/HT7Nh3P0GiEWkEcc+LBEhylB+qL+ywe+328gGrFNre3kWiE6EjsOi5EqPVS6EGEZrOJW0JVR5KMIy8TqCjQmlUcl7GLlvGrlgLcYWNzY2ICk1CUoFSgtdRPHAwtYteQeimUCuDsmebEMX7l3Pv3E1BCY+lUgqNaFZJ663ID3Fh/6ARKhFrqNVq15lVy1dRP1FjGRaZ6lQwnEKqkw+Si/QLMATwnHxhA7o65k2UJM0NwanOP30dATAPkhmjlmuYiuhCcja0fR7prNhqA4W5Fjwz3ydBTEGLZaKoV99p13y8AnGZjeeT4dfd8LrnnCYyoUQTQQsGtW7/y+tPnR7oZxPb2LywvncRd2dzaGnnP6aUlzBLJvKt1tIAsObUAF195kZ2dO0cSsLx0EgAz6yWQO3aSGeZOJ8swS5gNj+c+AeYwE4QgxlPHF6nNzkBKpGQ4EGMAnSksOGCA41nisJP/eTfuVIjAHQRCCITiPaPjBAC0kwMKMkvW7vuJTgZQffSkOBRCLqeL0cN4PKLA6trah2/FGB97wL05oSohKCEEzMBSRkpp4gf+3d3dq+SOTIAZ4Enyz+QwjYgpkIB7wF6RIxGo8eAJTgsDOpB/jP+38TcKdstukjAxWQAAAABJRU5ErkJggg==', +]; + +const profilesToIgnore = ['local/_unidentified', 'local/_unsolicited']; + +@Component({ + selector: 'app-icon', + templateUrl: './app-icon.html', + styleUrls: ['./app-icon.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AppIconComponent implements OnInit, OnDestroy { + private sub = Subscription.EMPTY; + private initDone = false; + + private resovler = inject(AppIconResolver); + + /** @private The data-URL for the app-icon if available */ + src: SafeUrl | string = ''; + + /** The profile for which to show the app-icon */ + @Input() + set profile(p: IDandName | null | undefined | string) { + if (typeof p === 'string') { + const parts = p.split("/") + p = { + Source: parts[0], + ID: parts[1], + Name: '', + } + } + + if (!!this._profile && !!p && this._profile.ID === p.ID) { + // skip if this is the same profile + return; + } + + this._profile = p || null; + + if (this.initDone) { + this.updateView(); + } + } + get profile(): IDandName | null | undefined { + return this._profile; + } + private _profile: IDandName | null = null; + + /** isIgnoredProfile is set to true if the profile is part of profilesToIgnore */ + isIgnoredProfile = false; + + /** If not icon is available, this holds the first - uppercased - letter of the app - name */ + letter: string = ''; + + /** @private The background color of the component, based on icon availability and generated by ID */ + @HostBinding('style.background-color') + color: string = 'var(--text-tertiary)'; + + constructor( + private profileService: AppProfileService, + private changeDetectorRef: ChangeDetectorRef, + private portapi: PortapiService, + // @HostBinding() is not evaluated in our change-detection run but rather + // checked by the parent component during updateRenderer. + // Since we want the background color to change immediately after we set the + // src path we need to tell the parent (which ever it is) to update as wel. + @SkipSelf() private parentCdr: ChangeDetectorRef, + private sanitzier: DomSanitizer, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string + ) { } + + /** Updates the view of the app-icon and tries to find the actual application icon */ + private requestedAnimationFrame: number | null = null; + private updateView(skipIcon = false) { + if (this.requestedAnimationFrame !== null) { + cancelAnimationFrame(this.requestedAnimationFrame); + } + + this.requestedAnimationFrame = requestAnimationFrame(() => { + this.__updateView(); + }) + } + + ngOnInit(): void { + this.updateView(); + this.initDone = true; + } + + private __updateView(skipIcon = false) { + this.requestedAnimationFrame = null; + + const p = this.profile; + const sourceAndId = this.getIDAndSource(); + + if (!!p && sourceAndId !== null) { + let idx = 0; + for (let i = 0; i < (p.ID || p.Name).length; i++) { + idx += (p.ID || p.Name).charCodeAt(i); + } + + const combinedID = `${sourceAndId[0]}/${sourceAndId[1]}`; + this.isIgnoredProfile = profilesToIgnore.includes(combinedID); + + this.updateLetter(p); + + if (!this.isIgnoredProfile) { + this.color = AppColors[idx % AppColors.length]; + } else { + this.color = 'transparent'; + } + + if (!skipIcon) { + this.tryGetSystemIcon(p); + } + + } else { + this.isIgnoredProfile = false; + this.color = 'var(--text-tertiary)'; + } + + this.changeDetectorRef.markForCheck(); + this.parentCdr.markForCheck(); + } + + private updateLetter(p: IDandName) { + if (p.Name !== '') { + if (p.Name[0] === '<') { + // we might get the name with search-highlighting which + // will then include tags. If the first character is a < + // make sure to strip all HTML tags before getting [0]. + this.letter = p.Name.replace( + /( |<([^>]+)>)/gi, + '' + )[0].toLocaleUpperCase(); + } else { + this.letter = p.Name[0]; + } + + this.letter = this.letter.toLocaleUpperCase(); + } else { + this.letter = '?'; + } + } + + getIDAndSource(): [string, string] | null { + if (!this.profile) { + return null; + } + + let id = this.profile.ID; + if (!id) { + return null; + } + + // if there's a source ID only holds the profile ID + if (!!this.profile.Source) { + return [this.profile.Source, id]; + } + + // otherwise, ID likely contains the source + let [source, ...rest] = id.split('/'); + if (rest.length > 0) { + return [source, rest.join('/')]; + } + + // id does not contain a forward-slash so we + // assume the source is local + return ['local', id]; + } + + /** + * Tries to get the application icon form the system. + * Requires the app to be running in the electron wrapper. + */ + private tryGetSystemIcon(p: IDandName) { + const sourceAndId = this.getIDAndSource(); + if (sourceAndId === null) { + return; + } + + this.sub.unsubscribe(); + + this.sub = this.profileService + .watchAppProfile(sourceAndId[0], sourceAndId[1]) + .pipe( + switchMap((profile) => { + this.updateLetter(profile); + + if (!!profile.Icons?.length) { + const firstIcon = profile.Icons[0]; + + console.log(`profile ${profile.Name} has icon of from source ${firstIcon.Source} stored in ${firstIcon.Type}`) + + switch (firstIcon.Type) { + case 'database': + return this.portapi + .get(firstIcon.Value) + .pipe( + map((result) => { + return result.iconData; + }) + ); + + case 'api': + return of(`${this.httpAPI}/v1/profile/icon/${firstIcon.Value}`); + + default: + console.error(`Icon type ${firstIcon.Type} not yet supported`); + } + } + + this.resovler.resolveIcon(profile); + + // return an empty icon here. If the resolver manages to find an icon + // the profle will get updated and we'll run again here. + return of(''); + }) + ) + .subscribe({ + next: (icon) => { + if (iconsToIngore.some((i) => i === icon)) { + icon = ''; + } + if (icon !== '') { + this.src = this.sanitzier.bypassSecurityTrustUrl(icon); + this.color = 'unset'; + } else { + this.src = ''; + this.color = + this.color === 'unset' ? 'var(--text-tertiary)' : this.color; + } + this.changeDetectorRef.detectChanges(); + this.parentCdr.markForCheck(); + }, + error: (err) => console.error(err), + }); + } + + ngOnDestroy(): void { + this.sub.unsubscribe(); + } +} + +export const AppColors: string[] = [ + 'rgba(244, 67, 54, .7)', + 'rgba(233, 30, 99, .7)', + 'rgba(156, 39, 176, .7)', + 'rgba(103, 58, 183, .7)', + 'rgba(63, 81, 181, .7)', + 'rgba(33, 150, 243, .7)', + 'rgba(3, 169, 244, .7)', + 'rgba(0, 188, 212, .7)', + 'rgba(0, 150, 136, .7)', + 'rgba(76, 175, 80, .7)', + 'rgba(139, 195, 74, .7)', + 'rgba(205, 220, 57, .7)', + 'rgba(255, 235, 59, .7)', + 'rgba(255, 193, 7, .7)', + 'rgba(255, 152, 0, .7)', + 'rgba(255, 87, 34, .7)', + 'rgba(121, 85, 72, .7)', + 'rgba(158, 158, 158, .7)', + 'rgba(96, 125, 139, .7)', +]; diff --git a/desktop/angular/src/app/shared/app-icon/index.ts b/desktop/angular/src/app/shared/app-icon/index.ts new file mode 100644 index 00000000..90675ee7 --- /dev/null +++ b/desktop/angular/src/app/shared/app-icon/index.ts @@ -0,0 +1,2 @@ +export { AppIconComponent } from './app-icon'; +export { SfngAppIconModule } from './app-icon.module'; diff --git a/desktop/angular/src/app/shared/config/basic-setting/basic-setting.html b/desktop/angular/src/app/shared/config/basic-setting/basic-setting.html new file mode 100644 index 00000000..11e864a8 --- /dev/null +++ b/desktop/angular/src/app/shared/config/basic-setting/basic-setting.html @@ -0,0 +1,69 @@ + + + + + + + + + {{opt.Name}} + + + + + + {{opt}} + + + + + + +
+ + + {{ unit }} + +
+
+
+ + +
+ + + {{ unit }} + +
+ + + + + + + + + + +
+
diff --git a/desktop/angular/src/app/shared/config/basic-setting/basic-setting.scss b/desktop/angular/src/app/shared/config/basic-setting/basic-setting.scss new file mode 100644 index 00000000..0bb87370 --- /dev/null +++ b/desktop/angular/src/app/shared/config/basic-setting/basic-setting.scss @@ -0,0 +1,28 @@ +label { + @apply text-sm; +} + +input[type="checkbox"] { + float: right; + user-select: none; +} + +.input-container { + display: block; + position: relative; + font-size: 0.75rem; + + input { + font-size: inherit; + } + + .suffix { + user-select: none; + position: absolute; + left: 0; + top: calc(50% - 0.55rem); + padding-left: 0.3rem; + color: #aaa; + font: inherit; + } +} diff --git a/desktop/angular/src/app/shared/config/basic-setting/basic-setting.ts b/desktop/angular/src/app/shared/config/basic-setting/basic-setting.ts new file mode 100644 index 00000000..82433fb8 --- /dev/null +++ b/desktop/angular/src/app/shared/config/basic-setting/basic-setting.ts @@ -0,0 +1,333 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { DOCUMENT } from '@angular/common'; +import { AfterViewChecked, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Inject, Input, Output, ViewChild } from '@angular/core'; +import { AbstractControl, ControlValueAccessor, NgModel, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms'; +import { BaseSetting, ExternalOptionHint, OptionType, parseSupportedValues, SettingValueType, WellKnown } from '@safing/portmaster-api'; + +@Component({ + selector: 'app-basic-setting', + templateUrl: './basic-setting.html', + styleUrls: ['./basic-setting.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => BasicSettingComponent), + }, + { + provide: NG_VALIDATORS, + multi: true, + useExisting: forwardRef(() => BasicSettingComponent), + } + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class BasicSettingComponent> implements ControlValueAccessor, Validator, AfterViewChecked { + /** @private template-access to all external option hits */ + readonly optionHints = ExternalOptionHint; + + /** @private template-access to parseSupportedValues */ + readonly parseSupportedValues = parseSupportedValues; + + @ViewChild('suffixElement', { static: false, read: ElementRef }) + suffixElement?: ElementRef; + + /** Cached canvas element used by getTextWidth */ + private cachedCanvas?: HTMLCanvasElement; + + /** Returns the value of external-option hint annotation */ + externalOptType(opt: S): ExternalOptionHint | null { + return opt.Annotations?.["safing/portbase:ui:display-hint"] || null; + } + + /** Whether or not the input should be currently disabled. */ + @Input() + set disabled(v: any) { + const disabled = coerceBooleanProperty(v); + this.setDisabledState(disabled); + } + get disabled() { + return this._disabled; + } + + /** The setting to display */ + @Input() + setting: S | null = null; + + /** Emits when the user activates focus on this component */ + @Output() + blured = new EventEmitter(); + + /** @private The ngModel in our view used to display the value */ + @ViewChild(NgModel) + model: NgModel | null = null; + + /** The unit of the setting */ + get unit() { + if (!this.setting) { + return ''; + } + return this.setting.Annotations[WellKnown.Unit] || ''; + } + + /** + * Holds the value as it is presented to the user. + * That is, a JSON encoded object or array is dumped as a + * JSON string. Strings, numbers and booleans are presented + * as they are. + */ + _value: string | number | boolean = ""; + + /** + * Describes the type of the original settings value + * as passed to writeValue(). + * This may be anything that can be returned from `typeof v`. + * If set to "string", "number" or "boolean" then _value is emitted + * as it is. + * If it's set anything else (like "object") than _value is JSON.parse`d + * before being emitted. + */ + _type: string = ''; + + /* Returns true if the current _type and _value is managed as JSON */ + get isJSON(): boolean { + return this._type !== 'string' + && this._type !== 'number' + && this._type !== 'boolean' + } + + /* + * _onChange is set using registerOnChange by @angular/forms + * and satisfies the ControlValueAccessor. + */ + private _onChange: (_: SettingValueType) => void = () => { }; + + /* _onTouch is set using registerOnTouched by @angular/forms + * and satisfies the ControlValueAccessor. + */ + private _onTouch: () => void = () => { }; + + private _onValidatorChange: () => void = () => { }; + + /* Whether or not the input field is disabled. Set by setDisabledState + * from @angular/forms + */ + _disabled: boolean = false; + private _valid: boolean = true; + + // We are using ChangeDetectionStrategy.OnPush so angular does not + // update ourself when writeValue or setDisabledState is called. + // Using the changeDetectorRef we can take care of that ourself. + constructor( + @Inject(DOCUMENT) private document: Document, + private _changeDetectorRef: ChangeDetectorRef + ) { } + + ngAfterViewChecked() { + // update the suffix position everytime angular has + // checked our view for changes. + this.updateUnitSuffixPosition(); + } + + /** + * Sets the user-presented value and emits a change. + * Used by our view. Not meant to be used from outside! + * Use writeValue instead. + * @private + * + * @param value The value to set + */ + setInternalValue(value: string | number | boolean) { + let toEmit: any = value; + try { + if (!this.isJSON) { + toEmit = value; + } else { + toEmit = JSON.parse(value as string); + } + } catch (err) { + this._valid = false; + this._onValidatorChange(); + return; + } + + this._valid = true; + this._value = value; + this._onChange(toEmit); + this.updateUnitSuffixPosition(); + } + + /** + * Updates the position of the value's unit suffix element + */ + private updateUnitSuffixPosition() { + if (!!this.unit && !!this.suffixElement) { + const input = this.suffixElement.nativeElement.previousSibling! as HTMLInputElement; + const style = window.getComputedStyle(input); + let paddingleft = parseInt(style.paddingLeft.slice(0, -2)) + // we need to use `input.value` instead of `value` as we need to + // get preceding zeros of the number input as well, while still + // using the value as a fallback. + let value = input.value || (this._value as string); + const width = this.getTextWidth(value, style.font) + paddingleft; + this.suffixElement.nativeElement.style.left = `${width}px`; + } + } + + /** + * Validates if "value" matches the settings requirements. + * It satisfies the NG_VALIDATORS interface and validates the + * value for THIS component. + * + * @param param0 The AbstractControl to validate + */ + validate({ value }: AbstractControl): ValidationErrors | null { + if (!this._valid) { + return { + jsonParseError: true + } + } + + if (this._type === 'string' || value === null) { + if (!!this.setting?.DefaultValue && !value) { + return { + required: true, + } + } + } + + if (!!this.setting?.ValidationRegex) { + const re = new RegExp(this.setting.ValidationRegex); + + if (!this.isJSON) { + if (!re.test(`${value}`)) { + return { + pattern: `"${value}"` + } + } + } else { + if (!Array.isArray(value)) { + return { + invalidType: true + } + } + const invalidLines = value.filter(v => !re.test(v)); + if (invalidLines.length) { + return { + pattern: invalidLines + } + } + } + } + + return null; + } + + /** + * Writes a new value and satisfies the ControlValueAccessor + * + * @param v The new value to write + */ + writeValue(v: SettingValueType) { + // the following is a super ugly work-around for the migration + // from security-settings to booleans. + // + // In order to not mess and hide an actual portmaster issue + // we only convert v to a boolean if it's a number value and marked as a security setting. + // In all other cases we don't mangle it. + // + // TODO(ppacher): Remove in v1.8? + // BOM + if (this.setting?.OptType === OptionType.Bool && this.setting?.Annotations[WellKnown.DisplayHint] === ExternalOptionHint.SecurityLevel) { + if (typeof v === 'number') { + (v as any) = v === 7; + } + } + // EOM + + let t = typeof v; + this._type = t; + + if (this.isJSON) { + this._value = JSON.stringify(v, undefined, 2); + } else { + this._value = v; + } + + this.updateUnitSuffixPosition(); + this._changeDetectorRef.markForCheck(); + } + + registerOnValidatorChange(fn: () => void) { + this._onValidatorChange = fn; + } + + /** + * Registers the onChange function requred by the + * ControlValueAccessor + * + * @param fn The fn to register + */ + registerOnChange(fn: (_: SettingValueType) => void) { + this._onChange = fn; + } + + /** + * @private + * Called when the input-component used for the setting is touched/focused. + */ + touched() { + this._onTouch(); + this.blured.next(); + } + + /** + * Registers the onTouch function requred by the + * ControlValueAccessor + * + * @param fn The fn to register + */ + registerOnTouched(fn: () => void) { + this._onTouch = fn; + } + + /** + * Enable or disable the component. Required for the + * ControlValueAccessor. + * + * @param disable Whether or not the component is disabled + */ + setDisabledState(disable: boolean) { + this._disabled = disable; + this._changeDetectorRef.markForCheck(); + } + + /** + * @private + * Returns the number of lines in value. If value is not + * a string 1 is returned. + */ + lineCount(value: string | number | boolean) { + if (typeof value === 'string') { + return value.split('\n').length + } + return 1 + } + + /** + * Calculates the amount of pixel a text requires when being rendered. + * It uses canvas.measureText on a dummy (no attached) element + * + * @param text The text that would be rendered + * @param font The CSS font descriptor that would be used for the text + */ + private getTextWidth(text: string, font: string): number { + let canvas = this.cachedCanvas || this.document.createElement('canvas'); + this.cachedCanvas = canvas; + + let context = canvas.getContext("2d")!; + context.font = font; + let metrics = context.measureText(text); + return metrics.width; + } +} diff --git a/desktop/angular/src/app/shared/config/basic-setting/index.ts b/desktop/angular/src/app/shared/config/basic-setting/index.ts new file mode 100644 index 00000000..ec1ff492 --- /dev/null +++ b/desktop/angular/src/app/shared/config/basic-setting/index.ts @@ -0,0 +1 @@ +export * from './basic-setting'; diff --git a/desktop/angular/src/app/shared/config/config-settings.html b/desktop/angular/src/app/shared/config/config-settings.html new file mode 100644 index 00000000..f6c9253e --- /dev/null +++ b/desktop/angular/src/app/shared/config/config-settings.html @@ -0,0 +1,111 @@ + + + +
+ + +
+ + + + + +

+ {{subsys.Name}} +

+ + +
+ +
+

{{cat.name}}

+ + + + + +
+ +
+ +
+ + + + +
+
+
+
+
+
+
+ + +

+ Other +

+
+ + + + +
+
+
+
+
diff --git a/desktop/angular/src/app/shared/config/config-settings.scss b/desktop/angular/src/app/shared/config/config-settings.scss new file mode 100644 index 00000000..f839c341 --- /dev/null +++ b/desktop/angular/src/app/shared/config/config-settings.scss @@ -0,0 +1,95 @@ +:host { + display: flex; + overflow: hidden; +} + + +fa-icon[icon="spinner"] { + @apply text-3xl; + display: block; + width: 100%; + text-align: center; + height: 6rem; +} + +div.settings-nav { + @apply mt-4; + flex-shrink: 0; + overflow: visible; + white-space: nowrap; + + transition: height cubic-bezier(0.25, 0.46, 0.45, 0.94) .5s; + @apply text-xs; + + + ul { + position: fixed; + + li { + @apply font-medium; + + &.separated { + margin-top: 1.25rem; + } + + } + + &>li { + @apply mb-1; + @apply text-tertiary; + + span { + cursor: pointer; + display: block; + } + + &:hover, + &.active { + @apply text-primary; + } + + &.active { + &.category:before { + content: ""; + width: 1px; + height: 1rem; + @apply bg-white block absolute; + left: 0.5rem; + } + + ul.settings { + display: inline-block; + } + } + + ul.settings { + position: unset; + @apply mt-2; + @apply ml-2; + @apply pl-3; + @apply text-xs; + @apply border-l; + @apply border-cards-tertiary; + display: none; + + li { + cursor: pointer; + margin-top: 0; + } + } + } + } +} + +.user-defined-value:before { + content: ""; + height: 1rem; + @apply bg-blue block absolute rounded-full w-1 h-1; + top: 0.45rem; + left: -1rem; +} + +.user-defined-value.category:before { + left: -2rem; + top: 0.35rem; +} diff --git a/desktop/angular/src/app/shared/config/config-settings.ts b/desktop/angular/src/app/shared/config/config-settings.ts new file mode 100644 index 00000000..49301abf --- /dev/null +++ b/desktop/angular/src/app/shared/config/config-settings.ts @@ -0,0 +1,606 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ScrollDispatcher } from '@angular/cdk/overlay'; +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + QueryList, + TrackByFunction, + ViewChildren, +} from '@angular/core'; +import { + ConfigService, + ExpertiseLevelNumber, + PortapiService, + Setting, + StringSetting, + releaseLevelFromName, +} from '@safing/portmaster-api'; +import { BehaviorSubject, Subscription, combineLatest } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; +import { StatusService, Subsystem } from 'src/app/services'; +import { + fadeInAnimation, + fadeInListAnimation, + fadeOutAnimation, +} from 'src/app/shared/animations'; +import { FuzzySearchService } from 'src/app/shared/fuzzySearch'; +import { ExpertiseLevelOverwrite } from '../expertise/expertise-directive'; +import { SaveSettingEvent } from './generic-setting/generic-setting'; +import { ActionIndicatorService } from '../action-indicator'; +import { SfngDialogService } from '@safing/ui'; +import { + ExportConfig, + ExportDialogComponent, +} from './export-dialog/export-dialog.component'; +import { + ImportConfig, + ImportDialogComponent, +} from './import-dialog/import-dialog.component'; + +interface Category { + name: string; + settings: Setting[]; + minimumExpertise: ExpertiseLevelNumber; + collapsed: boolean; + hasUserDefinedValues: boolean; +} + +interface SubsystemWithExpertise extends Subsystem { + minimumExpertise: ExpertiseLevelNumber; + isDisabled: boolean; + hasUserDefinedValues: boolean; +} + +@Component({ + selector: 'app-settings-view', + templateUrl: './config-settings.html', + styleUrls: ['./config-settings.scss'], + animations: [fadeInAnimation, fadeOutAnimation, fadeInListAnimation], +}) +export class ConfigSettingsViewComponent + implements OnInit, OnDestroy, AfterViewInit { + subsystems: SubsystemWithExpertise[] = []; + others: Setting[] | null = null; + settings: Map = new Map(); + + /** A list of all selected settings for export */ + selectedSettings: { [key: string]: boolean } = {}; + + /** Whether or not we are currently in "export" mode */ + exportMode = false; + + activeSection = ''; + activeCategory = ''; + loading = true; + + @Input() + resetLabelText = 'Reset to system default'; + + @Input() + set compactView(v: any) { + this._compactView = coerceBooleanProperty(v); + } + get compactView() { + return this._compactView; + } + private _compactView = false; + + @Input() + set lockDefaults(v: any) { + this._lockDefaults = coerceBooleanProperty(v); + } + get lockDefaults() { + return this._lockDefaults; + } + private _lockDefaults = false; + + @Input() + set userSettingsMarker(v: any) { + this._userSettingsMarker = coerceBooleanProperty(v); + } + get userSettingsMarker() { + return this._userSettingsMarker; + } + private _userSettingsMarker = true; + + @Input() + set searchTerm(v: string) { + this.onSearch.next(v); + } + + @Input() + set availableSettings(v: Setting[]) { + this.onSettingsChange.next(v); + } + + @Input() + set scope(scope: 'global' | string) { + this._scope = scope; + } + get scope() { + return this._scope; + } + private _scope: 'global' | string = 'global'; + + @Input() + displayStackable: string | boolean = false; + + @Input() + set highlightKey(key: string | null) { + this._highlightKey = key || null; + this._scrolledToHighlighted = false; + // If we already loaded the settings then instruct the window + // to scroll the setting into the view. + if (!!key && !!this.settings && this.settings.size > 0) { + this.scrollTo(key); + this._scrolledToHighlighted = true; + } + } + get highlightKey() { + return this._highlightKey; + } + private _highlightKey: string | null = null; + private _scrolledToHighlighted = false; + + mustShowSetting: ExpertiseLevelOverwrite = ( + lvl: ExpertiseLevelNumber, + s: Setting + ) => { + if (lvl >= s.ExpertiseLevel) { + // this setting is shown anyway. + return false; + } + if (s.Key === this.highlightKey) { + return true; + } + // the user is searching for settings so make sure we even show advanced or developer settings + if (this.onSearch.getValue() !== '') { + return true; + } + if (s.Value === undefined) { + // no value set + return false; + } + return true; + }; + + mustShowCategory: ExpertiseLevelOverwrite = ( + lvl: ExpertiseLevelNumber, + cat: Category + ) => { + return cat.settings.some((setting) => this.mustShowSetting(lvl, setting)); + }; + + mustShowSubsystem: ExpertiseLevelOverwrite = ( + lvl: ExpertiseLevelNumber, + subsys: SubsystemWithExpertise + ) => { + return !!this.settings + .get(subsys.ConfigKeySpace) + ?.some((cat) => this.mustShowCategory(lvl, cat)); + }; + + @Output() + save = new EventEmitter(); + + private onSearch = new BehaviorSubject(''); + private onSettingsChange = new BehaviorSubject([]); + + @ViewChildren('navLink', { read: ElementRef }) + navLinks: QueryList | null = null; + + private subscription = Subscription.EMPTY; + + constructor( + public statusService: StatusService, + public configService: ConfigService, + private elementRef: ElementRef, + private changeDetectorRef: ChangeDetectorRef, + private scrollDispatcher: ScrollDispatcher, + private searchService: FuzzySearchService, + private actionIndicator: ActionIndicatorService, + private portapi: PortapiService, + private dialog: SfngDialogService + ) { } + + openImportDialog() { + const importConfig: ImportConfig = { + type: 'setting', + key: this.scope, + }; + this.dialog.create(ImportDialogComponent, { + data: importConfig, + autoclose: false, + backdrop: 'light', + }); + } + + toggleExportMode() { + this.exportMode = !this.exportMode; + + if (this.exportMode) { + this.actionIndicator.info( + 'Settings Export', + 'Please select all settings you want to export and press "Save" to generate the export. Note that settings with system defaults cannot be exported and are hidden.' + ); + } + } + + generateExport() { + let selectedKeys = Object.keys(this.selectedSettings).reduce((sum, key) => { + if (this.selectedSettings[key]) { + sum.push(key); + } + + return sum; + }, [] as string[]); + + if (selectedKeys.length === 0) { + selectedKeys = Array.from(this.settings.values()).reduce( + (sum, current) => { + current.forEach((cat) => { + cat.settings.forEach((s) => { + if (s.Value !== undefined) { + sum.push(s.Key); + } + }); + }); + + return sum; + }, + [] as string[] + ); + } + + this.portapi.exportSettings(selectedKeys, this.scope).subscribe({ + next: (exportBlob) => { + const exportConfig: ExportConfig = { + type: 'setting', + content: exportBlob, + }; + + this.dialog.create(ExportDialogComponent, { + data: exportConfig, + backdrop: 'light', + autoclose: true, + }); + + this.exportMode = false; + }, + error: (err) => { + const msg = this.actionIndicator.getErrorMessgae(err); + this.actionIndicator.error('Failed To Generate Export', msg); + }, + }); + } + + saveSetting(event: SaveSettingEvent, s: Setting) { + this.save.next(event); + const subsys = this.subsystems.find( + (subsys) => s.Key === subsys.ToggleOptionKey + ); + if (!!subsys) { + // trigger a reload of the page as we now might need to show more + // settings. + this.onSettingsChange.next(this.onSettingsChange.getValue()); + } + } + + trackSubsystem: TrackByFunction = + this.statusService.trackSubsystem; + + trackCategory(_: number, cat: Category) { + return cat.name; + } + + ngOnInit(): void { + this.subscription = combineLatest([ + this.onSettingsChange, + this.statusService.querySubsystem(), + this.onSearch.pipe(debounceTime(250)), + this.configService.watch('core/releaseLevel'), + ]) + .pipe(debounceTime(10)) + .subscribe( + ([settings, subsystems, searchTerm, currentReleaseLevelSetting]) => { + this.subsystems = subsystems.map((s) => ({ + ...s, + // we start with developer and decrease to the lowest number required + // while grouping the settings. + minimumExpertise: ExpertiseLevelNumber.developer, + isDisabled: false, + hasUserDefinedValues: false, + })); + this.others = []; + this.settings = new Map(); + + // Get the current release level as a number (fallback to 'stable' is something goes wrong) + const currentReleaseLevel = releaseLevelFromName( + currentReleaseLevelSetting || ('stable' as any) + ); + + // Make sure we only display settings that are allowed by the releaselevel setting. + settings = settings.filter( + (setting) => setting.ReleaseLevel <= currentReleaseLevel + ); + + // Use fuzzy-search to limit the number of settings shown. + const filtered = this.searchService.searchList(settings, searchTerm, { + ignoreLocation: true, + ignoreFieldNorm: true, + threshold: 0.1, + minMatchCharLength: 3, + keys: [ + { name: 'Name', weight: 3 }, + { name: 'Description', weight: 2 }, + ], + }); + + // The search service wraps the items in a search-result object. + // Unwrap them now. + settings = filtered.map((res) => res.item); + + // use order-annotations to sort the settings. This affects the order of + // the categories as well as the settings inside the categories. + settings.sort((a, b) => { + const orderA = a.Annotations?.['safing/portbase:ui:order'] || 0; + const orderB = b.Annotations?.['safing/portbase:ui:order'] || 0; + return orderA - orderB; + }); + + settings.forEach((setting) => { + let pushed = false; + this.subsystems.forEach((subsys) => { + if ( + setting.Key.startsWith( + subsys.ConfigKeySpace.slice('config:'.length) + ) + ) { + // get the category name annotation and fallback to 'others' + let catName = 'other'; + if ( + !!setting.Annotations && + !!setting.Annotations['safing/portbase:ui:category'] + ) { + catName = setting.Annotations['safing/portbase:ui:category']; + } + + // ensure we have a category array for the subsystem. + let categories = this.settings.get(subsys.ConfigKeySpace); + if (!categories) { + categories = []; + this.settings.set(subsys.ConfigKeySpace, categories); + } + + // find or create the appropriate category object. + let cat = categories.find((c) => c.name === catName); + if (!cat) { + cat = { + name: catName, + minimumExpertise: ExpertiseLevelNumber.developer, + settings: [], + collapsed: false, + hasUserDefinedValues: false, + }; + categories.push(cat); + } + + // add the setting to the category object and update + // the minimum expertise required for the category. + cat.settings.push(setting); + if (setting.ExpertiseLevel < cat.minimumExpertise) { + cat.minimumExpertise = setting.ExpertiseLevel; + } + + pushed = true; + } + }); + + // if we did not push the setting to some subsystem + // we need to push it to "others" + if (!pushed) { + this.others!.push(setting); + } + }); + + if (this.others.length === 0) { + this.others = null; + } + + // Reduce the subsystem array to only contain subsystems that + // actually have settings to show. + // Also update the minimumExpertiseLevel for those subsystems + this.subsystems = this.subsystems + .filter((subsys) => { + return !!this.settings.get(subsys.ConfigKeySpace); + }) + .map((subsys) => { + let categories = this.settings.get(subsys.ConfigKeySpace)!; + let hasUserDefinedValues = false; + categories.forEach((c) => { + c.hasUserDefinedValues = c.settings.some( + (s) => s.Value !== undefined + ); + hasUserDefinedValues = + c.hasUserDefinedValues || hasUserDefinedValues; + }); + + subsys.hasUserDefinedValues = hasUserDefinedValues; + + let toggleOption: Setting | undefined = undefined; + for (let c of categories) { + toggleOption = c.settings.find( + (s) => s.Key === subsys.ToggleOptionKey + ); + if (!!toggleOption) { + if ( + (toggleOption.Value !== undefined && !toggleOption.Value) || + (toggleOption.Value === undefined && + !toggleOption.DefaultValue) + ) { + subsys.isDisabled = true; + + // remove all settings for all subsystem categories + // except for the ToggleOption. + categories = categories + .map((c) => ({ + ...c, + settings: c.settings.filter( + (s) => s.Key === toggleOption!.Key + ), + })) + .filter((cat) => cat.settings.length > 0); + this.settings.set(subsys.ConfigKeySpace, categories); + } + break; + } + } + + // reduce the categories to find the smallest expertise level requirement. + subsys.minimumExpertise = categories.reduce((min, current) => { + if (current.minimumExpertise < min) { + return current.minimumExpertise; + } + return min; + }, ExpertiseLevelNumber.developer as ExpertiseLevelNumber); + + return subsys; + }); + + // Force the core subsystem to the end. + if (this.subsystems.length >= 2 && this.subsystems[0].ID === 'core') { + this.subsystems.push( + this.subsystems.shift() as SubsystemWithExpertise + ); + } + + // Notify the user interface that we're done loading + // the settings. + this.loading = false; + + // If there's a highlightKey set and we have not yet scrolled + // to it (because it was set during component bootstrap) we + // need to scroll there now. + if (this._highlightKey !== null && !this._scrolledToHighlighted) { + this._scrolledToHighlighted = true; + + // Use the next animation frame for scrolling + window.requestAnimationFrame(() => { + this.scrollTo(this._highlightKey || ''); + }); + } + } + ); + } + + ngAfterViewInit() { + this.subscription = new Subscription(); + + // Whenever our scroll-container is scrolled we might + // need to update which setting is currently highlighted + // in the settings-navigation. + this.subscription.add( + this.scrollDispatcher + .scrolled(10) + .subscribe(() => this.intersectionCallback()) + ); + + // Also, entries in the settings-navigation might become + // visible with expertise/release level changes so make + // sure to recalculate the current one whenever a change + // happens. + this.subscription.add( + this.navLinks?.changes.subscribe(() => { + this.intersectionCallback(); + this.changeDetectorRef.detectChanges(); + }) + ); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + this.onSearch.complete(); + } + + /** + * Calculates which navigation entry should be highlighted + * depending on the scroll position. + */ + private intersectionCallback() { + // search our parents for the element that's scrollable + let elem: HTMLElement = this.elementRef.nativeElement; + while (!!elem) { + if (elem.scrollTop > 0) { + break; + } + elem = elem.parentElement!; + } + + // if there's no scrolled/scrollable parent element + // our content itself is scrollable so use our own + // host element as the anchor for the calculation. + if (!elem) { + elem = this.elementRef.nativeElement; + } + + // get the elements offset to page-top + var offsetTop = 0; + if (!!elem) { + const viewRect = elem.getBoundingClientRect(); + offsetTop = viewRect.top; + } + + this.navLinks?.some((link) => { + const subsystem = link.nativeElement.getAttribute('subsystem'); + const category = link.nativeElement.getAttribute('category'); + + const lastChild = (link.nativeElement as HTMLElement) + .lastElementChild as HTMLElement; + if (!lastChild) { + return false; + } + + const rect = lastChild.getBoundingClientRect(); + const styleBox = getComputedStyle(lastChild); + + const offset = + rect.top + + rect.height - + parseInt(styleBox.marginBottom) - + parseInt(styleBox.paddingBottom); + + if (offset >= offsetTop) { + this.activeSection = subsystem; + this.activeCategory = category; + return true; + } + + return false; + }); + this.changeDetectorRef.detectChanges(); + } + + /** + * @private + * Performs a smooth-scroll to the given anchor element ID. + * + * @param id The ID of the anchor element to scroll to. + */ + scrollTo(id: string, cat?: Category) { + if (!!cat) { + cat.collapsed = false; + } + document.getElementById(id)?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + inline: 'nearest', + }); + } +} diff --git a/desktop/angular/src/app/shared/config/config.module.ts b/desktop/angular/src/app/shared/config/config.module.ts new file mode 100644 index 00000000..127032af --- /dev/null +++ b/desktop/angular/src/app/shared/config/config.module.ts @@ -0,0 +1,77 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { + SfngSelectModule, + SfngTipUpModule, + SfngToggleSwitchModule, + SfngTooltipModule, +} from '@safing/ui'; +import { MarkdownModule } from 'ngx-markdown'; +import { ExpertiseModule } from '../expertise/expertise.module'; +import { SfngFocusModule } from '../focus'; +import { SfngMenuModule } from '../menu'; +import { SfngMultiSwitchModule } from '../multi-switch'; +import { BasicSettingComponent } from './basic-setting/basic-setting'; +import { ConfigSettingsViewComponent } from './config-settings'; +import { FilterListComponent } from './filter-lists'; +import { GenericSettingComponent } from './generic-setting'; +import { + OrderedListComponent, + OrderedListItemComponent, +} from './ordererd-list'; +import { RuleListItemComponent } from './rule-list/list-item'; +import { RuleListComponent } from './rule-list/rule-list'; +import { SafePipe } from './safe.pipe'; +import { ExportDialogComponent } from './export-dialog/export-dialog.component'; +import { ImportDialogComponent } from './import-dialog/import-dialog.component'; +import { SfngAppIconModule } from '../app-icon'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + DragDropModule, + SfngTooltipModule, + SfngSelectModule, + SfngMultiSwitchModule, + SfngFocusModule, + SfngMenuModule, + SfngTipUpModule, + FontAwesomeModule, + MarkdownModule, + RouterModule, + ExpertiseModule, + SfngToggleSwitchModule, + MarkdownModule, + SfngAppIconModule + ], + declarations: [ + BasicSettingComponent, + FilterListComponent, + OrderedListComponent, + OrderedListItemComponent, + RuleListComponent, + RuleListItemComponent, + ConfigSettingsViewComponent, + GenericSettingComponent, + SafePipe, + ExportDialogComponent, + ImportDialogComponent, + ], + exports: [ + BasicSettingComponent, + FilterListComponent, + OrderedListComponent, + OrderedListItemComponent, + RuleListComponent, + RuleListItemComponent, + ConfigSettingsViewComponent, + GenericSettingComponent, + SafePipe, + ], +}) +export class ConfigModule { } diff --git a/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.html b/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.html new file mode 100644 index 00000000..da8a3cb1 --- /dev/null +++ b/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.html @@ -0,0 +1,19 @@ +
+

+ {{ dialogRef.data.type === "setting" ? "Settings" : "Profile" }} Export +

+ + +
+ + + +
+ + +
\ No newline at end of file diff --git a/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.ts b/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.ts new file mode 100644 index 00000000..f451732e --- /dev/null +++ b/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.ts @@ -0,0 +1,67 @@ +import { DOCUMENT } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + ElementRef, + OnInit, + inject, +} from '@angular/core'; +import { SFNG_DIALOG_REF, SfngDialogRef } from '@safing/ui'; +import { ActionIndicatorService } from '../../action-indicator'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; + +export interface ExportConfig { + content: string; + type: 'setting' | 'profile'; +} + +@Component({ + templateUrl: './export-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + :host { + @apply flex flex-col gap-2 overflow-hidden; + min-height: 24rem; + min-width: 24rem; + max-height: 40rem; + max-width: 40rem; + } + `, + ], +}) +export class ExportDialogComponent implements OnInit { + readonly dialogRef: SfngDialogRef< + ExportDialogComponent, + unknown, + ExportConfig + > = inject(SFNG_DIALOG_REF); + + private readonly elementRef: ElementRef = inject(ElementRef); + private readonly document = inject(DOCUMENT); + private readonly uai = inject(ActionIndicatorService); + private readonly integration = inject(INTEGRATION_SERVICE); + + content = ''; + + ngOnInit(): void { + this.content = '```yaml\n' + this.dialogRef.data.content + '\n```'; + } + + download() { + const blob = new Blob([this.dialogRef.data.content], { type: 'text/yaml' }); + + const elem = this.document.createElement('a'); + elem.href = window.URL.createObjectURL(blob); + elem.download = 'export.yaml'; + this.elementRef.nativeElement.appendChild(elem); + elem.click(); + this.elementRef.nativeElement.removeChild(elem); + } + + copyToClipboard() { + this.integration.writeToClipboard(this.dialogRef.data.content) + .then(() => this.uai.success('Copied to Clipboard')) + .catch(() => this.uai.error('Failed to Copy to Clipboard')); + } +} diff --git a/desktop/angular/src/app/shared/config/filter-lists/filter-list.html b/desktop/angular/src/app/shared/config/filter-lists/filter-list.html new file mode 100644 index 00000000..a6a72a87 --- /dev/null +++ b/desktop/angular/src/app/shared/config/filter-lists/filter-list.html @@ -0,0 +1,55 @@ +
+ + +
+
+ + + + + + + {{ !!node.license ? 'License: ' + node.license : '' }} + + + + + + +
+ +
+
+ + + Expand + + + + Collapse + +
+
+ +
+
+ + + +
+
+
+ + + + + +
diff --git a/desktop/angular/src/app/shared/config/filter-lists/filter-list.scss b/desktop/angular/src/app/shared/config/filter-lists/filter-list.scss new file mode 100644 index 00000000..1b41d179 --- /dev/null +++ b/desktop/angular/src/app/shared/config/filter-lists/filter-list.scss @@ -0,0 +1,101 @@ +:host { + display: block; + overflow: hidden; + + @apply bg-cards-secondary; + @apply rounded; + @apply p-2; + @apply h-full; +} + +.node { + position: relative; + display: flex; + flex-direction: column; + + justify-content: flex-start; + @apply py-1; + + .head { + display: flex; + flex-direction: row; + align-items: baseline; + + input { + @apply mr-2; + position: relative; + top: 2px; + } + + label { + display: flex; + flex-direction: column; + flex-grow: 1; + } + + span.details { + opacity: 0; + text-transform: capitalize; + font-size: 0.9em; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + max-width: 6rem; + @apply text-tertiary; + } + + &:hover { + span.details { + opacity: 1; + + } + } + } + + span.name { + @apply text-primary; + + .id { + @apply text-tertiary; + font-style: italic; + } + } + + .description { + position: relative; + top: -2px; + + @apply text-tertiary; + } + + div.expand { + cursor: pointer; + @apply text-secondary; + display: flex; + flex-direction: row; + align-items: center; + @apply pb-2; + + fa-icon { + margin-right: 0.25rem; + } + } + + .children { + display: flex; + flex-direction: column; + margin-left: 1.25rem; + } + + .border { + position: absolute; + top: 1.2rem; + bottom: 0.5rem; + width: 0.7rem; + margin-left: -0.85rem; + border: 1px solid; + border-right: none; + border-top: none; + @apply border-cards-tertiary; + } +} diff --git a/desktop/angular/src/app/shared/config/filter-lists/filter-list.ts b/desktop/angular/src/app/shared/config/filter-lists/filter-list.ts new file mode 100644 index 00000000..b39c45b4 --- /dev/null +++ b/desktop/angular/src/app/shared/config/filter-lists/filter-list.ts @@ -0,0 +1,293 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, HostListener, OnDestroy, OnInit } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { PortapiService, Record } from '@safing/portmaster-api'; +import { Subscription } from 'rxjs'; +import { moveInOutListAnimation } from '../../animations'; + +interface Category { + name: string; + id: string; + description: string; + parent?: string | null; +} + +interface Source { + name: string; + id: string; + description: string; + category: string; + // urls: Resource[]; // we don't care about the actual URLs here. + website: string; + contribute: string; + license: string; +} + +interface FilterListIndex extends Record { + version: string; + schemaVersion: string; + categories: Category[]; + sources: Source[]; +} + +interface TreeNode { + id: string; + name: string; + description: string; + children: TreeNode[]; + expanded: boolean; + selected: boolean; + parent?: TreeNode; + website?: string; + license?: string; + hasSelectedChildren: boolean; +} + +@Component({ + selector: 'app-filter-list', + templateUrl: './filter-list.html', + styleUrls: ['./filter-list.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FilterListComponent), + multi: true, + } + ], + animations: [ + moveInOutListAnimation, + ] +}) +export class FilterListComponent implements OnInit, OnDestroy, ControlValueAccessor { + /** The actual filter-list index as loaded from the portmaster. */ + private index: FilterListIndex | null = null; + + /** @private a list of "tree-nodes" to render */ + nodes: TreeNode[] = []; + + /** A lookup map for fast ID to TreeNode lookups */ + private lookupMap: Map = new Map(); + + /** @private forward blur events to the onTouch callback. */ + @HostListener('blur') + onBlur() { + this.onTouch(); + } + + /** The currently selected IDs. */ + private selectedIDs: string[] = []; + + /** Subscription to watch the filterlist index. */ + private watchSubscription = Subscription.EMPTY; + + constructor(private portapi: PortapiService, + private changeDetectorRef: ChangeDetectorRef) { } + + ngOnInit() { + this.watchSubscription = + this.portapi.watch("cache:intel/filterlists/index") + .subscribe( + index => this.updateIndex(index), + err => { + // Filter list index not yet loaded. + console.error(`failed to get fitlerlist index`, err); + } + ); + } + + ngOnDestroy() { + this.watchSubscription.unsubscribe(); + } + + /** The onChange callback registered by ngModel or form controls */ + private _onChange: (v: string[]) => void = () => { }; + + /** Registers the onChange callback required by ControlValueAccessor */ + registerOnChange(fn: (v: string[]) => void) { + this._onChange = fn; + } + + /** The _onTouch callback registered by ngModel and form controls */ + private onTouch: () => void = () => { }; + + /** Registeres the onTouch callback required by ControlValueAccessor. */ + registerOnTouched(fn: () => void) { + this.onTouch = fn; + } + + /** + * Update the currently selected IDs. Used by ngModel + * and form controls. Implements ControlValueAccessor. + * + * @param ids A list of selected IDs + */ + writeValue(ids: string[]) { + this.selectedIDs = ids; + if (!!this.index) { + this.updateIndex(this.index); + } + } + + /** + * + * @param index The filter list index. + */ + private updateIndex(index: FilterListIndex) { + this.index = index; + + var nodes: TreeNode[] = []; + let lm = new Map(); + let childCategories: Category[] = []; + + // Create a tree-node for each category + this.index.categories.forEach(category => { + let tn: TreeNode = { + id: category.id, + description: category.description, + name: category.name, + children: [], + expanded: this.lookupMap.get(category.id)?.expanded || false, // keep it expanded if the user did not change anything. + selected: false, + hasSelectedChildren: false, + }; + + lm.set(category.id, tn) + + // if the category does not have a parent + // it's a root node. + if (!category.parent) { + nodes.push(tn); + } else { + // we need to handle child-categories later. + childCategories.push(category); + } + }); + + // iterate over all "child" categories and add + // them to the correct parent (which must be in lm already.) + childCategories.forEach(category => { + const tn = lm.get(category.id)!; + const parent = lm.get(category.parent!); + // if the parent category does not exist ignore it + if (!parent) { + return; + } + + parent.children.push(tn); + tn.parent = parent; + }); + + this.index.sources.forEach(source => { + let category = lm.get(source.category); + if (!category) { + return; + } + + let tn: TreeNode = { + id: source.id, + name: source.name, + description: source.description, + children: [], + expanded: false, + selected: false, + parent: category, + website: source.website, + license: source.license, + hasSelectedChildren: false + } + + // Add the source to the lookup-map + lm.set(source.id, tn); + + category.children.push(tn); + }); + + // make sure we expand all parent categories for + // all selected IDs so they are actually visible. + this.selectedIDs.forEach(id => { + const tn = lm.get(id); + if (!tn) { + return; + } + + this.updateNode(tn, true, true, true, false); + + let parent = tn.parent; + while (!!parent) { + parent.expanded = true; + parent.hasSelectedChildren = true; + parent = parent.parent; + } + }); + + this.nodes = nodes; + this.lookupMap = lm; + + this.changeDetectorRef.markForCheck(); + } + + /** Returns all actually selected IDs. */ + private getIDs() { + let ids: string[] = []; + + let collectIds = (n: TreeNode) => { + if (n.selected) { + // If the parent is selected we can ignore the + // childs because they must be selected as well. + ids.push(n.id); + return; + } + + n.children.forEach(child => collectIds(child)); + } + + this.nodes.forEach(node => collectIds(node)) + + return ids; + } + + updateNode(node: TreeNode, selected: boolean, updateChildren = true, updateParents = true, emit = true) { + if (node.selected === selected) { + // Nothing changed + return; + } + + // update the node an all children + node.selected = selected; + if (updateChildren) { + node.children.forEach(child => this.updateNode(child, selected, true, false, false)); + } + + // if we have a parent we might need to update + // the parent as well. + if (!!node.parent && updateParents) { + if (selected) { + // if we are now selected we might need to "select" the + // parent if all children are selected now. + const hasUnselected = node.parent.children.some(sibling => !sibling.selected); + if (!hasUnselected) { + // We need to update all parents but updating children + // is useless. + this.updateNode(node.parent, true, false, true, false); + } + } else if (node.parent.selected) { + // if we are unselected now we might need to "unselect" the parent + // but select siblings directly + const selectedSiblings = node.parent.children.filter(sibling => sibling.selected && sibling !== node); + this.updateNode(node.parent, false, false, true, false) + } + } + + if (emit) { + const ids = this.getIDs(); + this.selectedIDs = ids; + this._onChange(this.selectedIDs); + } + } + + /** @private TrackByFunction for tree nodes. */ + trackNode(_: number, node: TreeNode) { + return node.id; + } +} + diff --git a/desktop/angular/src/app/shared/config/filter-lists/index.ts b/desktop/angular/src/app/shared/config/filter-lists/index.ts new file mode 100644 index 00000000..07932b7e --- /dev/null +++ b/desktop/angular/src/app/shared/config/filter-lists/index.ts @@ -0,0 +1 @@ +export { FilterListComponent } from './filter-list'; diff --git a/desktop/angular/src/app/shared/config/generic-setting/generic-setting.html b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.html new file mode 100644 index 00000000..7c2f9bd1 --- /dev/null +++ b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.html @@ -0,0 +1,204 @@ +
+ + + +
+
+

+ + + + + + + + Saved {{ _setting?.RequiresRestart ? ' - Restart required' : (uiReloadRequired ? ' - Reload required' : '') }} + + + + + + + Invalid Value: {{ rejected }} + + + + + + + This feature requires a subscription. + +
+ + + + {{setting?.Key}} + + Beta + + Experimental + + Advanced + + Developer + + +
+ + + +
+ + + + + Quick Settings + + + + + {{quick.Name}} + + +
+ + + + + + + + +
+

This setting stacks on top of the following global setting:

+ + +
+
+ + + + + + + + + + + +
+

This setting stacks on top of the following global setting:

+ +
+
+ + + + + + + + +
+

This setting stacks on top of the following global setting:

+ + +
+
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + Inherited from Global Settings + + + App specific configuration + + +
+ +
+ + {{resetLabelText}} + +
+ + +
+ +

{{ _setting?.Name }}

+ + +
+
\ No newline at end of file diff --git a/desktop/angular/src/app/shared/config/generic-setting/generic-setting.scss b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.scss new file mode 100644 index 00000000..14e2c9e5 --- /dev/null +++ b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.scss @@ -0,0 +1,97 @@ +:host { + @apply block; + + &.ng-invalid { + @apply border border-red border-opacity-50; + } + + &.rejected { + .release-level.rejected { + opacity: 1; + } + } + + &.highlighted:not(.touched) { + .name { + animation: fade-color 5s ease-out; + } + } +} + +.stacked-values { + margin-top: 0.5rem; + opacity: 0.7; + @apply w-full; +} + +.unlock-button { + @apply flex w-6 h-6 rounded-full; + + justify-content: center; + align-items: center; + cursor: pointer; + + position: absolute; + right: calc(-1.5rem/2); + top: calc(50% - 1.5rem/2); + + &:hover { + @apply bg-blue; + } +} + +.description, +.help-text { + display: block; + @apply text-secondary; +} + +.help-text { + @apply mb-2; +} + +.notice { + display: block; + padding-left: 0.5rem; + padding-right: 0.5rem; + @apply mb-4; + @apply text-secondary; + + fa-icon { + @apply mr-2; + } +} + +.help-text { + @apply p-4; + @apply bg-cards-secondary; + @apply rounded; + + .toggle { + position: relative; + left: -0.25rem; + cursor: pointer; + + fa-icon { + @apply pr-1; + } + + &:hover { + @apply text-primary; + } + } +} + +@keyframes fade-color { + 0% { + @apply text-blue; + } + + 90% { + @apply text-blue; + } + + 100% { + @apply text-primary; + } +} diff --git a/desktop/angular/src/app/shared/config/generic-setting/generic-setting.ts b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.ts new file mode 100644 index 00000000..4ff5a7f3 --- /dev/null +++ b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.ts @@ -0,0 +1,715 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, EventEmitter, HostBinding, Input, OnInit, Output, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { NgModel } from '@angular/forms'; +import { BaseSetting, ConfigService, ExpertiseLevel, ExpertiseLevelNumber, ExternalOptionHint, OptionType, PortapiService, QuickSetting, ReleaseLevel, SPNService, SettingValueType, UserProfile, WellKnown, applyQuickSetting } from '@safing/portmaster-api'; +import { SfngDialogRef, SfngDialogService } from '@safing/ui'; +import { Button } from 'js-yaml-loader!../../../i18n/helptexts.yaml'; +import { Subject } from 'rxjs'; +import { debounceTime, tap } from 'rxjs/operators'; +import { ActionIndicatorService } from '../../action-indicator'; +import { fadeInAnimation, fadeOutAnimation } from '../../animations'; +import { ExpertiseService } from '../../expertise/expertise.service'; +import { SPNAccountDetailsComponent } from '../../spn-account-details'; + +export interface SaveSettingEvent = any> { + key: string; + value: SettingValueType; + isDefault: boolean; + rejected?: (err: any) => void + accepted?: () => void +} + +@Component({ + selector: 'app-generic-setting', + templateUrl: './generic-setting.html', + exportAs: 'appGenericSetting', + styleUrls: ['./generic-setting.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInAnimation, + fadeOutAnimation + ] +}) +export class GenericSettingComponent> implements OnInit { + // + // Constants used in the template. + // + + readonly optionHint = ExternalOptionHint; + readonly expertiseNames = ExpertiseLevel + readonly expertise = ExpertiseLevelNumber; + readonly optionType = OptionType; + readonly releaseLevel = ReleaseLevel; + readonly wellKnown = WellKnown; + + @ViewChild('helpTemplate', { read: TemplateRef, static: true }) + helpTemplate: TemplateRef | null = null; + private helpDialogRef: SfngDialogRef | null = null; + + // Whether or not the user needs to upgrade his/her account before + // this setting is valid. + _upgradeRequired = false; + + /** + * Whether or not the component/setting is disabled and should + * be read-only. + */ + @Input() + @HostBinding('class.disabled') + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v); + } + get disabled() { + return this._disabled || this._upgradeRequired; + } + private _disabled: boolean = false; + + /** Returns the symbolMap annoation for endpoint-lists */ + get symbolMap() { + return this.setting?.Annotations[WellKnown.EndpointListVerdictNames] || { + '+': 'Allow', + '-': 'Block' + }; + } + + /** Whether or not the setting should be in select mode */ + @Input() + set selectMode(v: any) { + this._selectMode = coerceBooleanProperty(v) + + if (!this.selectMode) { + this.selected = false; + this.selectedChange.next(false); + } + } + get selectMode() { return this._selectMode } + private _selectMode = false; + + /** Whether or not the setting has been selected */ + @Input() + set selected(v: any) { + this._selected = coerceBooleanProperty(v) + } + get selected() { return this._selected } + private _selected = false; + + /** Emits when the user (de-) selectes the setting. Can be used for two-way binding */ + @Output() + selectedChange = new EventEmitter(); + + /** Controls whether or not header with the setting name and success/failure markers is shown */ + @Input() + set showHeader(v: any) { + this._showHeader = coerceBooleanProperty(v); + } + get showHeader() { return this._showHeader } + private _showHeader = true; + + /** Controls whether or not the blue or red status borders are shown */ + @Input() + set enableActiveBorder(v: any) { + this._enableActiveBorder = coerceBooleanProperty(v); + } + get enableActiveBorder() { return this._enableActiveBorder } + private _enableActiveBorder = true; + + /** + * Whether or not the component should be displayed as "locked" + * when the default value is used (that is, no 'Value' property + * in the setting) + */ + @Input() + set lockDefaults(v: any) { + this._lockDefaults = coerceBooleanProperty(v); + } + get lockDefaults() { + return this._lockDefaults; + } + private _lockDefaults: boolean = false; + + /** The label to display in the reset-value button */ + @Input() + resetLabelText = 'Reset'; + + /** Emits an event whenever the setting should be saved. */ + @Output() + save = new EventEmitter>(); + + /** Wether or not stackable values should be displayed. */ + @Input() + set displayStackable(v: any) { + this._displayStackable = coerceBooleanProperty(v); + } + get displayStackable() { + return this._displayStackable; + } + private _displayStackable = false; + + /** + * Whether or not the help text is currently shown + */ + @Input() + set showHelp(v: any) { + this._showHelp = coerceBooleanProperty(v); + } + get showHelp() { + return this._showHelp; + } + private _showHelp = false; + + /** Used internally to publish save events. */ + private triggerSave = new Subject(); + + /** Whether or not the value was reset. */ + wasReset = false; + + /** Whether or not a save request was rejected */ + @HostBinding('class.rejected') + get rejected() { + return this._rejected; + } + private _rejected = null; + + @HostBinding('class.saved') + get changeAccepted() { + return this._changeAccepted; + } + private _changeAccepted = false; + + /** + * @private + * Returns the external option type hint from a setting. + * + * @param opt The setting for with to return the external option hint + */ + externalOptType(opt: S | null): ExternalOptionHint | null { + return opt?.Annotations?.[WellKnown.DisplayHint] || null; + } + + /** + * @private + * Returns whether or not a restart is pending for this setting + * to apply. + */ + get restartPending(): boolean { + return !!this._setting?.Annotations?.[WellKnown.RestartPending]; + } + + /** + * @private + * Returns whether or not a UI reload is required for this setting + * to apply + */ + get uiReloadRequired(): boolean { + return this._setting?.Annotations?.[WellKnown.RequiresUIReload] !== undefined; + } + + /** + * Returns true if the setting has been touched (modified) by the user + * since the component has been rendered. + */ + @HostBinding('class.touched') + get touched() { + return this._touched; + } + private _touched = false; + + /** + * Returns true if the settings is currently locked. + */ + @HostBinding('class.locked') + get isLocked() { + return (this.wasReset || !this.userConfigured) && this.lockDefaults; + } + + /** + * Returns true if the user has configured the setting on their + * own or if the default value is being used. + */ + @HostBinding('class.changed') + get userConfigured() { + return this.setting?.Value !== undefined; + } + + /** + * Returns true if the setting is dirty. That is, the user + * has changed the setting in the view but it has not yet + * been saved. + */ + @HostBinding('class.dirty') + get dirty() { + if (typeof this._currentValue !== 'object') { + return this._currentValue !== this._savedValue; + } + // JSON object (OptionType.StringArray) require will + // not be the same reference so we need to compare their + // string representations. That's a bit more costly but should + // still be fast enough. + // TODO(ppacher): calculate this only when required. + return JSON.stringify(this._currentValue) !== JSON.stringify(this._savedValue) + } + + /** + * Returns true if the setting is pristine. That is, the + * settings default value is used and the user has not yet + * changed the value inside the view. + */ + @HostBinding('class.pristine') + get pristine() { + return !this.dirty && !this.userConfigured + } + + /** A list of buttons for the tip-up */ + sfngTipUpButtons: Button[] = []; + + /** + * Unlock the setting if it is locked. Unlocking will + * emit the default value to be safed for the setting. + */ + unlock() { + if (!this.isLocked || !this.setting) { + return; + } + + this._touched = true; + this.wasReset = false; + let value = this.defaultValue; + + if (this.stackable) { + // TODO(ppacher): fix this one once string[] options can be + // stackable + value = [] as SettingValueType; + } + + this.updateValue(value, true); + // update the settings value now so the UI + // responds immediately. + this.setting!.Value = value; + } + + /** True if the current setting is stackable */ + get stackable() { + return !!this.setting?.Annotations[WellKnown.Stackable]; + } + + /** Wether or not stackable values should be shown right now */ + get showStackable() { + return this.stackable && this.displayStackable; + } + + /** + * @private + * Toggle Whether or not the help text is displayed + */ + toggleHelp() { + this.showHelp = !this.showHelp; + } + + /** + * @private + * Toggle Whether or not the setting is currently locked. + */ + toggleLock() { + if (this.isLocked) { + this.unlock(); + return; + } + + this.resetValue(); + } + + /** + * @private + * Closes the help dialog. + */ + closeHelpDialog() { + this.helpDialogRef?.close(); + } + + @ViewChild(NgModel, { static: false }) + model: NgModel | null = null; + + /** + * The actual setting that should be managed. + * The setter also updates the "currently" used + * value (which is either user configured or + * the default). See {@property userConfigured}. + */ + @Input() + set setting(s: S | null) { + this.sfngTipUpButtons = []; + + this._setting = s; + if (!s) { + this._currentValue = null; + return; + } + + if (this._setting?.Help) { + this.sfngTipUpButtons = [ + { + name: 'Show More', + action: { + ID: '', + Text: '', + Type: 'ui', + Run: async () => { + if (!this.helpTemplate) { + return; + } + + // close any existing help dialog for THIS setting. + if (!!this.helpDialogRef) { + this.helpDialogRef.close(); + } + + // Create a new dialog form the helpTemplate + const portal = new TemplatePortal(this.helpTemplate, this.viewRef); + const ref = this.dialog.create(portal, { + // we don't use a backdrop and make the dialog dragable so the user can + // move it somewhere else and keep it open while configuring the setting. + backdrop: false, + dragable: true, + }); + + // make sure we reset the helpDialogRef to null once it get's clsoed. + this.helpDialogRef = ref; + this.helpDialogRef.onClose.subscribe(() => { + // but only if helpDialogRef still points to the same + // dialog reference. Otherwise we got closed because the user + // opened a new one and helpDialogRef already points to the new + // dialog. + if (this.helpDialogRef === ref) { + this.helpDialogRef = null; + } + }); + }, + Payload: undefined, + }, + }, + ] + } + this.updateActualValue(); + } + get setting(): S | null { + return this._setting; + } + + /** + * The defaultValue input allows to overwrite the default + * value of the setting. + */ + @Input() + set defaultValue(val: SettingValueType) { + this._defaultValue = val; + this.updateActualValue(); + } + + get defaultValue() { + // Return cached value. + if (this._defaultValue !== null) { + return this._defaultValue; + } + + // Stackable options are displayed differently. + if (this.stackable) { + if (this.setting?.GlobalDefault === undefined && this.setting?.DefaultValue !== null) { + return this.setting?.DefaultValue; + } + return [] as SettingValueType; + } + + // Return global, then default value. + if (this.setting?.GlobalDefault !== undefined) { + return this.setting.GlobalDefault + } + return this.setting?.DefaultValue + } + + /* An optional default value overwrite */ + _defaultValue: SettingValueType | null = null; + + /* Whether or not the setting has been saved */ + saved = true; + + /* The settings value, updated by the setting() setter */ + _setting: S | null = null; + + /* The currently configured value. Updated by the setting() setter */ + _currentValue: SettingValueType | null = null; + + /* The currently saved value. Updated by the setting() setter */ + _savedValue: SettingValueType | null = null; + + /* Used to cache the value of a basic-setting because we only want to save that on blur */ + _basicSettingsValueCache: SettingValueType | null = null + + /** Whether or not the network rating system is enabled. */ + networkRatingEnabled$ = this.configService.networkRatingEnabled$; + + get expertiseLevel() { + return this.expertiseService.change; + } + + constructor( + private expertiseService: ExpertiseService, + private configService: ConfigService, + private portapi: PortapiService, + private dialog: SfngDialogService, + private changeDetectorRef: ChangeDetectorRef, + private actionIndicator: ActionIndicatorService, + private spn: SPNService, + private viewRef: ViewContainerRef, + private destryoRef: DestroyRef, + ) { } + + ngOnInit() { + this.triggerSave + .pipe( + debounceTime(500), + takeUntilDestroyed(this.destryoRef), + ) + .subscribe(() => this.emitSaveRequest()) + + // watch the SPN user profile so we know which feature_ids + // are available for the user. + this.spn.profile$ + .pipe(takeUntilDestroyed(this.destryoRef)) + .subscribe((profile: UserProfile | null) => { + let value = this.setting?.Annotations[WellKnown.RequiresFeatureID] + if (value === undefined) { + this._upgradeRequired = false; + } else { + if (!Array.isArray(value)) { + value = [value]; + } + + this._upgradeRequired = value.some(val => !(profile?.current_plan?.feature_ids || []).includes(val)) + } + + this.changeDetectorRef.markForCheck(); + }) + } + + /** + * @private + * Resets the value of setting by discarding any user + * configured values and reverting back to the default + * value. + */ + resetValue() { + if (!this._setting) { + return; + } + this._touched = true; + + this._currentValue = this.defaultValue; + this.wasReset = true; + + this.triggerSave.next(); + } + + /** + * @private + * Aborts/reverts the current change to the value that's + * already saved. + */ + abortChange() { + this._currentValue = this._savedValue; + this._touched = true; + this._rejected = null; + } + + /** + * @private + * Update the current value by applying a quick-setting. + * + * @param qs The quick-settting to apply + */ + applyQuickSetting(qs: QuickSetting>) { + if (this.disabled) { + return; + } + + const value = applyQuickSetting(this._currentValue, qs); + if (value === null) { + return; + } + + this.updateValue(value, true); + } + + openAccountDetails() { + this.dialog.create(SPNAccountDetailsComponent, { + autoclose: true, + backdrop: 'light' + }) + } + + restartNow() { + if (this._setting?.RequiresRestart) { + this.dialog.confirm({ + header: 'Restart Portmaster', + message: 'Do you want to restart the Portmaster now?', + buttons: [ + { + id: 'no', + text: 'Maybe Later', + class: 'outline', + }, + { + id: 'restart', + text: 'Restart', + class: 'danger' + } + ] + }) + .onAction('restart', () => + this.portapi.restartPortmaster() + .subscribe(this.actionIndicator.httpObserver( + 'Restarting ...', + 'Failed to Restart', + )) + ) + .onAction('no', () => { + this._changeAccepted = false; + this.changeDetectorRef.markForCheck(); + }); + + return; + } + + if (this.uiReloadRequired) { + this.portapi.reloadUI() + .pipe( + tap(() => { + setTimeout(() => window.location.reload(), 1000) + }) + ) + .subscribe(this.actionIndicator.httpObserver( + 'Reloading UI ...', + 'Failed to Reload UI', + )) + } + } + + /** + * Emits a save request to the parent component. + */ + private _saveInterval: any; + private emitSaveRequest() { + const isDefault = this.wasReset; + let value = this._setting!['Value']; + + if (isDefault) { + delete (this._setting!['Value']); + } else { + this._setting!.Value = this._currentValue; + } + + + let wasReset = this.wasReset; + this.wasReset = false; + this._rejected = null; + this._changeAccepted = false; + if (!!this._saveInterval) { + clearTimeout(this._saveInterval); + } + + this.save.next({ + key: this.setting!.Key, + isDefault: isDefault, + value: this._setting!.Value, + rejected: (err: any) => { + this._setting!['Value'] = value; + this._rejected = err; + this.changeDetectorRef.markForCheck(); + }, + accepted: () => { + if (!wasReset) { + this._changeAccepted = true; + // if no restart is required fade the "✔️ Saved" out after + // a few seconds. + if (!this._setting?.RequiresRestart) { + this._saveInterval = setTimeout(() => { + this._changeAccepted = false; + this._saveInterval = null; + this.changeDetectorRef.markForCheck(); + }, 4000); + } + } + + this.changeDetectorRef.markForCheck(); + + } + }) + } + + /** + * @private + * Used in our view as a ngModelChange callback to + * update the value. + * + * @param value The new value as emitted by the view + */ + updateValue(value: SettingValueType, save = false) { + this._touched = true; + + this._changeAccepted = false; + this._rejected = null; + if (!!this._saveInterval) { + clearTimeout(this._saveInterval); + } + + if (save) { + + this._currentValue = value; + this.triggerSave.next(); + } else { + this._basicSettingsValueCache = value; + } + } + + /** + * @private + * A list of quick-settings available for the setting. + * The getter makes sure to always return an array. + */ + get quickSettings(): QuickSetting>[] { + if (!this.setting || !this.setting.Annotations[WellKnown.QuickSetting]) { + return []; + } + + const quickSettings = this.setting.Annotations[WellKnown.QuickSetting]!; + + return Array.isArray(quickSettings) + ? quickSettings + : [quickSettings]; + } + + /** + * Determine the current, actual value of the setting + * by taking the settings Value, default Value or global + * default into account. + */ + private updateActualValue() { + if (!this.setting) { + return + } + + this.wasReset = false; + + const s = this.setting; + + const value = s.Value === undefined + ? this.defaultValue + : s.Value; + + + this._currentValue = value; + this._savedValue = value; + this._basicSettingsValueCache = value; + } +} diff --git a/desktop/angular/src/app/shared/config/generic-setting/index.ts b/desktop/angular/src/app/shared/config/generic-setting/index.ts new file mode 100644 index 00000000..0fbe8492 --- /dev/null +++ b/desktop/angular/src/app/shared/config/generic-setting/index.ts @@ -0,0 +1 @@ +export * from './generic-setting'; diff --git a/desktop/angular/src/app/shared/config/import-dialog/cursor.ts b/desktop/angular/src/app/shared/config/import-dialog/cursor.ts new file mode 100644 index 00000000..1ab638ee --- /dev/null +++ b/desktop/angular/src/app/shared/config/import-dialog/cursor.ts @@ -0,0 +1,90 @@ +// Credit to Liam (Stack Overflow) +// https://stackoverflow.com/a/41034697/3480193 +export class Cursor { + static getCurrentCursorPosition(parentElement: Node) { + var selection = window.getSelection(), + charCount = -1, + node; + + if (selection?.focusNode) { + if (Cursor._isChildOf(selection.focusNode, parentElement)) { + node = selection.focusNode; + charCount = selection.focusOffset; + + while (node) { + if (node === parentElement) { + break; + } + + if (node.previousSibling) { + node = node.previousSibling; + charCount += node.textContent?.length || 0 + } else { + node = node.parentNode; + if (node === null) { + break; + } + } + } + } + } + + return charCount; + } + + static setCurrentCursorPosition(chars: number, element: Node) { + if (chars >= 0) { + var selection = window.getSelection(); + + let range = Cursor._createRange(element, { count: chars }); + + if (range) { + range.collapse(false); + selection?.removeAllRanges(); + selection?.addRange(range); + } + } + } + + static _createRange(node: Node, chars: { count: number }, range?: Range): Range { + if (!range) { + range = document.createRange() + range.selectNode(node); + range.setStart(node, 0); + } + + if (chars.count === 0) { + range.setEnd(node, chars.count); + } else if (node && chars.count > 0) { + if (node.nodeType === Node.TEXT_NODE) { + if (node.textContent!.length < chars.count) { + chars.count -= node.textContent!.length; + } else { + range.setEnd(node, chars.count); + chars.count = 0; + } + } else { + for (var lp = 0; lp < node.childNodes.length; lp++) { + range = Cursor._createRange(node.childNodes[lp], chars, range); + + if (chars.count === 0) { + break; + } + } + } + } + + return range; + } + + static _isChildOf(node: Node, parentElement: Node) { + while (node !== null) { + if (node === parentElement) { + return true; + } + node = node.parentNode!; + } + + return false; + } +} diff --git a/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.html b/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.html new file mode 100644 index 00000000..c55709d4 --- /dev/null +++ b/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.html @@ -0,0 +1,99 @@ +
+

+ Import {{ dialogRef.data.type === "setting" ? "Settings" : "Profile" }} +

+ + +
+ +Please paste the "Export Content" or use "Choose File" to select one from + your hard disk. + +

+
+
+ Configuration + +
+
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+
+
+ +
+ Warning + +
+ + + + {{ errorMessage }} +
+ +
    +
  • + This export contains unknown settings. To import it, you must enable + "Allow unknown settings". +
  • + +
  • + {{ + dialogRef.data.type === "setting" + ? "This export will overwrite settings that have been changed by you." + : "This export will overwrite an existing profile." + }} + + + And deletes {{ count }} previously merged profile{{ count > 1 ? 's' : '' }} + +
  • + +
  • + This export will require a restart of the Portmaster to take effect. +
  • +
+
+ +
+ + + +
+ + \ No newline at end of file diff --git a/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.ts b/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.ts new file mode 100644 index 00000000..57a5713c --- /dev/null +++ b/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.ts @@ -0,0 +1,201 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + ViewChild, + inject, +} from '@angular/core'; +import { ImportResult, PortapiService, ProfileImportResult } from '@safing/portmaster-api'; +import { SFNG_DIALOG_REF, SfngDialogRef } from '@safing/ui'; +import { ActionIndicatorService } from '../../action-indicator'; +import { getSelectionOffset, setSelectionOffset } from './selection'; +import { Observable } from 'rxjs'; + +export interface ImportConfig { + key: string; + type: 'setting' | 'profile'; +} + +@Component({ + templateUrl: './import-dialog.component.html', + styles: [ + ` + :host { + @apply flex flex-col gap-2 overflow-hidden; + min-height: 24rem; + min-width: 24rem; + max-height: 40rem; + max-width: 40rem; + } + `, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ImportDialogComponent { + readonly dialogRef: SfngDialogRef< + ImportDialogComponent, + unknown, + ImportConfig + > = inject(SFNG_DIALOG_REF); + + private readonly portapi = inject(PortapiService); + private readonly uai = inject(ActionIndicatorService); + private readonly cdr = inject(ChangeDetectorRef); + + @ViewChild('codeBlock', { static: true, read: ElementRef }) + codeBlockElement!: ElementRef; + + result: ImportResult | ProfileImportResult | null = null; + reset = false; + allowUnknown = false; + triggerRestart = false; + allowReplace = false; + + get replacedProfiles() { + if (this.result === null) { + return [] + } + + if ('replacesProfiles' in this.result) { + return this.result.replacesProfiles || []; + } + + return []; + } + + errorMessage: string = ''; + + get scope() { + return this.dialogRef.data; + } + + onBlur() { + const text = this.codeBlockElement.nativeElement.innerText; + this.updateAndValidate(text); + } + + onPaste(event: ClipboardEvent) { + event.stopPropagation(); + event.preventDefault(); + + // Get pasted data via clipboard API + const clipboardData = event.clipboardData || (window as any).clipboardData; + const text = clipboardData.getData('Text'); + + this.updateAndValidate(text); + } + + import() { + const text = this.codeBlockElement.nativeElement.innerText; + + let saveFunc: Observable; + + if (this.dialogRef.data.type === 'setting') { + saveFunc = this.portapi.importSettings( + text, + this.dialogRef.data.key, + 'text/yaml', + this.reset, + this.allowUnknown + ); + } else { + saveFunc = this.portapi.importProfile( + text, + 'text/yaml', + this.reset, + this.allowUnknown, + this.allowReplace + ); + } + + saveFunc.subscribe({ + next: (result) => { + let msg = ''; + if (result.restartRequired) { + if (this.triggerRestart) { + this.portapi.restartPortmaster().subscribe(); + msg = 'Portmaster will be restarted now.'; + } else { + msg = 'Please restart Portmaster to apply the new settings.'; + } + } + + this.uai.success('Settings Imported Successfully', msg); + this.dialogRef.close(); + }, + error: (err) => { + this.uai.error( + 'Failed To Import Settings', + this.uai.getErrorMessgae(err) + ); + }, + }); + } + + updateAndValidate(content: string) { + const [start, end] = getSelectionOffset( + this.codeBlockElement.nativeElement + ); + + const p = (window as any).Prism; + const blob = p.highlight(content, p.languages.yaml, 'yaml'); + this.codeBlockElement.nativeElement.innerHTML = blob; + + setSelectionOffset(this.codeBlockElement.nativeElement, start, end); + + if (content === '') { + return; + } + + window.getSelection()?.removeAllRanges(); + + let validateFunc: Observable; + + if (this.dialogRef.data.type === 'setting') { + validateFunc = this.portapi.validateSettingsImport( + content, + this.dialogRef.data.key, + 'text/yaml' + ); + } else { + validateFunc = this.portapi.validateProfileImport(content, 'text/yaml'); + } + + validateFunc.subscribe({ + next: (result) => { + this.result = result; + this.errorMessage = ''; + + this.cdr.markForCheck(); + }, + error: (err) => { + const msg = this.uai.getErrorMessgae(err); + this.errorMessage = msg; + this.result = null; + + this.cdr.markForCheck(); + }, + }); + } + + loadFile(event: Event) { + const file: File = (event.target as any).files[0]; + if (!file) { + this.updateAndValidate(''); + + return; + } + + const reader = new FileReader(); + + reader.onload = (data) => { + (event.target as any).value = ''; + + let content = (data.target as any).result; + this.updateAndValidate(content); + }; + + reader.readAsText(file); + } +} diff --git a/desktop/angular/src/app/shared/config/import-dialog/selection.ts b/desktop/angular/src/app/shared/config/import-dialog/selection.ts new file mode 100644 index 00000000..e7018115 --- /dev/null +++ b/desktop/angular/src/app/shared/config/import-dialog/selection.ts @@ -0,0 +1,185 @@ +/** return true if node found */ +function searchNode( + container: Node, + startNode: Node, + predicate: (node: Node) => boolean, + excludeSibling?: boolean, +): boolean { + if (predicate(startNode as Text)) { + return true + } + + for (let i = 0, len = startNode.childNodes.length; i < len; i++) { + if (searchNode(startNode, startNode.childNodes[i], predicate, true)) { + return true + } + } + + if (!excludeSibling) { + let parentNode = startNode + while (parentNode && parentNode !== container) { + let nextSibling = parentNode.nextSibling + while (nextSibling) { + if (searchNode(container, nextSibling, predicate, true)) { + return true + } + nextSibling = nextSibling.nextSibling + } + parentNode = parentNode.parentNode! + } + } + + return false +} + +function createRange(container: Node, start: number, end: number): Range { + let startNode: any; + + searchNode(container, container, node => { + if (node.nodeType === Node.TEXT_NODE) { + const dataLength = (node as Text).data.length + if (start <= dataLength) { + startNode = node + return true + } + start -= dataLength + end -= dataLength + } + + return false + }) + + let endNode: any; + + if (startNode) { + searchNode(container, startNode, node => { + if (node.nodeType === Node.TEXT_NODE) { + const dataLength = (node as Text).data.length + if (end <= dataLength) { + endNode = node + return true + } + end -= dataLength + } + + return false + }) + } + + const range = document.createRange() + if (startNode) { + if (start < startNode.data.length) { + range.setStart(startNode, start) + } else { + range.setStartAfter(startNode) + } + } else { + if (start === 0) { + range.setStart(container, 0) + } else { + range.setStartAfter(container) + } + } + + if (endNode) { + if (end < endNode.data.length) { + range.setEnd(endNode, end) + } else { + range.setEndAfter(endNode) + } + } else { + if (end === 0) { + range.setEnd(container, 0) + } else { + range.setEndAfter(container) + } + } + + return range +} + +export function setSelectionOffset(node: Node, start: number, end: number) { + const range = createRange(node, start, end) + const selection = window.getSelection()! + selection.removeAllRanges() + selection.addRange(range) +} + + +function getAbsoluteOffset(container: Node, offset: number) { + if (container.nodeType === Node.TEXT_NODE) { + return offset + } + + let absoluteOffset = 0 + for (let i = 0, len = Math.min(container.childNodes.length, offset); i < len; i++) { + const childNode = container.childNodes[i] + searchNode(childNode, childNode, node => { + if (node.nodeType === Node.TEXT_NODE) { + absoluteOffset += (node as Text).data.length + } + return false + }) + } + + return absoluteOffset +} + +export function getSelectionOffset(container: Node): [number, number] { + let start = 0 + let end = 0 + + const selection = window.getSelection()! + for (let i = 0, len = selection.rangeCount; i < len; i++) { + const range = selection.getRangeAt(i) + if (range.intersectsNode(container)) { + const startNode = range.startContainer + searchNode(container, container, node => { + if (startNode === node) { + start += getAbsoluteOffset(node, range.startOffset) + return true + } + + const dataLength = node.nodeType === Node.TEXT_NODE + ? (node as Text).data.length + : 0 + + start += dataLength + end += dataLength + + return false + }) + + const endNode = range.endContainer + searchNode(container, startNode, node => { + if (endNode === node) { + end += getAbsoluteOffset(node, range.endOffset) + return true + } + + const dataLength = node.nodeType === Node.TEXT_NODE + ? (node as Text).data.length + : 0 + + end += dataLength + + return false + }) + + break + } + } + + return [start, end] +} + +export function getInnerText(container: Node): string { + const buffer: any = [] + searchNode(container, container, node => { + if (node.nodeType === Node.TEXT_NODE) { + buffer.push((node as Text).data) + } + return false + }) + return buffer.join('') +} diff --git a/desktop/angular/src/app/shared/config/index.ts b/desktop/angular/src/app/shared/config/index.ts new file mode 100644 index 00000000..d71f0297 --- /dev/null +++ b/desktop/angular/src/app/shared/config/index.ts @@ -0,0 +1,8 @@ +export * from './basic-setting'; +export * from './config-settings'; +export * from './config.module'; +export * from './filter-lists'; +export * from './generic-setting'; +export * from './ordererd-list'; +export * from './rule-list'; +export * from './safe.pipe'; diff --git a/desktop/angular/src/app/shared/config/ordererd-list/index.ts b/desktop/angular/src/app/shared/config/ordererd-list/index.ts new file mode 100644 index 00000000..e8849b33 --- /dev/null +++ b/desktop/angular/src/app/shared/config/ordererd-list/index.ts @@ -0,0 +1,2 @@ +export { OrderedListComponent } from './ordered-list'; +export { OrderedListItemComponent } from './item'; \ No newline at end of file diff --git a/desktop/angular/src/app/shared/config/ordererd-list/item.html b/desktop/angular/src/app/shared/config/ordererd-list/item.html new file mode 100644 index 00000000..8550145b --- /dev/null +++ b/desktop/angular/src/app/shared/config/ordererd-list/item.html @@ -0,0 +1,14 @@ +
+ + {{value}} + + + + + + +
+ + +
+
diff --git a/desktop/angular/src/app/shared/config/ordererd-list/item.scss b/desktop/angular/src/app/shared/config/ordererd-list/item.scss new file mode 100644 index 00000000..169a61c5 --- /dev/null +++ b/desktop/angular/src/app/shared/config/ordererd-list/item.scss @@ -0,0 +1,56 @@ +:host { + @apply flex outline-none; + @apply space-x-2; + + &>* { + @apply rounded; + @apply bg-gray-300; + } +} + +div.value { + @apply border-gray-500 border; + @apply p-1; + @apply px-2; + + &.edit { + @apply p-0; + @apply bg-gray-400; + + input { + margin: 0; + width: auto; + flex-grow: 1; + border: none; + @apply shadow-none; + } + + input:focus+.buttons { + @apply bg-gray-500 border-gray-600 bg-opacity-75 border-opacity-75; + } + } + + flex-grow : 1; + display : flex; + justify-content: space-between; + align-items : center; + + .buttons { + flex-shrink: 0; + height: 100%; + width: 4rem; + @apply flex items-center justify-evenly; + + fa-icon { + cursor: pointer; + @apply text-primary; + @apply p-1; + opacity: 0.7; + font-size: 0.6rem; + + &:hover { + opacity: 1; + } + } + } +} diff --git a/desktop/angular/src/app/shared/config/ordererd-list/item.ts b/desktop/angular/src/app/shared/config/ordererd-list/item.ts new file mode 100644 index 00000000..eefb4e3b --- /dev/null +++ b/desktop/angular/src/app/shared/config/ordererd-list/item.ts @@ -0,0 +1,87 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +@Component({ + selector: 'app-ordered-list-item', + templateUrl: './item.html', + styleUrls: ['./item.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class OrderedListItemComponent implements OnInit { + @Input() + set readonly(v: any) { + this._readonly = coerceBooleanProperty(v); + } + get readonly() { + return this._readonly; + } + private _readonly = false; + + @Input() + set value(v: string) { + this._value = v; + this._savedValue = v; + } + get value() { + return this._value; + } + _value = ''; + + private _savedValue = ''; + + @Output() + readonly valueChange = new EventEmitter(); + + @Output() + readonly delete = new EventEmitter(); + + @Input() + set edit(v: any) { + this._edit = coerceBooleanProperty(v); + } + get edit() { + return this._edit; + } + _edit = false; + + @Output() + readonly editChange = new EventEmitter(); + + ngOnInit() { + if (this._value === '' && this._savedValue === '') { + this.edit = true; + } + } + + toggleEdit() { + const wasEdit = this._edit; + this._edit = !wasEdit; + this.editChange.next(this._edit); + + if (!wasEdit) { + return; + } + + if (this._value !== this._savedValue) { + this._value = this._value.trim() + + this.valueChange.next(this.value); + this._savedValue = this._value; + } + this.changeDetectorRef.markForCheck(); + } + + reset() { + if (this._edit) { + if (this._value !== '' || this._savedValue !== '') { + this._value = this._savedValue; + this.changeDetectorRef.markForCheck(); + return; + } + } + + this.delete.next(); + } + + constructor(private changeDetectorRef: ChangeDetectorRef) { } +} diff --git a/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.html b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.html new file mode 100644 index 00000000..fa043cb5 --- /dev/null +++ b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.html @@ -0,0 +1,23 @@ +
+
+ + + + + +
+
+ +
+ +
diff --git a/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.scss b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.scss new file mode 100644 index 00000000..d4c1c086 --- /dev/null +++ b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.scss @@ -0,0 +1,77 @@ +:host { + outline: none; +} + +.item, +.cdk-drag-preview { + display: flex; + align-items: center; + padding: 3px; + + fa-icon { + cursor: pointer; + @apply text-tertiary; + @apply text-lg; + @apply mr-2; + } + + app-ordered-list-item { + flex-grow: 1; + } +} + +.cdk-drag-placeholder { + left: -4px; + padding: 1px; + padding-left: 4px; +} + +// TODO(ppacher9): move this transition to a mixin +.list-items.cdk-drop-list-dragging .list:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-preview { + left: -4px; + padding: 1px; + padding-left: 4px; +} + +.button-list { + @apply mt-2; + @apply ml-8; +} + +.new-entry { + position: relative; + cursor: pointer; + @apply w-full; + @apply rounded; + @apply p-1; + @apply border-2; + @apply border-dashed; + @apply border-buttons-light; + @apply bg-background; + @apply text-secondary; + + span { + @apply font-medium; + } + + fa-icon { + font-size: 1rem; + } + + &:hover { + @apply text-primary; + @apply bg-cards-secondary; + + span { + @apply text-primary; + } + } + + display : flex; + align-items : center; + justify-content: center; +} diff --git a/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.ts b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.ts new file mode 100644 index 00000000..0655ccb5 --- /dev/null +++ b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.ts @@ -0,0 +1,111 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, HostBinding, HostListener, Input } from "@angular/core"; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + + +@Component({ + selector: 'app-ordered-list', + templateUrl: './ordered-list.html', + styleUrls: ['./ordered-list.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => OrderedListComponent), + multi: true, + } + ] +}) +export class OrderedListComponent implements ControlValueAccessor { + @HostBinding('tabindex') + readonly tabindex = 0; + + @HostListener('blur') + onBlur() { + this.onTouch(); + } + + @Input() + set readonly(v: any) { + this._readonly = coerceBooleanProperty(v); + } + get readonly() { + return this._readonly; + } + _readonly = false; + + @Input() + set fixedOrder(v: any) { + this._fixedOrder = coerceBooleanProperty(v); + } + get fixedOrder() { + return this._fixedOrder; + } + private _fixedOrder = false; + + entries: string[] = []; + + constructor(private changeDetector: ChangeDetectorRef) { } + + updateValue(index: number, newValue: string) { + // we need to make a new object copy here. + this.entries = [ + ...this.entries, + ]; + + this.entries[index] = newValue; + this.onChange(this.entries); + } + + deleteEntry(index: number) { + this.entries = [...this.entries]; + this.entries.splice(index, 1); + this.onChange(this.entries); + } + + addEntry() { + // if there's already one empty entry abort + if (this.entries.some(e => e.trim() === '')) { + return; + } + + this.entries = [...this.entries]; + this.entries.push(''); + //this.onChange(this.entries); + } + + writeValue(value: string[]) { + this.entries = value; + + this.changeDetector.markForCheck(); + } + + onChange = (_: string[]): void => { }; + registerOnChange(fn: (value: string[]) => void) { + this.onChange = fn; + } + + onTouch = (): void => { }; + registerOnTouched(fn: () => void) { + this.onTouch = fn; + } + + drop(event: CdkDragDrop) { + if (this._readonly) { + return; + } + + // create a copy of the array + this.entries = [...this.entries]; + moveItemInArray(this.entries, event.previousIndex, event.currentIndex); + + this.changeDetector.markForCheck(); + this.onChange(this.entries); + } + + trackBy(idx: number, value: string) { + return `${value}`; + } +} + diff --git a/desktop/angular/src/app/shared/config/rule-list/index.ts b/desktop/angular/src/app/shared/config/rule-list/index.ts new file mode 100644 index 00000000..a2d41fde --- /dev/null +++ b/desktop/angular/src/app/shared/config/rule-list/index.ts @@ -0,0 +1,2 @@ +export * from './list-item'; +export * from './rule-list'; diff --git a/desktop/angular/src/app/shared/config/rule-list/list-item.html b/desktop/angular/src/app/shared/config/rule-list/list-item.html new file mode 100644 index 00000000..34eeae30 --- /dev/null +++ b/desktop/angular/src/app/shared/config/rule-list/list-item.html @@ -0,0 +1,29 @@ +
+ + {{ symbolMap["+"] }} + {{ symbolMap["-"] }} + + + + + {{ symbolMap["+"] }} + {{ symbolMap["-"] }} + + +
+
+ + {{ display }} + + + + + + +
+ + + +
+ +
diff --git a/desktop/angular/src/app/shared/config/rule-list/list-item.scss b/desktop/angular/src/app/shared/config/rule-list/list-item.scss new file mode 100644 index 00000000..814d311b --- /dev/null +++ b/desktop/angular/src/app/shared/config/rule-list/list-item.scss @@ -0,0 +1,65 @@ +:host { + display: flex; + outline: none; + @apply space-x-2; + + &>* { + @apply rounded; + @apply bg-gray-300; + } +} + +div.action { + @apply border-gray-500 border; + flex-shrink: 0; + min-width: 6rem; + text-align: center; +} + +div.value { + @apply border-gray-500 border; + @apply p-1.5; + @apply px-2; + + &.edit { + @apply p-0; + @apply bg-gray-400; + + input { + margin: 0; + width: auto; + height: 100%; + flex-grow: 1; + border: none; + @apply shadow-none; + } + + input:focus+.buttons { + @apply bg-gray-500 border-gray-600 bg-opacity-75 border-opacity-75; + } + } + + flex-grow : 1; + display : flex; + justify-content: space-between; + align-items : center; + + .buttons { + flex-shrink: 0; + height: 100%; + width: 4rem; + @apply flex items-center justify-evenly; + + fa-icon { + cursor: pointer; + @apply text-primary; + @apply p-1; + opacity: 0.7; + font-size: 0.6rem; + + &:hover { + opacity: 1; + } + } + } +} diff --git a/desktop/angular/src/app/shared/config/rule-list/list-item.ts b/desktop/angular/src/app/shared/config/rule-list/list-item.ts new file mode 100644 index 00000000..09a6beaf --- /dev/null +++ b/desktop/angular/src/app/shared/config/rule-list/list-item.ts @@ -0,0 +1,221 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnInit, Output } from '@angular/core'; +import { fadeInAnimation, fadeOutAnimation } from '../../animations'; + +@Component({ + selector: 'app-rule-list-item', + templateUrl: 'list-item.html', + styleUrls: ['list-item.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInAnimation, + fadeOutAnimation + ] +}) +export class RuleListItemComponent implements OnInit { + /** The host element is going to fade in/out */ + @HostBinding('@fadeIn') + @HostBinding('@fadeOut') + readonly animation = true; + + @Input() + symbolMap: { [key: string]: string } = {} + + /** + * The current value (rule) displayed by this component. + * Supports two-way bindings. + */ + @Input() + set value(v: string) { + this.updateValue(v); + this._savedValue = this._value; + } + private _value = ''; + + /** The last actually saved value of this rule. Required for resets */ + private _savedValue = ''; + + /** + * Emits whenever the rule value changes. + * Supports two-way-bindings on ([value]) + */ + @Output() + valueChange = new EventEmitter(); + + /** Whether or not the rule list item is selected */ + @Input() + set selected(v: any) { + this._selected = coerceBooleanProperty(v) + } + get selected() { + return this._selected; + } + private _selected = false; + + @Output() + selectedChange = new EventEmitter(); + + /** + * Whether or not the component is in edit mode. + * Supports two-way-bindings on ([edit]) + */ + @Input() + set edit(v: any) { + this._edit = coerceBooleanProperty(v); + } + get edit() { + return this._edit; + } + private _edit: boolean = false; + + /** + * Emits whenever the component switch to or away from edit + * mode. + * Supports two-way-bindings on ([edit]) + */ + @Output() + editChange = new EventEmitter(); + + /** + * Whether or not the component should be in read-only mode. + */ + @Input() + set readonly(v: any) { + this._readonly = coerceBooleanProperty(v); + } + get readonly() { + return this._readonly; + } + private _readonly: boolean = false; + + /** + * Emits when the user presses the delete button of + * this rule component. + */ + @Output() + delete = new EventEmitter(); + + /** @private Whether or not this rule is a "Allow" rule - we default to allow since this is what most rules are used for */ + isAllow = true; + + /** @private Whether or not this rule is a "Deny" rule */ + isBlock = false; + + /** @private the actually displayed rule value (without the verdict) */ + display = ''; + + /** @private the character representation of the current verdict */ + get currentAction() { + if (this.isBlock) { + return '-'; + } + if (this.isAllow) { + return '+'; + } + return ''; + } + + constructor(private cdr: ChangeDetectorRef) { } + + ngOnInit() { + // new entries always start in edit mode + if (!this.isAllow && !this.isBlock) { + this._edit = true; + } + } + + /** + * @private + * Toggle between edit and view mode. When switching from + * edit to view mode, the current value is emitted to the + * parent element in case it has been changed. + */ + toggleEdit() { + if (this._edit) { + // do nothing if the rule is obviously invalid (no verdict or value). + if (this.display === '' || !(this.isAllow || this.isBlock)) { + return; + } + + if (this._value !== this._savedValue) { + this.valueChange.next(this._value); + } + } + + this._edit = !this._edit; + this.editChange.next(this._edit); + } + + toggleSelection() { + this.selected = !this.selected; + this.selectedChange.next(this.selected); + + this.cdr.markForCheck(); + } + + /** + * @private + * Sets the new rule action. Used as a callback in the drop-down. + * + * @param action The new action + */ + setAction(action: '+' | '-') { + this.updateValue(`${action} ${this.display}`); + } + + /** + * @private + * Update the actual value of the rule. + * + * @param entity The new rule value + */ + setEntity(entity: string) { + const action = this.isAllow ? '+' : '-'; + this.updateValue(`${action} ${entity}`); + } + + /** + * @private + * + * Reset the value to it's previously saved value if it was changed. + * If the value is unchanged a reset counts as a delete and triggers + * on our delete output. + */ + reset() { + if (this._edit) { + // if the user did not change anything we can immediately + // delete it. + if (this._savedValue !== '') { + this.value = this._savedValue; + this._edit = false; + return; + } + } + + this.delete.next(); + } + + /** + * Updates our internal states to correctly display the rule. + * + * @param v The actual rule value + */ + private updateValue(v: string) { + this._value = v.trim(); + switch (this._value[0]) { + case '+': + this.isAllow = true; + this.isBlock = false; + break; + case '-': + this.isAllow = false; + this.isBlock = true; + break; + default: + // not yet set + this.isBlock = this.isAllow = false; + } + + this.display = this._value.slice(1).trim(); + } +} diff --git a/desktop/angular/src/app/shared/config/rule-list/rule-list.html b/desktop/angular/src/app/shared/config/rule-list/rule-list.html new file mode 100644 index 00000000..3f7115c3 --- /dev/null +++ b/desktop/angular/src/app/shared/config/rule-list/rule-list.html @@ -0,0 +1,46 @@ +
+
+ + + + + +
+
+ +
+
+ No entries available +
+ + +
+ +
+ + + {{ selectedItems.length }} Rule{{ selectedItems.length > 1 ? 's' : ''}} selected + + + + + + + + Remove Rules + Cancel + +
diff --git a/desktop/angular/src/app/shared/config/rule-list/rule-list.scss b/desktop/angular/src/app/shared/config/rule-list/rule-list.scss new file mode 100644 index 00000000..23bf7034 --- /dev/null +++ b/desktop/angular/src/app/shared/config/rule-list/rule-list.scss @@ -0,0 +1,75 @@ +:host { + outline: none; +} + +.item, +.cdk-drag-preview { + display: flex; + align-items: center; + padding: 3px; + + fa-icon { + cursor: pointer; + @apply text-tertiary; + @apply text-lg; + @apply mr-2; + } + + app-rule-list-item { + flex-grow: 1; + } +} + +.cdk-drag-placeholder { + left: -4px; + padding: 1px; + padding-left: 4px; +} + +// TODO(ppacher9): move this transition to a mixin +.list-items.cdk-drop-list-dragging .list:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} + +.cdk-drag-preview { + left: -4px; + padding: 1px; + padding-left: 4px; +} + +.button-list { + @apply mt-2; + @apply ml-8; +} + +.dotted { + @apply w-full; + @apply rounded; + @apply p-1; + @apply border-2; + @apply border-dashed; + @apply border-buttons-light; + @apply bg-background; + @apply text-secondary; + + display: flex; + align-items: center; + justify-content: center; + + span { + @apply font-medium; + } +} + +.new-entry { + cursor: pointer; + + &:hover { + @apply text-primary; + @apply bg-gray-300; + + span { + @apply text-primary; + } + } +} diff --git a/desktop/angular/src/app/shared/config/rule-list/rule-list.ts b/desktop/angular/src/app/shared/config/rule-list/rule-list.ts new file mode 100644 index 00000000..f5ac6c86 --- /dev/null +++ b/desktop/angular/src/app/shared/config/rule-list/rule-list.ts @@ -0,0 +1,226 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, HostBinding, HostListener, Input, QueryList, ViewChildren } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { SfngDialogService } from '@safing/ui'; +import { RuleListItemComponent } from './list-item'; + +@Component({ + selector: 'app-rule-list', + templateUrl: './rule-list.html', + styleUrls: ['./rule-list.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => RuleListComponent), + multi: true, + } + ], +}) +export class RuleListComponent implements ControlValueAccessor { + /** Add the host element into the tab-sequence */ + @HostBinding('tabindex') + readonly tabindex = 0; + + @ViewChildren(RuleListItemComponent) + renderedRules!: QueryList; + + /** A list of selected rule indexes */ + selectedItems: number[] = []; + + /** + * @private + * Mark the component as dirty by calling the onTouch callback of the control-value accessor + */ + @HostListener('blur') + onBlur() { + this.onTouch(); + } + + @Input() + symbolMap = { + '+': 'Allow', + '-': 'Block', + } + + /** + * Whether or not the component should be displayed as read-only. + */ + @Input() + set readonly(v: any) { + this._readonly = coerceBooleanProperty(v); + } + get readonly() { + return this._readonly; + } + private _readonly = false; + + /** + * @private + * The actual rule entries. Displayed as RuleListItemComponent. + */ + entries: string[] = []; + + constructor( + private changeDetector: ChangeDetectorRef, + private dialog: SfngDialogService + ) { } + + /** + * @private + * Update the value of a rule-list entry. Used as a callback function + * for the valueChange output of the RuleListItemComponent. + * + * @param index The index of the rule list entry to update + * @param newValue The new value of the rule + */ + updateValue(index: number, newValue: string) { + // we need create a copy of the actual value as + // the parent component might still have a reference + // to the current values. + this.entries = [ + ...this.entries, + ]; + this.entries[index] = newValue; + + // tell the control that we have a new value + this.onChange(this.entries); + } + + /** + * @private + * Delete a rule list entry. + * + * @param index The index of the rule list entry to delete + */ + deleteEntry(index: number) { + this.entries = [...this.entries]; + this.entries.splice(index, 1); + this.onChange(this.entries); + } + + /** + * @private + * Add a new, empty rule list entry at the end of the + * list. + * + * This is a no-op if there's already an empty item + * available. + */ + addEntry() { + // if there's already one empty entry abort + if (this.entries.some(e => e.trim() === '')) { + return; + } + + this.entries = [...this.entries]; + this.entries.push(''); + } + + /** + * Set a new value for the rule list. This is the + * only way to configure the existing entries and is + * used by the control-value-accessor and ngModel. + * + * @param value The new value set via [ngModel] + */ + writeValue(value: string[]) { + this.entries = value; + + this.changeDetector.markForCheck(); + } + + /** Toggles selection of a rule item */ + selectItem(index: number, selected: boolean) { + if (selected && !this.selectedItems.includes(index)) { + this.selectedItems = [ + ...this.selectedItems, + index, + ] + + return; + } + + if (!selected && this.selectedItems.includes(index)) { + this.selectedItems = this.selectedItems.filter(idx => idx !== index) + + return; + } + } + + /** Removes all selected items after displaying a confirmation dialog. */ + removeSelectedItems() { + this.dialog.confirm({ + buttons: [ + { + id: 'abort', + text: 'Cancel', + class: 'outline' + }, + { + id: 'delete', + text: 'Delete Rules', + class: 'danger' + } + ], + canCancel: true, + caption: 'Caution', + header: 'Rule Deletion', + message: 'Do you want to delete the selected rules' + }) + .onAction('delete', () => { + this.entries = this.entries.filter((_, idx: number) => !this.selectedItems.includes(idx)) + this.abortSelection(); + this.onChange(this.entries); + }) + + } + + /** Aborts the current selection */ + abortSelection() { + this.selectedItems.forEach(itemIdx => this.renderedRules.get(itemIdx)?.toggleSelection()) + this.selectedItems = []; + } + + /** @private onChange callback registered by ngModel and form controls */ + onChange = (_: string[]): void => { }; + + /** Registers the onChange callback and required for the ControlValueAccessor interface */ + registerOnChange(fn: (value: string[]) => void) { + this.onChange = fn; + } + + /** @private onTouch callback registered by ngModel and form controls */ + onTouch = (): void => { }; + + /** Registers the onChange callback and required for the ControlValueAccessor interface */ + registerOnTouched(fn: () => void) { + this.onTouch = fn; + } + + /** + * @private + * Used as a callback for the @angular/cdk drop component + * and used to update the actual order of the entries. + * + * @param event The drop-event + */ + drop(event: CdkDragDrop) { + if (this._readonly) { + return; + } + + // create a copy of the array + this.entries = [...this.entries]; + moveItemInArray(this.entries, event.previousIndex, event.currentIndex); + + this.changeDetector.markForCheck(); + this.onChange(this.entries); + } + + /** @private TrackByFunction for entries */ + trackBy(idx: number, value: string) { + return `${value}`; + } +} diff --git a/desktop/angular/src/app/shared/config/safe.pipe.ts b/desktop/angular/src/app/shared/config/safe.pipe.ts new file mode 100644 index 00000000..0cbf2855 --- /dev/null +++ b/desktop/angular/src/app/shared/config/safe.pipe.ts @@ -0,0 +1,21 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DomSanitizer, SafeHtml, SafeStyle, SafeScript, SafeUrl, SafeResourceUrl } from '@angular/platform-browser'; + +@Pipe({ + name: 'safe' +}) +export class SafePipe implements PipeTransform { + + constructor(protected sanitizer: DomSanitizer) { } + + public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl { + switch (type) { + case 'html': return this.sanitizer.bypassSecurityTrustHtml(value); + case 'style': return this.sanitizer.bypassSecurityTrustStyle(value); + case 'script': return this.sanitizer.bypassSecurityTrustScript(value); + case 'url': return this.sanitizer.bypassSecurityTrustUrl(value); + case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value); + default: throw new Error(`Invalid safe type specified: ${type}`); + } + } +} diff --git a/desktop/angular/src/app/shared/count-indicator/count-indicator.html b/desktop/angular/src/app/shared/count-indicator/count-indicator.html new file mode 100644 index 00000000..fdbb0c22 --- /dev/null +++ b/desktop/angular/src/app/shared/count-indicator/count-indicator.html @@ -0,0 +1,4 @@ +{{ count | prettyCount }} +
+
+
diff --git a/desktop/angular/src/app/shared/count-indicator/count-indicator.module.ts b/desktop/angular/src/app/shared/count-indicator/count-indicator.module.ts new file mode 100644 index 00000000..0961a129 --- /dev/null +++ b/desktop/angular/src/app/shared/count-indicator/count-indicator.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; +import { CountIndicatorComponent } from "./count-indicator"; +import { PrettyCountPipe } from "./count.pipe"; + +@NgModule({ + declarations: [ + CountIndicatorComponent, + PrettyCountPipe, + ], + exports: [ + CountIndicatorComponent, + PrettyCountPipe, + ] +}) +export class CountIndicatorModule { } diff --git a/desktop/angular/src/app/shared/count-indicator/count-indicator.scss b/desktop/angular/src/app/shared/count-indicator/count-indicator.scss new file mode 100644 index 00000000..3d97d2c9 --- /dev/null +++ b/desktop/angular/src/app/shared/count-indicator/count-indicator.scss @@ -0,0 +1,8 @@ +@import '../../../theme/mixins/_pill.scss'; + +:host { + @include pill-container; + @apply pl-2; + @apply bg-buttons-dark; + @apply w-20; +} diff --git a/desktop/angular/src/app/shared/count-indicator/count-indicator.ts b/desktop/angular/src/app/shared/count-indicator/count-indicator.ts new file mode 100644 index 00000000..8c49e098 --- /dev/null +++ b/desktop/angular/src/app/shared/count-indicator/count-indicator.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; + +@Component({ + selector: 'app-count-indicator', + templateUrl: './count-indicator.html', + styleUrls: ['./count-indicator.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CountIndicatorComponent implements OnChanges { + @Input() + count = 0; + + @Input() + countAllowed: number = 0; + + allowedPercentage: number = 0; + + ngOnChanges() { + const ratio = (this.countAllowed / this.count) || 0; + this.allowedPercentage = Math.round(ratio * 100); + } +} diff --git a/desktop/angular/src/app/shared/count-indicator/count.pipe.ts b/desktop/angular/src/app/shared/count-indicator/count.pipe.ts new file mode 100644 index 00000000..69140b3e --- /dev/null +++ b/desktop/angular/src/app/shared/count-indicator/count.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + name: 'prettyCount', + pure: true +}) +export class PrettyCountPipe implements PipeTransform { + transform(value: number) { + if (value > 999) { + const v = Math.floor(value / 1000); + if (value === v * 1000) { + return `${v}k`; + } + return `${v}k+` + } + return `${value}` + } +} diff --git a/desktop/angular/src/app/shared/count-indicator/index.ts b/desktop/angular/src/app/shared/count-indicator/index.ts new file mode 100644 index 00000000..be4276b7 --- /dev/null +++ b/desktop/angular/src/app/shared/count-indicator/index.ts @@ -0,0 +1,2 @@ +export * from './count-indicator'; +export * from './count-indicator.module'; diff --git a/desktop/angular/src/app/shared/country-flag/country-flag.ts b/desktop/angular/src/app/shared/country-flag/country-flag.ts new file mode 100644 index 00000000..df91a3c5 --- /dev/null +++ b/desktop/angular/src/app/shared/country-flag/country-flag.ts @@ -0,0 +1,45 @@ +import { AfterViewInit, Directive, ElementRef, HostBinding, Input, OnChanges, Renderer2, SimpleChanges } from '@angular/core'; + +@Directive({ + selector: 'span[appCountryFlags]', +}) +export class CountryFlagDirective implements AfterViewInit, OnChanges { + private readonly flagDir = "/assets/img/flags/"; + private readonly OFFSET = 127397; + + @HostBinding('style.text-shadow') + textShadow = 'rgba(255, 255, 255, .5) 0px 0px 1px'; + + @Input() + appCountryFlags: string = ''; + + constructor( + private el: ElementRef, + private renderer: Renderer2 + ) { } + + ngOnChanges(changes: SimpleChanges): void { + if (!changes['appCountryFlags'].isFirstChange()) { + this.update(); + } + } + + ngAfterViewInit() { + this.update(); + } + + private update() { + const span = this.el.nativeElement as HTMLSpanElement; + const flag = this.toUnicodeFlag(this.appCountryFlags); + this.renderer.setAttribute(span, 'data-before', flag); + + span.innerHTML = ``; + } + + private toUnicodeFlag(code: string) { + const base = 127462 - 65; + const cc = code.toUpperCase(); + const res = String.fromCodePoint(...cc.split('').map(c => base + c.charCodeAt(0))); + return res; + } +} diff --git a/desktop/angular/src/app/shared/country-flag/country.module.ts b/desktop/angular/src/app/shared/country-flag/country.module.ts new file mode 100644 index 00000000..2acdb3f8 --- /dev/null +++ b/desktop/angular/src/app/shared/country-flag/country.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { CountryFlagDirective } from './country-flag'; + +@NgModule({ + declarations: [ + CountryFlagDirective + ], + exports: [ + CountryFlagDirective, + ] +}) +export class CountryFlagModule { } diff --git a/desktop/angular/src/app/shared/country-flag/index.ts b/desktop/angular/src/app/shared/country-flag/index.ts new file mode 100644 index 00000000..cc7d4306 --- /dev/null +++ b/desktop/angular/src/app/shared/country-flag/index.ts @@ -0,0 +1,2 @@ +export * from './country-flag'; +export * from './country.module'; diff --git a/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.html b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.html new file mode 100644 index 00000000..7b630621 --- /dev/null +++ b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.html @@ -0,0 +1,322 @@ +

+ + + {{ isEditMode ? 'Edit App Profile' : 'Create New App Profile' }} +

+ +
+ + +
+ + Configure basic profile information like the profile name, it's + description and optionally the profile icon. + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ +
+
+ + +
+ The icon must be smaller than 10kB and it's dimensions must not + exceed 512x512 px. Only JPG and PNG files are supported. +
+ + {{ imageError }} +
+ + +
+
+
+ + +
+ This profile will be applied to processes that match one of the + following fingerprints: + +
+ No fingerprints configured. Please press "Add New" to get started. +
+
+
+ + + + + + + + + Tag + + + Command Line + + Environment + + Path + + + + + + + {{ tag.Name }} + + + + + Equals + Prefix + Regex + + + + + +
+
+ + +
+
+ + +
+
+ Select a Profile to copy settings from: +
+ + + + + {{ p.Name }} + + + + +
+ +
+
+ + + + + {{ p.Name }} +
+ +
+
+ + Settings will be copied from all specified profiles in order with + settings from higher profiles taking precedence.
+ Existing settings may be overwritten. +
+
+
+
+
+ +
+ +
+ + +
diff --git a/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.scss b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.scss new file mode 100644 index 00000000..03e9ccea --- /dev/null +++ b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.scss @@ -0,0 +1,29 @@ +:host { + @apply flex flex-col gap-4 max-w-2xl; + min-width: 500px; + width: 60vw; +} + +.tab-content { + @apply flex flex-col gap-4 overflow-x-hidden h-96 pt-2; +} + +.input { + @apply flex flex-col gap-1; + + label { + @apply text-primary uppercase text-xxs relative left-1.5; + } + + input[type="text"] { + @apply border border-gray-500; + + &.ng-invalid.ng-dirty { + @apply border-red-200; + } + } + + input[type="file"] { + display: none; + } +} diff --git a/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.ts b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.ts new file mode 100644 index 00000000..60b5b514 --- /dev/null +++ b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.ts @@ -0,0 +1,393 @@ +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { + ChangeDetectorRef, + Component, + Inject, + OnDestroy, + OnInit, + TrackByFunction, +} from '@angular/core'; +import { + AppProfile, + AppProfileService, + FingerpringOperation, + Fingerprint, + FingerprintType, + PORTMASTER_HTTP_API_ENDPOINT, + PortapiService, + Record, + TagDescription, + mergeDeep, +} from '@safing/portmaster-api'; +import { SFNG_DIALOG_REF, SfngDialogRef, SfngDialogService } from '@safing/ui'; +import { Observable, Subject, map, of, switchMap, takeUntil } from 'rxjs'; +import { ActionIndicatorService } from 'src/app/shared/action-indicator'; + +@Component({ + templateUrl: './edit-profile-dialog.html', + //changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['./edit-profile-dialog.scss'], +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class EditProfileDialog implements OnInit, OnDestroy { + private destory$ = new Subject(); + + profile: Partial = { + ID: '', + Source: 'local', + Name: '', + Description: '', + Icons: [], + Fingerprints: [], + }; + + isEditMode = false; + iconData: string | ArrayBuffer = ''; + iconType: string = ''; + iconChanged = false; + iconObjectURL = ''; + imageError: string | null = null; + + allProfiles: AppProfile[] = []; + + copySettingsFrom: AppProfile[] = []; + + selectedCopyFrom: AppProfile | null = null; + + fingerPrintTypes = FingerprintType; + fingerPrintOperations = FingerpringOperation; + processTags: TagDescription[] = []; + + trackFingerPrint: TrackByFunction = ( + _: number, + fp: Fingerprint + ) => `${fp.Type}-${fp.Key}-${fp.Operation}-${fp.Value}`; + + constructor( + @Inject(SFNG_DIALOG_REF) + private dialgoRef: SfngDialogRef< + EditProfileDialog, + any, + string | null | AppProfile + >, + private profileService: AppProfileService, + private portapi: PortapiService, + private actionIndicator: ActionIndicatorService, + private dialog: SfngDialogService, + private cdr: ChangeDetectorRef, + @Inject(PORTMASTER_HTTP_API_ENDPOINT) private httpAPI: string + ) { } + + ngOnInit(): void { + this.profileService.tagDescriptions().subscribe((result) => { + this.processTags = result; + this.cdr.markForCheck(); + }); + + this.profileService + .watchProfiles() + .pipe(takeUntil(this.destory$)) + .subscribe((profiles) => { + this.allProfiles = profiles; + this.cdr.markForCheck(); + }); + + if (!!this.dialgoRef.data && typeof this.dialgoRef.data === 'string') { + this.isEditMode = true; + this.profileService + .getAppProfile(this.dialgoRef.data) + .subscribe((profile) => { + this.profile = profile; + this.loadIcon(); + }); + } else if ( + !!this.dialgoRef.data && + typeof this.dialgoRef.data === 'object' + ) { + this.profile = this.dialgoRef.data; + this.loadIcon(); + } + } + + private loadIcon() { + if (!this.profile.Icons?.length) { + return; + } + + const firstIcon = this.profile.Icons[0]; + + // get the current icon of the profile + switch (firstIcon.Type) { + case 'database': + this.portapi + .get(firstIcon.Value) + .subscribe((data) => { + this.iconData = data.iconData; + this.iconObjectURL = this.iconData; + this.cdr.markForCheck(); + }); + break; + + case 'api': + this.iconData = `${this.httpAPI}/v1/profile/icon/${firstIcon.Value}`; + this.iconObjectURL = this.iconData; + + break; + + default: + console.error(`Unsupported icon type ${firstIcon.Type}`); + } + + this.cdr.markForCheck(); + } + + ngOnDestroy() { + this.destory$.next(); + this.destory$.complete(); + } + + addFingerprint() { + this.profile.Fingerprints?.push({ + Key: '', + Operation: FingerpringOperation.Equal, + Value: '', + Type: FingerprintType.Path, + }); + } + + removeFingerprint(idx: number) { + this.profile.Fingerprints?.splice(idx, 1); + this.profile.Fingerprints = [...this.profile.Fingerprints!]; + } + + removeCopyFrom(idx: number) { + this.copySettingsFrom.splice(idx, 1); + this.copySettingsFrom = [...this.copySettingsFrom]; + } + + addCopyFrom() { + this.copySettingsFrom = [...this.copySettingsFrom, this.selectedCopyFrom!]; + this.selectedCopyFrom = null; + } + + drop(event: CdkDragDrop) { + // create a copy of the array + this.copySettingsFrom = [...this.copySettingsFrom]; + moveItemInArray( + this.copySettingsFrom, + event.previousIndex, + event.currentIndex + ); + + this.cdr.markForCheck(); + } + + deleteProfile() { + this.dialog + .confirm({ + caption: 'Caution', + header: 'Confirm Profile Deletion', + message: 'Do you want to delete this profile?', + buttons: [ + { + id: 'delete', + class: 'danger', + text: 'Delete', + }, + { + id: 'abort', + class: 'outline', + text: 'Cancel', + }, + ], + }) + .onAction('delete', () => { + this.profileService + .deleteProfile(this.profile as AppProfile) + .subscribe({ + next: () => this.dialgoRef.close('deleted'), + error: (err) => { + this.actionIndicator.error('Failed to delete profile', err); + }, + }); + }); + } + + resetIcon() { + this.iconChanged = true; + this.iconData = ''; + this.iconType = ''; + this.iconObjectURL = ''; + } + + save() { + if (!this.profile.ID) { + this.profile.ID = this.uuidv4(); + } + + if (!this.profile.Source) { + this.profile.Source = 'local'; + } + + let updateIcon: Observable = of(undefined); + + if (this.iconChanged) { + // delete any previously set icon + this.profile.Icons?.forEach((icon) => { + if (icon.Type === 'database') { + this.portapi.delete(icon.Value).subscribe(); + } + + // FIXME(ppacher): we cannot yet delete API based icons ... + }); + + if (this.iconData !== '') { + // save the new icon in the cache database + + // FIXME(ppacher): we currently need to calls because the icon API in portmaster + // does not update the profile but just saves the file and returns the filename. + // So we still need to update the profile manually. + updateIcon = this.profileService + .setProfileIcon(this.iconData, this.iconType) + .pipe( + map(({ filename }) => { + this.profile.Icons = [ + { + Type: 'api', + Value: filename, + Source: 'user', + }, + ]; + }) + ); + + // FIXME(ppacher): reset presentationpath + } else { + // just clear out that there was an icon + this.profile.Icons = []; + } + } + + if (this.profile.Fingerprints!.length > 1) { + this.profile.PresentationPath = ''; + } + const oldConfig = this.profile.Config || {}; + this.profile.Config = {}; + + mergeDeep( + this.profile.Config, + ...[...this.copySettingsFrom.map((p) => p.Config || {}), oldConfig] + ); + + updateIcon + .pipe( + switchMap(() => { + return this.profileService.saveProfile(this.profile as AppProfile); + }) + ) + .subscribe({ + next: () => { + this.actionIndicator.success( + this.profile.Name!, + 'Profile saved successfully' + ); + this.dialgoRef.close('saved'); + }, + error: (err) => { + this.actionIndicator.error('Failed to save profile', err); + }, + }); + } + + abort() { + this.dialgoRef.close('abort'); + } + + fileChangeEvent(fileInput: any) { + this.imageError = null; + this.iconData = ''; + this.iconChanged = true; + + if (fileInput.target.files && fileInput.target.files[0]) { + const max_size = 10 * 1024; + const allowed_types = [ + 'image/png', + 'image/jpeg', + 'image/svg', + 'image/gif', + 'image/tiff', + ]; + const max_height = 512; + const max_width = 512; + const file: File = fileInput.target.files[0]; + + if (file.size > max_size) { + this.imageError = 'Maximum size allowed is ' + max_size / 1000 + 'KB'; + } + + if (!allowed_types.includes(file.type)) { + this.imageError = 'Only JPG, PNG, SVG, GIF or Tiff files are allowed'; + } + + this.iconType = file.type; + + const reader = new FileReader(); + reader.onload = (e: ProgressEvent) => { + const content: ArrayBuffer = e.target!.result! as ArrayBuffer; + const blob = new Blob([content], { type: file.type }); + + const image = new Image(); + image.src = URL.createObjectURL(blob); + this.iconObjectURL = image.src; + + image.onload = (rs: any) => { + const img_height = rs.currentTarget['height']!; + const img_width = rs.currentTarget['width']; + + if (img_height > max_height && img_width > max_width) { + this.imageError = + 'Maximum dimentions allowed ' + + max_height + + '*' + + max_width + + 'px'; + } else { + this.iconData = content; + } + + this.cdr.markForCheck(); + }; + + image.onerror = (err: any) => { + this.actionIndicator.error( + 'Failed to get image', + this.actionIndicator.getErrorMessgae(err) + ); + }; + + this.cdr.markForCheck(); + }; + + reader.onerror = (err: any) => { + this.actionIndicator.error( + 'Failed to get image', + this.actionIndicator.getErrorMessgae(err) + ); + }; + + reader.readAsArrayBuffer(fileInput.target.files[0]); + } + } + + private uuidv4(): string { + if (typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + + // This one is not really random and not RFC compliant but serves enough for fallback + // purposes if the UI is opened in a browser that does not yet support randomUUID + console.warn('Using browser with lacking support for crypto.randomUUID()'); + + return Date.now().toString(36) + Math.random().toString(36).substring(2); + } +} diff --git a/desktop/angular/src/app/shared/edit-profile-dialog/index.ts b/desktop/angular/src/app/shared/edit-profile-dialog/index.ts new file mode 100644 index 00000000..0a4c617d --- /dev/null +++ b/desktop/angular/src/app/shared/edit-profile-dialog/index.ts @@ -0,0 +1 @@ +export * from './edit-profile-dialog'; diff --git a/desktop/angular/src/app/shared/exit-screen/exit-screen.html b/desktop/angular/src/app/shared/exit-screen/exit-screen.html new file mode 100644 index 00000000..cff2a960 --- /dev/null +++ b/desktop/angular/src/app/shared/exit-screen/exit-screen.html @@ -0,0 +1,19 @@ +
+ Tip + + +

Close User Interface

+ + Closing the User Interface does not shut down the Portmaster. You can shut down the Portmaster + in the Settings or the Tray Notifier. + +
+ + + + + +
+
diff --git a/desktop/angular/src/app/shared/exit-screen/exit-screen.scss b/desktop/angular/src/app/shared/exit-screen/exit-screen.scss new file mode 100644 index 00000000..65e5dc8b --- /dev/null +++ b/desktop/angular/src/app/shared/exit-screen/exit-screen.scss @@ -0,0 +1,68 @@ +caption { + @apply text-sm; + opacity : .6; + font-size: .6rem; +} + +.content-wrapper { + display : flex; + flex-direction: column; + align-items : flex-start; + + h1 { + font-size : 0.85rem; + font-weight : 500; + margin-bottom: 1rem; + } + + .message, + h1 { + flex-shrink : 0; + text-overflow: ellipsis; + word-break : normal; + } + + .message { + font-size: 0.75rem; + flex-grow: 1; + opacity : .6; + } + + .close-icon { + position: absolute; + top : 1rem; + right : 1rem; + opacity : .7; + cursor : pointer; + + &:hover { + opacity: 1; + } + } + + .actions { + margin-top : 1rem; + width : 100%; + display : flex; + justify-content: space-between; + align-items : center; + + button { + @apply bg-info-blue; + + &.danger { + @apply bg-info-red; + } + } + + &>span { + display : flex; + align-items: center; + + label { + margin-left: .5rem; + user-select: none; + } + } + } +} diff --git a/desktop/angular/src/app/shared/exit-screen/exit-screen.ts b/desktop/angular/src/app/shared/exit-screen/exit-screen.ts new file mode 100644 index 00000000..3dbda654 --- /dev/null +++ b/desktop/angular/src/app/shared/exit-screen/exit-screen.ts @@ -0,0 +1,52 @@ +import { OverlayRef } from '@angular/cdk/overlay'; +import { Component, Inject, InjectionToken } from '@angular/core'; +import { SfngDialogRef, SFNG_DIALOG_REF } from '@safing/ui'; +import { Observable, of } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { UIStateService } from 'src/app/services'; +import { fadeInAnimation, fadeOutAnimation } from '../animations'; + +export const OVERLAYREF = new InjectionToken('OverlayRef'); + +@Component({ + templateUrl: './exit-screen.html', + styleUrls: ['./exit-screen.scss'], + animations: [ + fadeInAnimation, + fadeOutAnimation, + ] +}) +export class ExitScreenComponent { + constructor( + @Inject(SFNG_DIALOG_REF) private _dialogRef: SfngDialogRef, + private stateService: UIStateService, + ) { } + + /** @private - used as ngModel form the template */ + neveragain: boolean = false; + + closeUI() { + const closeObserver = { + next: () => { + this._dialogRef.close('exit'); + } + } + + let close: Observable = of(null); + if (this.neveragain) { + close = this.stateService.uiState() + .pipe( + map(state => { + state.hideExitScreen = true; + return state; + }), + switchMap(state => this.stateService.saveState(state)), + ) + } + close.subscribe(closeObserver) + } + + cancel() { + this._dialogRef.close() + } +} diff --git a/desktop/angular/src/app/shared/exit-screen/exit.service.ts b/desktop/angular/src/app/shared/exit-screen/exit.service.ts new file mode 100644 index 00000000..c1da5b92 --- /dev/null +++ b/desktop/angular/src/app/shared/exit-screen/exit.service.ts @@ -0,0 +1,146 @@ +import { IntegrationService } from './../../integration/integration'; +import { Injectable, inject } from '@angular/core'; +import { PortapiService } from '@safing/portmaster-api'; +import { SfngDialogService } from '@safing/ui'; +import { BehaviorSubject, merge, of } from 'rxjs'; +import { catchError, debounceTime, distinctUntilChanged, map, skip, switchMap, tap, timeout } from 'rxjs/operators'; +import { UIStateService } from 'src/app/services'; +import { ActionIndicatorService } from '../action-indicator'; +import { ExitScreenComponent } from './exit-screen'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; + +const MessageConnecting = 'Connecting to Portmaster'; +const MessageShutdown = 'Shutting Down Portmaster'; +const MessageRestart = 'Restarting Portmaster'; +const MessageHidden = ''; + +export type OverlayMessage = typeof MessageConnecting + | typeof MessageShutdown + | typeof MessageRestart + | typeof MessageHidden; + +@Injectable({ providedIn: 'root' }) +export class ExitService { + private integration = inject(INTEGRATION_SERVICE); + + private hasOverlay = false; + + private _showOverlay = new BehaviorSubject(MessageConnecting); + + /** + * Emits whenever the "Connecting to ..." or "Restarting ..." overlays + * should be shown. It actually emits the message that should be shown. + * An empty string indicates the overlay should be closed. + */ + get showOverlay$() { return this._showOverlay.asObservable() } + + constructor( + private stateService: UIStateService, + private portapi: PortapiService, + private dialog: SfngDialogService, + private uai: ActionIndicatorService, + ) { + + this.portapi.connected$ + .pipe( + distinctUntilChanged(), + ) + .subscribe(connected => { + if (connected) { + this._showOverlay.next(MessageHidden); + } else if (this._showOverlay.getValue() !== MessageShutdown) { + this._showOverlay.next(MessageConnecting) + } + }) + + + let restartInProgress = false; + merge( + this.portapi.sub('runtime:modules/core/event/shutdown') + .pipe(map(() => MessageShutdown)), + this.portapi.sub('runtime:modules/core/event/restart') + .pipe( + tap(() => restartInProgress = true), + map(() => MessageRestart) + ), + ) + .pipe( + tap(msg => this._showOverlay.next(msg)), + switchMap(() => this.portapi.connected$), + distinctUntilChanged(), + skip(1), + debounceTime(1000), // make sure we display the "shutdown" overlay for at least a second + ) + .subscribe(connected => { + if (this._showOverlay.getValue() === MessageShutdown) { + setTimeout(() => { + this.integration.exitApp(); + }, 1000) + } + + if (connected && restartInProgress) { + restartInProgress = false; + this.portapi.reloadUI() + .pipe( + tap(() => { + setTimeout(() => window.location.reload(), 1000) + }) + ) + .subscribe(this.uai.httpObserver( + 'Reloading UI ...', + 'Failed to Reload UI', + )) + } + }) + + window.addEventListener('beforeunload', () => { + // best effort. may not work all the time depending on + // the current websocket buffer state + this.portapi.bridgeAPI('ui/reload', 'POST').subscribe(); + }) + + this.integration.onExitRequest(() => { + this.stateService.uiState() + // make sure to not wait for the portmaster to start + .pipe(timeout(1000), catchError(() => of(null))) + .subscribe(state => { + if (state?.hideExitScreen) { + this.integration.exitApp(); + return + } + + if (this.hasOverlay) { + return; + } + this.hasOverlay = true; + + this.dialog.create(ExitScreenComponent, { autoclose: true }) + .onAction('exit', () => this.integration.exitApp()) + .onClose.subscribe(() => this.hasOverlay = false); + }) + }) + } + + shutdownPortmaster() { + this.dialog.confirm({ + canCancel: true, + header: 'Shutting Down Portmaster', + message: 'Shutting down the Portmaster will stop all Portmaster components and will leave your system unprotected!', + caption: 'Caution', + buttons: [ + { + id: 'shutdown', + class: 'danger', + text: 'Shut Down Portmaster' + } + ] + }) + .onAction('shutdown', () => { + this.portapi.shutdownPortmaster() + .subscribe(this.uai.httpObserver( + 'Shutting Down ...', + 'Failed to Shut Down', + )) + }) + } +} diff --git a/desktop/angular/src/app/shared/exit-screen/index.ts b/desktop/angular/src/app/shared/exit-screen/index.ts new file mode 100644 index 00000000..8ea5634d --- /dev/null +++ b/desktop/angular/src/app/shared/exit-screen/index.ts @@ -0,0 +1,2 @@ +export * from './exit.service'; +export * from './exit-screen'; diff --git a/desktop/angular/src/app/shared/expertise/expertise-directive.ts b/desktop/angular/src/app/shared/expertise/expertise-directive.ts new file mode 100644 index 00000000..379466af --- /dev/null +++ b/desktop/angular/src/app/shared/expertise/expertise-directive.ts @@ -0,0 +1,93 @@ +import { Directive, EmbeddedViewRef, Input, isDevMode, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; +import { ExpertiseLevelNumber } from '@safing/portmaster-api'; +import { Subscription } from 'rxjs'; +import { ExpertiseService } from './expertise.service'; + +// ExpertiseLevelOverwrite may be called to display a DOM node decorated +// with [appExpertiseLevel] even if the current user setting does not +// match the required expertise. +export type ExpertiseLevelOverwrite = (lvl: ExpertiseLevelNumber, data: T) => boolean; +@Directive({ + selector: '[appExpertiseLevel]', +}) +export class ExpertiseDirective implements OnInit, OnDestroy { + private allowedValue: ExpertiseLevelNumber = ExpertiseLevelNumber.user; + private subscription = Subscription.EMPTY; + private view: EmbeddedViewRef | null = null; + + @Input() + set appExpertiseLevelOverwrite(fn: ExpertiseLevelOverwrite) { + this._levelOverwriteFn = fn; + this.update(); + } + private _levelOverwriteFn: ExpertiseLevelOverwrite | null = null; + + @Input() + set appExpertiseLevelData(d: T) { + this._data = d; + this.update(); + } + private _data: T | undefined = undefined; + + @Input() + set appExpertiseLevel(lvl: ExpertiseLevelNumber | string) { + if (typeof lvl === 'string') { + lvl = ExpertiseLevelNumber[lvl as any]; + } + if (lvl === undefined) { + if (isDevMode()) { + throw new Error(`[appExpertiseLevel] got undefined expertise-level value`); + } + return; + } + if (lvl !== this.allowedValue) { + this.allowedValue = lvl as ExpertiseLevelNumber; + this.update(); + } + } + + private update() { + const current = ExpertiseLevelNumber[this.expertiseService.currentLevel]; + let hide = current < this.allowedValue; + + // if there's an overwrite function defined make sue to check that. + if (hide && !!this._levelOverwriteFn) { + hide = !this._levelOverwriteFn(current, this._data!); + if (!hide) { + console.log("overwritten", current, this._data); + } + } + + if (hide) { + if (!!this.view) { + this.view.destroy(); + this.viewContainer.clear(); + this.view = null; + } + return + } + + if (!!this.view) { + this.view.markForCheck(); + return; + } + + this.view = this.viewContainer.createEmbeddedView(this.templateRef); + this.view.detectChanges(); + } + + constructor( + private expertiseService: ExpertiseService, + private templateRef: TemplateRef, + private viewContainer: ViewContainerRef + ) { } + + ngOnInit() { + this.subscription = this.expertiseService.change.subscribe(() => this.update()) + } + + ngOnDestroy() { + this.viewContainer.clear(); + this.subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/shared/expertise/expertise-switch.html b/desktop/angular/src/app/shared/expertise/expertise-switch.html new file mode 100644 index 00000000..8ad8eca7 --- /dev/null +++ b/desktop/angular/src/app/shared/expertise/expertise-switch.html @@ -0,0 +1,16 @@ + + + + Simple Interface + + + + Advanced Interface + + + + + Developer Interface + + + diff --git a/desktop/angular/src/app/shared/expertise/expertise-switch.scss b/desktop/angular/src/app/shared/expertise/expertise-switch.scss new file mode 100644 index 00000000..b795503d --- /dev/null +++ b/desktop/angular/src/app/shared/expertise/expertise-switch.scss @@ -0,0 +1,12 @@ +:host { + display: flex; + @apply pl-2; + user-select: none; + flex-direction: row; + align-items: center; + justify-content: center; +} + +sfng-tipup { + margin-right: 0.5rem; +} diff --git a/desktop/angular/src/app/shared/expertise/expertise-switch.ts b/desktop/angular/src/app/shared/expertise/expertise-switch.ts new file mode 100644 index 00000000..1b822927 --- /dev/null +++ b/desktop/angular/src/app/shared/expertise/expertise-switch.ts @@ -0,0 +1,38 @@ +import { Component, ElementRef } from '@angular/core'; +import { ExpertiseLevel } from '@safing/portmaster-api'; +import { ExpertiseService } from './expertise.service'; + +@Component({ + selector: 'app-expertise', + templateUrl: './expertise-switch.html', + styleUrls: ['./expertise-switch.scss'] +}) +export class ExpertiseComponent { + /** @private provide the expertise-level enums to the template */ + readonly expertiseLevels = ExpertiseLevel; + + currentLevel = this.expertiseService.change; + + /** + * @private + * Getter to access the expertise level as saved in the database + */ + get savedLevel() { + return this.expertiseService.savedLevel; + } + + constructor( + private expertiseService: ExpertiseService, + public host: ElementRef, + ) { } + + /** + * @private + * Configures a new expertise level + * + * @param lvl The new expertise level to use + */ + selectLevel(lvl: ExpertiseLevel) { + this.expertiseService.setLevel(lvl); + } +} diff --git a/desktop/angular/src/app/shared/expertise/expertise.module.ts b/desktop/angular/src/app/shared/expertise/expertise.module.ts new file mode 100644 index 00000000..7bf6a7fa --- /dev/null +++ b/desktop/angular/src/app/shared/expertise/expertise.module.ts @@ -0,0 +1,24 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { SfngSelectModule, SfngTipUpModule } from "@safing/ui"; +import { ExpertiseDirective } from "./expertise-directive"; +import { ExpertiseComponent } from "./expertise-switch"; + +@NgModule({ + imports: [ + SfngSelectModule, + CommonModule, + SfngTipUpModule, + FormsModule, + ], + declarations: [ + ExpertiseComponent, + ExpertiseDirective, + ], + exports: [ + ExpertiseComponent, + ExpertiseDirective, + ] +}) +export class ExpertiseModule { } diff --git a/desktop/angular/src/app/shared/expertise/expertise.service.ts b/desktop/angular/src/app/shared/expertise/expertise.service.ts new file mode 100644 index 00000000..5b5d7a20 --- /dev/null +++ b/desktop/angular/src/app/shared/expertise/expertise.service.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@angular/core'; +import { ConfigService, ExpertiseLevel, StringSetting } from '@safing/portmaster-api'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { distinctUntilChanged, map, repeat, share } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class ExpertiseService { + /** If the user overwrites the expertise level on a per-page setting we track that here */ + private _localOverwrite: ExpertiseLevel | null = null; + private _currentLevel: ExpertiseLevel = ExpertiseLevel.User; + + /** Watches the expertise level as saved in the configuration */ + private _savedLevel$ = this.configService.watch('core/expertiseLevel') + .pipe( + repeat({ delay: 2000 }), + map(upd => { + return upd as ExpertiseLevel; + }), + distinctUntilChanged(), + share(), + ); + + private level$ = new BehaviorSubject(ExpertiseLevel.User); + + get currentLevel() { + return this._localOverwrite === null + ? this._currentLevel + : this._localOverwrite; + } + + get savedLevel() { + return this._currentLevel; + } + + get change(): Observable { + return this.level$.asObservable(); + } + + constructor(private configService: ConfigService) { + this._savedLevel$ + .subscribe(lvl => { + this._currentLevel = lvl; + if (this._localOverwrite === null) { + this.level$.next(lvl); + } + }); + } + + setLevel(lvl: ExpertiseLevel | null) { + if (lvl === this._currentLevel) { + lvl = null; + } + + this._localOverwrite = lvl; + if (!!lvl) { + this.level$.next(lvl); + } else { + this.level$.next(this._currentLevel!); + } + } +} diff --git a/desktop/angular/src/app/shared/expertise/index.ts b/desktop/angular/src/app/shared/expertise/index.ts new file mode 100644 index 00000000..6c41ae61 --- /dev/null +++ b/desktop/angular/src/app/shared/expertise/index.ts @@ -0,0 +1,3 @@ +export * from './expertise-directive'; +export * from './expertise-switch'; +export * from './expertise.service'; diff --git a/desktop/angular/src/app/shared/external-link.directive.ts b/desktop/angular/src/app/shared/external-link.directive.ts new file mode 100644 index 00000000..47a16c28 --- /dev/null +++ b/desktop/angular/src/app/shared/external-link.directive.ts @@ -0,0 +1,53 @@ +import { isPlatformBrowser } from '@angular/common'; +import { + Directive, + HostBinding, HostListener, Inject, + Input, OnChanges, PLATFORM_ID, inject +} from '@angular/core'; +import { INTEGRATION_SERVICE } from '../integration'; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: 'a[href]' +}) +export class ExternalLinkDirective implements OnChanges { + private readonly integration = inject(INTEGRATION_SERVICE); + + @HostBinding('attr.rel') + relAttr = ''; + + @HostBinding('attr.target') + targetAttr = ''; + + @HostBinding('attr.href') + hrefAttr = ''; + + @Input() + href: string = ''; + + constructor(@Inject(PLATFORM_ID) private platformId: string) { } + + @HostListener('click', ['$event']) + onClick(event: Event) { + event.preventDefault(); + event.stopPropagation(); + + this.integration.openExternal(this.href); + } + + ngOnChanges() { + this.hrefAttr = this.href; + + if (this.isLinkExternal()) { + this.relAttr = 'noopener'; + this.targetAttr = '_blank'; + } + } + + private isLinkExternal() { + return ( + isPlatformBrowser(this.platformId) && + !this.href.includes(location.hostname) + ); + } +} diff --git a/desktop/angular/src/app/shared/feature-scout/feature-scout.html b/desktop/angular/src/app/shared/feature-scout/feature-scout.html new file mode 100644 index 00000000..f0ee7af7 --- /dev/null +++ b/desktop/angular/src/app/shared/feature-scout/feature-scout.html @@ -0,0 +1,106 @@ + +
+ +
+ + + + + + + + + + + + + + + + +
+ +
+
+ + + + SPN is connecting...
+ Fail-safe blocking enabled +
+ + SPN failed to connect
+ Fail-safe blocking enabled +
+ + SPN is connecting...
+ Fail-safe blocking enabled +
+ + + + + {{ spnStatus?.HomeHubName }} + + in + + + {{ spnStatus?.ConnectedCountry?.Name }} + +
+ +
+
+ + + +
+
+ + + SPN Home (Entry) Node +
    +
  • Connected to {{ spnStatus?.ConnectedIP }}
  • +
  • Uplink is always encrypted
  • +
  • Built with transport/decoy {{ spnStatus?.ConnectedTransport }}
  • +
+
diff --git a/desktop/angular/src/app/shared/feature-scout/feature-scout.scss b/desktop/angular/src/app/shared/feature-scout/feature-scout.scss new file mode 100644 index 00000000..5ae271f6 --- /dev/null +++ b/desktop/angular/src/app/shared/feature-scout/feature-scout.scss @@ -0,0 +1,15 @@ +.feature-icon { + @apply text-primary text-opacity-80; + + &.feature-icon-off { + opacity: 0.25; + } +} + +.status-info { + @apply text-primary text-opacity-80 text-xxs text-center; + + &:hover { + cursor: default; + } +} diff --git a/desktop/angular/src/app/shared/feature-scout/feature-scout.ts b/desktop/angular/src/app/shared/feature-scout/feature-scout.ts new file mode 100644 index 00000000..596edf14 --- /dev/null +++ b/desktop/angular/src/app/shared/feature-scout/feature-scout.ts @@ -0,0 +1,98 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BoolSetting, ConfigService, FeatureID, Netquery, SPNService, SPNStatus, UserProfile } from "@safing/portmaster-api"; +import { catchError, of } from "rxjs"; +import { fadeInAnimation, fadeOutAnimation } from "../animations"; +import { CountryFlagModule } from 'src/app/shared/country-flag'; + +@Component({ + selector: 'app-feature-scout', + templateUrl: './feature-scout.html', + styleUrls: [ + './feature-scout.scss' + ], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInAnimation, + fadeOutAnimation, + ] +}) +export class FeatureScoutComponent implements OnInit { + private destroyRef = inject(DestroyRef); + + /** The current SPN user profile */ + profile: UserProfile | null = null; + + /** Whether or not the SPN is currently enabled */ + spnEnabled = false; + + /** The current status of the SPN module */ + spnStatus: SPNStatus | null = null; + + /** Whether or not the Network History is currently enabled */ + historyEnabled = false; + + /** Returns whether or not the current package has the SPN feature */ + get packageHasSPN() { + return this.profile?.current_plan?.feature_ids?.includes(FeatureID.SPN) + } + + /** Returns whether or not the current package has the Network History feature */ + get packageHasHistory() { + return this.profile?.current_plan?.feature_ids?.includes(FeatureID.History) + } + + constructor( + private configService: ConfigService, + private spnService: SPNService, + private cdr: ChangeDetectorRef, + ) { } + + ngOnInit(): void { + this.spnService + .profile$ + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(() => of(null)) + ) + .subscribe(profile => { + this.profile = profile || null; + + this.cdr.markForCheck(); + }); + + this.spnService.status$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(status => { + this.spnStatus = status; + + this.cdr.markForCheck(); + }) + + this.configService.watch("spn/enable") + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(value => { + this.spnEnabled = value; + + this.cdr.markForCheck(); + }); + + this.configService.watch("history/enable") + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(value => { + this.historyEnabled = value; + + this.cdr.markForCheck(); + }); + } + + setSPNEnabled(v: boolean) { + this.configService.save(`spn/enable`, v) + .subscribe(); + } + + setHistoryEnabled(v: boolean) { + this.configService.save(`history/enable`, v) + .subscribe(); + } +} diff --git a/desktop/angular/src/app/shared/feature-scout/index.ts b/desktop/angular/src/app/shared/feature-scout/index.ts new file mode 100644 index 00000000..6fc7f610 --- /dev/null +++ b/desktop/angular/src/app/shared/feature-scout/index.ts @@ -0,0 +1 @@ +export * from './feature-scout'; diff --git a/desktop/angular/src/app/shared/focus/focus.directive.ts b/desktop/angular/src/app/shared/focus/focus.directive.ts new file mode 100644 index 00000000..79b83f40 --- /dev/null +++ b/desktop/angular/src/app/shared/focus/focus.directive.ts @@ -0,0 +1,32 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { Directive, ElementRef, Input, OnInit } from "@angular/core"; + +@Directive({ + // eslint-disable-next-line @angular-eslint/directive-selector + selector: '[autoFocus]', +}) +export class AutoFocusDirective implements OnInit { + private _focus = true; + private _afterInit = false; + + @Input('autoFocus') + set focus(v: any) { + this._focus = coerceBooleanProperty(v) !== false; + + if (this._afterInit && this.elementRef) { + this.elementRef.nativeElement.focus() + } + } + + constructor(private elementRef: ElementRef) { } + + ngOnInit(): void { + setTimeout(() => { + if (this._focus) { + this.elementRef.nativeElement.focus(); + } + }, 100) + + this._afterInit = true; + } +} diff --git a/desktop/angular/src/app/shared/focus/focus.module.ts b/desktop/angular/src/app/shared/focus/focus.module.ts new file mode 100644 index 00000000..29593994 --- /dev/null +++ b/desktop/angular/src/app/shared/focus/focus.module.ts @@ -0,0 +1,16 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { AutoFocusDirective } from "./focus.directive"; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + AutoFocusDirective, + ], + exports: [ + AutoFocusDirective, + ] +}) +export class SfngFocusModule { } diff --git a/desktop/angular/src/app/shared/focus/index.ts b/desktop/angular/src/app/shared/focus/index.ts new file mode 100644 index 00000000..f7f9cff0 --- /dev/null +++ b/desktop/angular/src/app/shared/focus/index.ts @@ -0,0 +1,2 @@ +export { AutoFocusDirective } from './focus.directive'; +export * from './focus.module'; diff --git a/desktop/angular/src/app/shared/fuzzySearch/fuse.service.ts b/desktop/angular/src/app/shared/fuzzySearch/fuse.service.ts new file mode 100644 index 00000000..d7f3e192 --- /dev/null +++ b/desktop/angular/src/app/shared/fuzzySearch/fuse.service.ts @@ -0,0 +1,105 @@ +import { Injectable } from '@angular/core'; +import { deepClone } from '@safing/portmaster-api'; +import Fuse from 'fuse.js'; + +export type FuseResult = Fuse.FuseResult; + +export interface FuseSearchOpts extends Fuse.IFuseOptions { + minSearchTermLength?: number; + maximumScore?: number; +} + +@Injectable({ + providedIn: 'root' +}) +export class FuzzySearchService { + + readonly defaultOptions: FuseSearchOpts = { + minMatchCharLength: 2, + includeMatches: true, + includeScore: true, + minSearchTermLength: 3, + }; + + searchList(list: Array, searchTerms: string, options: FuseSearchOpts & { disableHighlight?: boolean } = {}): Array> { + const opts: FuseSearchOpts = { + ...this.defaultOptions, + ...options, + } + + let result: FuseResult[] = []; + + + if (searchTerms && searchTerms.length >= (opts.minSearchTermLength || 0)) { + let fuse = new Fuse(list, opts); + result = fuse.search(searchTerms); + + } else { + result = list.map((item, index) => ({ + item: item, + refIndex: index, + score: 0, + })) + } + + if (!!options.disableHighlight) { + return result; + } + + return this.handleHighlight(result, options); + } + + private handleHighlight(result: FuseResult[], options: FuseSearchOpts): FuseResult[] { + return result.map(matchObject => { + matchObject.item = deepClone(matchObject.item); + + if (!matchObject.matches) { + return matchObject; + } + + for (let match of matchObject.matches!) { + const indices = match.indices; + + let highlightOffset: number = 0; + + for (let indice of indices) { + let initialValue = getFromMatch(matchObject, match); + + const startOffset = indice[0] + highlightOffset; + const endOffset = indice[1] + highlightOffset + 1; + + if (endOffset - startOffset < 4) { + continue + } + + let highlightedTerm = initialValue.substring(startOffset, endOffset); + let newValue = initialValue.substring(0, startOffset) + '' + highlightedTerm + '' + initialValue.substring(endOffset); + + highlightOffset += ''.length; + + setOnMatch(matchObject, match, newValue); + } + } + + return matchObject; + }); + } +} + +function getFromMatch(result: Fuse.FuseResult, match: Fuse.FuseResultMatch): string { + if (match.refIndex === undefined) { + return (result.item as any)[match.key!]; + } + return (result.item as any)[match.key!][match.refIndex]; +} + +function setOnMatch(result: Fuse.FuseResult, match: Fuse.FuseResultMatch, value: string) { + if (match.refIndex === undefined) { + (result.item as any)[match.key!] = value; + return; + } + + (result.item as any)[match.key!][match.refIndex] = value; +} diff --git a/desktop/angular/src/app/shared/fuzzySearch/index.ts b/desktop/angular/src/app/shared/fuzzySearch/index.ts new file mode 100644 index 00000000..d1194321 --- /dev/null +++ b/desktop/angular/src/app/shared/fuzzySearch/index.ts @@ -0,0 +1,4 @@ +import Fuse from 'fuse.js'; + +export { FuseSearchOpts, FuzzySearchService } from './fuse.service'; +export { FuzzySearchPipe } from './search-pipe'; diff --git a/desktop/angular/src/app/shared/fuzzySearch/search-pipe.ts b/desktop/angular/src/app/shared/fuzzySearch/search-pipe.ts new file mode 100644 index 00000000..4f6d7b1b --- /dev/null +++ b/desktop/angular/src/app/shared/fuzzySearch/search-pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { FuseResult, FuseSearchOpts, FuzzySearchService } from './fuse.service'; + + +@Pipe({ + name: 'fuzzySearch', +}) +export class FuzzySearchPipe implements PipeTransform { + constructor( + private FusejsService: FuzzySearchService + ) { } + + transform(elements: Array, + searchTerms: string, + options: FuseSearchOpts = {}): Array> { + + return this.FusejsService.searchList(elements, searchTerms, options); + } +} diff --git a/desktop/angular/src/app/shared/loading/index.ts b/desktop/angular/src/app/shared/loading/index.ts new file mode 100644 index 00000000..68c5f495 --- /dev/null +++ b/desktop/angular/src/app/shared/loading/index.ts @@ -0,0 +1 @@ +export { LoadingComponent } from './loading'; diff --git a/desktop/angular/src/app/shared/loading/loading.html b/desktop/angular/src/app/shared/loading/loading.html new file mode 100644 index 00000000..bfa7d9b6 --- /dev/null +++ b/desktop/angular/src/app/shared/loading/loading.html @@ -0,0 +1,3 @@ + + + diff --git a/desktop/angular/src/app/shared/loading/loading.scss b/desktop/angular/src/app/shared/loading/loading.scss new file mode 100644 index 00000000..fccdce9d --- /dev/null +++ b/desktop/angular/src/app/shared/loading/loading.scss @@ -0,0 +1,52 @@ +:host { + --internal-dot-size : var(--dot-size, 5px); + --internal-animation-speed: var(--animation-speed, 1.3s); + + display : flex; + position : relative; + justify-content: space-evenly; + align-items : flex-end; + width : var(--animation-width, calc(var(--internal-dot-size) * 5)); + + height: calc(var(--internal-dot-size) * 3); + + &.animate { + .dot { + display : block; + flex-shrink: 0; + flex-grow : 0; + width : var(--internal-dot-size); + height : var(--internal-dot-size); + + @apply shadow-inner-xs; + @apply rounded-full; + @apply bg-buttons-icon; + + animation: wave var(--internal-animation-speed) linear infinite; + + &:nth-child(2) { + animation-delay: -1.1s; + } + + &:nth-child(3) { + animation-delay: -0.9s; + } + } + } + +} + +@keyframes wave { + + 0%, + 60%, + 100% { + transform: initial; + @apply bg-buttons-light; + } + + 90% { + transform : translateY(var(--loading-height, -9px)); + background-color: white; + } +} diff --git a/desktop/angular/src/app/shared/loading/loading.ts b/desktop/angular/src/app/shared/loading/loading.ts new file mode 100644 index 00000000..fb7f049d --- /dev/null +++ b/desktop/angular/src/app/shared/loading/loading.ts @@ -0,0 +1,14 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding } from '@angular/core'; + +@Component({ + selector: 'app-loading', + templateUrl: './loading.html', + styleUrls: ['./loading.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class LoadingComponent { + @HostBinding('class.animate') + _animate = true; + + constructor(private changeDetectorRef: ChangeDetectorRef) { } +} diff --git a/desktop/angular/src/app/shared/menu/index.ts b/desktop/angular/src/app/shared/menu/index.ts new file mode 100644 index 00000000..bb5dcd95 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/index.ts @@ -0,0 +1,2 @@ +export { MenuComponent, MenuTriggerComponent, MenuItemComponent, MenuGroupComponent } from './menu'; +export * from './menu.module'; diff --git a/desktop/angular/src/app/shared/menu/menu-group.scss b/desktop/angular/src/app/shared/menu/menu-group.scss new file mode 100644 index 00000000..c2cd7063 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/menu-group.scss @@ -0,0 +1,13 @@ +:host { + display: block; + width: 100%; + + @apply p-1; + @apply px-4; + @apply text-secondary; + + display: block; + text-transform: uppercase; + font-size: 0.7rem; + opacity: .7; +} diff --git a/desktop/angular/src/app/shared/menu/menu-item.scss b/desktop/angular/src/app/shared/menu/menu-item.scss new file mode 100644 index 00000000..fdba9c32 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/menu-item.scss @@ -0,0 +1,17 @@ +:host { + @apply block w-full; + + cursor: pointer; + @apply p-2; + @apply px-4 text-primary text-xxs; + font-weight: 500; + + &:hover { + @apply bg-gray-300; + } + + &.disabled { + cursor: not-allowed; + opacity: 0.5; + } +} diff --git a/desktop/angular/src/app/shared/menu/menu-trigger.html b/desktop/angular/src/app/shared/menu/menu-trigger.html new file mode 100644 index 00000000..02642109 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/menu-trigger.html @@ -0,0 +1,14 @@ +
+ +
diff --git a/desktop/angular/src/app/shared/menu/menu-trigger.scss b/desktop/angular/src/app/shared/menu/menu-trigger.scss new file mode 100644 index 00000000..77cc16b0 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/menu-trigger.scss @@ -0,0 +1,41 @@ +:host { + user-select: none; + margin-right: .5rem; + display: block; + @apply rounded-t-sm; +} + +div { + cursor: pointer; + display: flex; + @apply rounded-t; + flex-grow: 0; + transition: all .1s ease-in-out; + justify-content: center; + align-items: center; + @apply py-1; + @apply px-3; +} + +.dropdown { + margin-left: 1px; + height: auto; + padding: 0; + margin: 0; + + svg { + opacity: 0.7; + fill: var(--text-primary); + width: 0.51rem; + transition: all cubic-bezier(0.175, 0.885, 0.32, 1.275) .2s; + + transform: rotate(90deg); + position: relative; + top: 3px; + } +} + +:host.active { + @apply bg-gray-400; + color: white !important; +} diff --git a/desktop/angular/src/app/shared/menu/menu.html b/desktop/angular/src/app/shared/menu/menu.html new file mode 100644 index 00000000..d33da3d7 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/menu.html @@ -0,0 +1,6 @@ + +
+ +
+
diff --git a/desktop/angular/src/app/shared/menu/menu.module.ts b/desktop/angular/src/app/shared/menu/menu.module.ts new file mode 100644 index 00000000..4f97a6c0 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/menu.module.ts @@ -0,0 +1,26 @@ +import { OverlayModule } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SfngDropDownModule } from "@safing/ui"; +import { MenuComponent, MenuGroupComponent, MenuItemComponent, MenuTriggerComponent } from "./menu"; + +@NgModule({ + imports: [ + SfngDropDownModule, + CommonModule, + OverlayModule, + ], + declarations: [ + MenuComponent, + MenuGroupComponent, + MenuTriggerComponent, + MenuItemComponent, + ], + exports: [ + MenuComponent, + MenuGroupComponent, + MenuTriggerComponent, + MenuItemComponent, + ], +}) +export class SfngMenuModule { } diff --git a/desktop/angular/src/app/shared/menu/menu.ts b/desktop/angular/src/app/shared/menu/menu.ts new file mode 100644 index 00000000..f5467921 --- /dev/null +++ b/desktop/angular/src/app/shared/menu/menu.ts @@ -0,0 +1,111 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { CdkOverlayOrigin } from '@angular/cdk/overlay'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, EventEmitter, HostBinding, HostListener, Input, Output, QueryList, ViewChild } from '@angular/core'; +import { SfngDropdownComponent } from '@safing/ui'; + +@Component({ + selector: 'app-menu-trigger', + templateUrl: './menu-trigger.html', + styleUrls: ['./menu-trigger.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MenuTriggerComponent { + @ViewChild(CdkOverlayOrigin, { static: true }) + origin!: CdkOverlayOrigin; + + @Input() + menu: MenuComponent | null = null; + + @Input() + set useContent(v: any) { + this._useContent = coerceBooleanProperty(v); + } + get useContent() { return this._useContent; } + private _useContent: boolean = false; + + @HostBinding('class.active') + get isOpen() { + if (!this.menu) { + return false; + } + + return this.menu.dropdown.isOpen; + } + + constructor( + public changeDetectorRef: ChangeDetectorRef, + ) { } + + toggle(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); + + this.menu?.dropdown.toggle(this.origin) + } +} + +@Component({ + selector: 'app-menu-item', + template: '', + styleUrls: ['./menu-item.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MenuItemComponent { + @Input() + @HostBinding('class.disabled') + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v); + } + get disabled() { return this._disabled; } + private _disabled: boolean = false; + + @HostListener('click', ['$event']) + closeMenu(event: MouseEvent) { + if (this.disabled) { + return; + } + this.activate.next(event); + this.menu.dropdown.close(); + } + + /** + * activate fires when the menu item is clicked. + * Use activate rather than (click)="" if you want + * [disabled] to be considered. + */ + @Output() + activate = new EventEmitter(); + + constructor(private menu: MenuComponent) { } +} + +@Component({ + selector: 'app-menu-group', + template: '', + styleUrls: ['./menu-group.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MenuGroupComponent { } + +@Component({ + selector: 'app-menu', + exportAs: 'appMenu', + templateUrl: './menu.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MenuComponent { + @ContentChildren(MenuItemComponent) + items: QueryList | null = null; + + @ViewChild(SfngDropdownComponent, { static: true }) + dropdown!: SfngDropdownComponent; + + @Input() + offsetY?: string | number; + + @Input() + offsetX?: string | number; + + @Input() + overlayClass?: string; +} diff --git a/desktop/angular/src/app/shared/multi-switch/index.ts b/desktop/angular/src/app/shared/multi-switch/index.ts new file mode 100644 index 00000000..6009e482 --- /dev/null +++ b/desktop/angular/src/app/shared/multi-switch/index.ts @@ -0,0 +1,3 @@ +export { MultiSwitchComponent } from './multi-switch'; +export { SwitchItemComponent } from './switch-item'; +export * from './multi-switch.module'; diff --git a/desktop/angular/src/app/shared/multi-switch/multi-switch.html b/desktop/angular/src/app/shared/multi-switch/multi-switch.html new file mode 100644 index 00000000..6af6d004 --- /dev/null +++ b/desktop/angular/src/app/shared/multi-switch/multi-switch.html @@ -0,0 +1,5 @@ +
+ + +
+ diff --git a/desktop/angular/src/app/shared/multi-switch/multi-switch.module.ts b/desktop/angular/src/app/shared/multi-switch/multi-switch.module.ts new file mode 100644 index 00000000..a3404d52 --- /dev/null +++ b/desktop/angular/src/app/shared/multi-switch/multi-switch.module.ts @@ -0,0 +1,26 @@ +import { DragDropModule } from "@angular/cdk/drag-drop"; +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { SfngTipUpModule, SfngTooltipModule } from "@safing/ui"; +import { MultiSwitchComponent } from "./multi-switch"; +import { SwitchItemComponent } from "./switch-item"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + SfngTooltipModule, + SfngTipUpModule, + DragDropModule, + ], + declarations: [ + MultiSwitchComponent, + SwitchItemComponent, + ], + exports: [ + MultiSwitchComponent, + SwitchItemComponent, + ], +}) +export class SfngMultiSwitchModule { } diff --git a/desktop/angular/src/app/shared/multi-switch/multi-switch.scss b/desktop/angular/src/app/shared/multi-switch/multi-switch.scss new file mode 100644 index 00000000..6b96e2f3 --- /dev/null +++ b/desktop/angular/src/app/shared/multi-switch/multi-switch.scss @@ -0,0 +1,46 @@ +.buttons { + display: flex; + align-items: flex-end; + position: relative; + height: 3rem; + flex-grow: 0; + width: fit-content; + + fa-icon[icon*="question-circle"] { + height: 100%; + display: flex; + align-items: center; + margin-left: 1rem; + } +} + +.marker { + display: block; + height: 16px; + width: 16px; + position: absolute; + bottom: -8px; + cursor: grab; + transition: all .5s cubic-bezier(0.175, 0.885, 0.32, 1.075); + @apply rounded-full; +} + +:host { + flex-grow: 0; + width: fit-content; + display: block; + outline: none; + user-select: none; + + &.disabled { + .marker { + cursor: unset; + } + } + + &.grabbing { + .marker { + cursor: grabbing; + } + } +} diff --git a/desktop/angular/src/app/shared/multi-switch/multi-switch.ts b/desktop/angular/src/app/shared/multi-switch/multi-switch.ts new file mode 100644 index 00000000..2726427c --- /dev/null +++ b/desktop/angular/src/app/shared/multi-switch/multi-switch.ts @@ -0,0 +1,370 @@ +import { ListKeyManager } from '@angular/cdk/a11y'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { DOCUMENT } from '@angular/common'; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, Inject, Input, NgZone, OnDestroy, Output, QueryList, Renderer2, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { animationFrameScheduler, fromEvent, Subscription } from 'rxjs'; +import { map, startWith, subscribeOn, take, takeUntil } from 'rxjs/operators'; +import { SwitchItemComponent } from './switch-item'; + +@Component({ + selector: 'app-multi-switch', + templateUrl: './multi-switch.html', + styleUrls: ['./multi-switch.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MultiSwitchComponent), + multi: true, + } + ] +}) +export class MultiSwitchComponent implements OnDestroy, AfterViewInit, ControlValueAccessor { + /** Subscription to all button-select changes */ + private sub = Subscription.EMPTY; + + /** Holds the current x-translation offset for the marker */ + private markerOffset: number = 0; + + /** Keymanager used for keyboard navigation support */ + private keyManager: ListKeyManager> | null = null; + + /** Subscription to the key manager */ + private keyManagerSub = Subscription.EMPTY; + + @Input() + tipUpKey: string = ''; + + /** All buttons projected into the multi-switch */ + @ContentChildren(SwitchItemComponent) + buttons: QueryList> | null = null; + + /** Emits whenever the selected button changes. */ + @Output() + changed = new EventEmitter(); + + /** Reference to the marker inside our view container */ + @ViewChild('marker', { read: ElementRef, static: true }) + marker: ElementRef | null = null; + + @HostListener('blur') + onBlur() { + this._onTouch(); + } + + @HostBinding('attr.tabindex') + readonly tabindex = 0; + + @HostListener('keyup', ['$event']) + onKeyUp(event: KeyboardEvent) { + if (this.disabled) { + return; + } + this.keyManager!.onKeydown(event); + } + + /** Whether or not the switch button component is disabled */ + @Input() + @HostBinding('class.disabled') + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v); + + // Update all buttons states as well. + if (!!this.buttons) { + this.buttons.forEach(btn => btn.disabled = this.disabled); + } + } + get disabled() { return this._disabled; } + private _disabled = false; + + @HostBinding('class.grabbing') + isGrabbing = false; + + /** External write tracks calls to writeValue so we don't end up re-emitting the values. */ + private externalWrite = false; + + /** Which button is currently active (and holds the marker) */ + activeButton: T | null = null; + + constructor( + public host: ElementRef, + private changeDetectorRef: ChangeDetectorRef, + private renderer: Renderer2, + private ngZone: NgZone, + @Inject(DOCUMENT) private document: Document, + ) { } + + /** Registeres the change callback. Required for ControlValueAccessor */ + registerOnChange(fn: (v: T) => void) { + this._onChange = fn; + } + private _onChange: (value: T) => void = () => { } + + /** Registers the touch callback. Required for ControlValueAccessor */ + registerOnTouched(fn: () => void) { + this._onTouch = fn; + } + private _onTouch: () => void = () => { }; + + /** Disable or enable the button. Required for ControlValueAccessor */ + setDisabledState(disabled: boolean) { + this.disabled = disabled; + } + + /** Writes a new value for the multi-line switch */ + writeValue(value: T) { + this.activeButton = value; + if (!!this.buttons) { + // Set externalWrite to true while we iterate the buttons + // and eventually call `setActiveItem` so we don't re-emit + // the active item once the keyManager publishes the change + // to use. + // This workaround is required as we need to inform the + // keyManager about the new active item. Otherwise it would + // work with a stale internal state the next time the user + // uses the keyboard. + this.externalWrite = true; + this.buttons.forEach(btn => { + if (btn.id === value) { + this.keyManager!.setActiveItem(btn); + this.repositionMarker(btn); + } + }) + this.externalWrite = false; + } + } + + ngAfterViewInit() { + if (!this.buttons) { + return; + } + + this.keyManager = new ListKeyManager(this.buttons) + .withHorizontalOrientation('ltr') + .withTypeAhead() + .withWrap(); + + this.keyManagerSub = this.keyManager.change + .subscribe(activeIndex => { + const active = Array.from(this.buttons!)[activeIndex]; + this.selectButton(active, !this.externalWrite); + }); + + // Subscribe to all (clicked) and (selectedChange) events of + // all buttons projected into our content. + this.buttons.changes + .pipe(startWith(null)) + .subscribe(() => { + this.sub.unsubscribe(); + this.sub = new Subscription(); + + this.buttons!.forEach(btn => { + btn.disabled = this.disabled; + this.sub.add( + btn.clicked.subscribe((e: MouseEvent) => { + this.keyManager!.setActiveItem(btn); + }) + ); + }); + + // wait until the zone and change-detection stabilizes and + // reposition the marker afterwards. Doing it right now will + // likely position it wrongly since the DOM has not yet been + // fully updated. + this.ngZone.onStable.pipe(take(1)) + .subscribe(() => this.repositionMarker()) + }); + + this.buttons.forEach(btn => { + if (this.activeButton === btn.id) { + btn.selected = true; + } + }) + + this.repositionMarker(); + } + + ngOnDestroy() { + this.sub.unsubscribe(); + this.keyManagerSub.unsubscribe(); + } + + /** Selects a new button and deselects all others. */ + private selectButton(btn: SwitchItemComponent, emit = true) { + if (this.disabled) { + return; + } + + this.activeButton = btn.id; + + if (emit) { + this.changed.next(btn.id!); + this._onChange(btn.id!); + } + + this.repositionMarker(btn); + } + + /** @private View-callback for (mousedown) to start dragging the marker. */ + dragStarted(event: MouseEvent) { + if (this.disabled) { + return; + } + + this.isGrabbing = true; + this.renderer.addClass(this.document.getElementsByTagName("body")[0], 'document-grabbing'); + + const mousemove$ = fromEvent(this.document, 'mousemove'); + const hostRect = this.host.nativeElement.getBoundingClientRect(); + const start = this.markerOffset; + const markerWidth = this.marker!.nativeElement.getBoundingClientRect().width; + + // we don't want angular to run change detection all the time we move a pixel + // so detach the change-detector for now. + this.changeDetectorRef.detach(); + + mousemove$ + .pipe( + map(move => { + move.preventDefault(); + return move.clientX - event.clientX; + }), + takeUntil(fromEvent(document, 'mouseup')), + subscribeOn(animationFrameScheduler) + ) + .subscribe({ + next: diff => { + // clip the new offset inside our host-view. + let offset = start + diff; + if (offset < 0) { + offset = 0; + } else if (offset > hostRect.width) { + offset = hostRect.width; + } + + // center the marker at the mouse position. + offset -= Math.round(markerWidth / 2); + + this.markerOffset = offset; + this.updatePosition(offset); + + let foundTarget = false; + let target = this.findTargetButton(offset); + + if (!!target) { + this.marker!.nativeElement.style.backgroundColor = target.borderColorActive; + + this.buttons!.forEach(btn => { + if (!foundTarget && btn.group === target!.group) { + this.renderer.addClass(btn.elementRef.nativeElement, 'selected'); + btn.elementRef.nativeElement.style.borderColor = btn.borderColorActive; + } else { + this.renderer.removeClass(btn.elementRef.nativeElement, 'selected'); + btn.elementRef.nativeElement.style.borderColor = btn.borderColorInactive; + } + + if (target === btn) { + foundTarget = true; + } + }); + } + }, + complete: () => { + this.changeDetectorRef.reattach(); + this.markerDropped(); + + // make sure we don't keep the selected class on buttons that + // are not selected anymore. + this.buttons!.forEach(btn => { + if (!btn.selected) { + this.renderer.removeClass(btn.elementRef.nativeElement, 'selected'); + btn.elementRef.nativeElement.style.borderColor = btn.borderColorInactive; + } + }); + + this.isGrabbing = false; + this.renderer.removeClass(this.document.getElementsByTagName("body")[0], 'document-grabbing'); + } + }); + } + + /** Update the markers position by applying a translate3d */ + private updatePosition(x: number) { + this.marker!.nativeElement.style.transform = `translate3d(${x}px, 0px, 0px)`; + } + + /** Find the button item that is below x */ + private findTargetButton(x: number, cb?: (item: SwitchItemComponent, target: boolean) => void): SwitchItemComponent | null { + const host = this.host.nativeElement.getBoundingClientRect(); + let newButton: SwitchItemComponent | null = null; + this.buttons?.forEach(btn => { + const btnRect = btn.elementRef.nativeElement.getBoundingClientRect(); + const min = btnRect.x - host.x; + const max = min + btnRect.width; + + if (x >= min && x <= max) { + newButton = btn; + + if (!!cb) { + cb(btn, true); + } + } else if (!!cb) { + cb(btn, false); + } + }); + + return newButton; + } + + /** Calculates which button should be activated based on the drop-position of the marker */ + private markerDropped() { + let newButton = this.findTargetButton(this.markerOffset); + + if (!newButton) { + newButton = Array.from(this.buttons!)[0]; + } + + if (!!newButton) { + this.keyManager!.setActiveItem(newButton); + } + } + + /** + * Calculates the new position required to center the + * marker at the currently selected button. + * If `selected` is unset the last button with selected == true is + * used. + * + * @param selected The switch item button to select (optional). + */ + private repositionMarker(selected: SwitchItemComponent | null = null) { + // If there's no selected button given search for the last one that + // matches selected === true. + if (selected === null) { + this.buttons?.forEach(btn => { + if (btn.selected) { + selected = btn; + } + }); + } + + // There's not button selected so we move the marker back to the + // start. + if (selected === null) { + this.markerOffset = 0; + this.updatePosition(0); + return; + } + + // Calculate and reposition the marker. + const offsetLeft = selected!.elementRef.nativeElement.offsetLeft; + const clientWidth = selected!.elementRef.nativeElement.clientWidth; + + this.markerOffset = Math.round(offsetLeft - 8 + clientWidth / 2); + this.marker!.nativeElement.style.backgroundColor = selected.borderColorActive; + + this.updatePosition(this.markerOffset); + this.changeDetectorRef.markForCheck(); + } +} diff --git a/desktop/angular/src/app/shared/multi-switch/switch-item.scss b/desktop/angular/src/app/shared/multi-switch/switch-item.scss new file mode 100644 index 00000000..da737c13 --- /dev/null +++ b/desktop/angular/src/app/shared/multi-switch/switch-item.scss @@ -0,0 +1,35 @@ +:host { + display : flex; + align-items : center; + justify-content: center; + width : 6rem; + height : 2.7rem; + position : relative; + bottom : 0; + transition : all .3s cubic-bezier(0.075, 0.82, 0.165, 1); + + @apply bg-buttons-dark; + + @apply border-b-2; + + &.selected { + @apply bg-buttons-light; + height: 3rem; + } + + &:not(.disabled) { + cursor: pointer; + + &:hover { + @apply bg-buttons-light; + } + } + + &:first-of-type { + @apply rounded-tl; + } + + &:last-of-type { + @apply rounded-tr; + } +} diff --git a/desktop/angular/src/app/shared/multi-switch/switch-item.ts b/desktop/angular/src/app/shared/multi-switch/switch-item.ts new file mode 100644 index 00000000..f70f3a2f --- /dev/null +++ b/desktop/angular/src/app/shared/multi-switch/switch-item.ts @@ -0,0 +1,80 @@ +import { Component, ChangeDetectionStrategy, Input, isDevMode, OnInit, HostBinding, Output, EventEmitter, HostListener, ElementRef, ChangeDetectorRef } from '@angular/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'app-switch-item', + template: '', + styleUrls: ['./switch-item.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SwitchItemComponent implements OnInit { + @Input() + id: T | null = null; + + @Input() + group = ''; + + @Output() + clicked = new EventEmitter(); + + @HostListener('click', ['$event']) + onClick(e: MouseEvent) { + this.clicked.next(e); + } + + @Input() + borderColorActive: string = 'var(--info-green)'; + + @Input() + borderColorInactive: string = 'var(--button-light)'; + + @HostBinding('style.border-color') + get borderColor() { + if (this.selected) { + return this.borderColorActive; + } + return this.borderColorInactive; + } + + @Input() + @HostBinding('class.disabled') + set disabled(v: any) { + this._disabled = coerceBooleanProperty(v); + } + get disabled() { + return this._disabled; + } + private _disabled = false; + + @Input() + @HostBinding('class.selected') + set selected(v: any) { + const selected = coerceBooleanProperty(v); + if (selected !== this._selected) { + this._selected = selected; + this.selectedChange.next(selected); + } + } + get selected() { + return this._selected; + } + private _selected = false; + + getLabel() { + return this.elementRef.nativeElement.innerText; + } + + @Output() + selectedChange = new EventEmitter(); + + ngOnInit() { + if (this.id === null && isDevMode()) { + throw new Error(`SwitchItemComponent must have an ID`); + } + } + + constructor( + public readonly elementRef: ElementRef, + public readonly changeDetectorRef: ChangeDetectorRef, + ) { } +} diff --git a/desktop/angular/src/app/shared/netquery/.eslintrc.json b/desktop/angular/src/app/shared/netquery/.eslintrc.json new file mode 100644 index 00000000..5ac41541 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/.eslintrc.json @@ -0,0 +1,44 @@ +{ + "extends": "../../../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "projects/safing/ui/tsconfig.lib.json", + "projects/safing/ui/tsconfig.spec.json" + ], + "createDefaultProgram": true + }, + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "sfng", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "sfng", + "style": "kebab-case" + } + ] + } + }, + { + "files": [ + "*.html" + ], + "rules": {} + } + ] +} diff --git a/desktop/angular/src/app/shared/netquery/add-to-filter/add-to-filter.ts b/desktop/angular/src/app/shared/netquery/add-to-filter/add-to-filter.ts new file mode 100644 index 00000000..f87d4910 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/add-to-filter/add-to-filter.ts @@ -0,0 +1,93 @@ +import { ChangeDetectorRef, Directive, HostBinding, HostListener, Input, OnDestroy, OnInit, inject } from "@angular/core"; +import { NetqueryConnection } from "@safing/portmaster-api"; +import { Subscription, combineLatest } from "rxjs"; +import { ActionIndicatorService } from "../../action-indicator"; +import { NetqueryHelper } from "../connection-helper.service"; +import { INTEGRATION_SERVICE } from "src/app/integration"; + +@Directive({ + selector: '[sfngAddToFilter]' +}) +export class SfngNetqueryAddToFilterDirective implements OnInit, OnDestroy { + private subscription = Subscription.EMPTY; + private readonly integration = inject(INTEGRATION_SERVICE); + + @Input('sfngAddToFilter') + key: keyof NetqueryConnection | null = null; + + @Input('sfngAddToFilterValue') + set value(v: any | any[]) { + if (!Array.isArray(v)) { + v = [v] + } + this._values = v; + } + private _values: any[] = []; + + @HostListener('click', ['$event']) + onClick(evt: MouseEvent) { + if (!this.key) { + return + } + + let prevent = false + if (evt.shiftKey) { + this.helper.addToFilter(this.key, this._values); + prevent = true + } else if (evt.ctrlKey) { + this.integration.writeToClipboard(this._values.join(', ')) + .then(() => { + this.uai.success("Copied to clipboard", "Successfully copied " + this._values.join(", ") + " to your clipboard") + }) + .catch(err => { + this.uai.error("Failed to copy to clipboard", this.uai.getErrorMessgae(err)) + }) + + prevent = true + } + + if (prevent) { + evt.preventDefault(); + evt.stopPropagation(); + } + } + + @HostBinding('class.border-dashed') + @HostBinding('class.border-gray-500') + @HostBinding('class.hover:border-gray-700') + readonly _styleHost = true; + + @HostBinding('class.cursor-pointer') + @HostBinding('class.hover:cursor-pointer') + @HostBinding('class.border-b') + @HostBinding('class.select-none') + get shouldHiglight() { + return this.isShiftKeyPressed || this.isCtrlKeyPressed + } + + isShiftKeyPressed = false; + isCtrlKeyPressed = false; + + constructor( + private helper: NetqueryHelper, + private uai: ActionIndicatorService, + private cdr: ChangeDetectorRef, + ) { } + + ngOnInit(): void { + this.subscription = combineLatest([this.helper.onShiftKey, this.helper.onCtrlKey]) + .subscribe(([isShiftKeyPressed, isCtrlKeyPressed]) => { + if (!this.key) { + return; + } + + this.isShiftKeyPressed = isShiftKeyPressed; + this.isCtrlKeyPressed = isCtrlKeyPressed; + this.cdr.markForCheck(); + }) + } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/shared/netquery/add-to-filter/index.ts b/desktop/angular/src/app/shared/netquery/add-to-filter/index.ts new file mode 100644 index 00000000..1dfab44f --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/add-to-filter/index.ts @@ -0,0 +1 @@ +export * from './add-to-filter'; diff --git a/desktop/angular/src/app/shared/netquery/circular-bar-chart/circular-bar-chart.component.ts b/desktop/angular/src/app/shared/netquery/circular-bar-chart/circular-bar-chart.component.ts new file mode 100644 index 00000000..f2b736d9 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/circular-bar-chart/circular-bar-chart.component.ts @@ -0,0 +1,358 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, ElementRef, Input, OnInit, inject } from '@angular/core'; +import { QueryResult } from '@safing/portmaster-api'; +import * as d3 from 'd3'; + +export interface CircularBarChartConfig { + // stack either holds the attribute name or an accessor function + // to determine which serieses belong to the same stack. + stack: keyof T | ((d: T) => string); + + // series either holds the attribute name of the key or an accessor function. + seriesKey: keyof T | ((d: T) => string); + + seriesLabel?: (s: string) => string; + + // value either holds the attribute name or an accessor function + // to get the value of the series. + value: keyof T | ((d: T) => number); + + colorAsClass?: boolean; + + // the actual series configuration + series?: { + [key: string]: { + color: string; + } + }; + + // The number of ticks for the y axis + ticks?: number; + + formatTick?: (v: number) => string; + + // an optional function to format the value + formatValue?: (stack: string, series: string, value: number, data?: T) => string; + + formatStack?: (sel: d3.Selection, data: T[]) => d3.Selection; +} + + +export function splitQueryResult(results: T[], series: K[]): (QueryResult & { series: string, value: number })[] { + let mapped: (QueryResult & { series: string, value: number })[] = []; + + results.forEach(row => { + series.forEach(seriesKey => { + mapped.push({ + ...row, + value: row[seriesKey], + series: seriesKey as string, + }) + }) + }) + + return mapped +} + +@Component({ + selector: 'sfng-netquery-circular-bar-chart', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CircularBarChartComponent implements OnInit, AfterViewInit { + private readonly elementRef = inject(ElementRef) as ElementRef; + private readonly destroyRef = inject(DestroyRef); + + // D3 related members + private svg?: d3.Selection; + private x?: d3.ScaleBand; + private y?: d3.ScaleRadial; + private height = 0; + private width = 0; + + @Input() + config: CircularBarChartConfig | null = null; + + @Input() + innerRadius?: number; + + @Input() + set data(d: T[] | null) { + this._data = d || []; + + this.prepareChart() + this.render(); + } + private _data: T[] = []; + + ngOnInit(): void { + this.prepareChart() + this.render() + } + + ngAfterViewInit(): void { + const observer = new ResizeObserver(() => { + this.prepareChart() + this.render() + }) + + observer.observe(this.elementRef.nativeElement) + + this.destroyRef.onDestroy(() => observer.disconnect()) + + this.prepareChart() + this.render(); + } + + private prepareChart() { + if (!!this.svg) { + const parent = this.svg.node()?.parentElement + parent?.remove() + } + + const margin = 0.2 + const bbox = this.elementRef.nativeElement.getBoundingClientRect(); + + const marginLeft = bbox.width * margin; + const marginTop = bbox.height * margin; + this.width = bbox.width - 2 * marginLeft; + this.height = bbox.height - 2 * marginTop; + + this.svg = d3.select(this.elementRef.nativeElement) + .append('svg') + .attr('width', "100%") + .attr('height', "100%") + .append('g') + .attr('transform', `translate(${this.width / 2 + marginLeft}, ${this.height / 2 + marginTop})`); + + + this.x = d3.scaleBand() + .range([0, 2 * Math.PI]) + .align(0); + + this.y = d3.scaleRadial() + + // prepare the SVGGElement that we use for rendering + this.svg.append("g") + .attr("id", "chart") + + this.svg.append("g") + .attr("id", "text") + + this.svg.append("g") + .attr("id", "legend") + + this.svg.append("g") + .attr("id", "ticks") + } + + private render() { + const x = this.x; + const y = this.y; + + if (!this.svg || !x || !y) { + console.log("not yet ready") + return; + } + + let stackName: (d: T) => string; + if (typeof this.config?.stack === 'function') { + stackName = this.config.stack; + } else { + stackName = (d: T) => { + return d[this.config!.stack as keyof T] + '' + } + } + + let seriesKey: (d: T) => string; + if (typeof this.config?.seriesKey === 'function') { + seriesKey = this.config!.seriesKey + } else { + seriesKey = (d: T) => { + return d[this.config!.seriesKey as keyof T] + '' + } + } + + let value: (d: T) => number; + if (typeof this.config?.value === 'function') { + value = this.config!.value + } else { + value = (d: T) => { + return +d[this.config!.value as keyof T] + } + } + + let formatValue: Exclude["formatValue"], undefined> = (stack, series, value) => `${stack} ${series}\n${value}` + if (this.config?.formatValue) { + formatValue = this.config.formatValue; + } + + // Prepare the stacked data + const indexed = d3.index(this._data, stackName, seriesKey) + const stackGenerator = d3.stack<[string, d3.InternMap]>() + .keys(d3.union(this._data.map(seriesKey))) + .value((data, key) => { + const obj = data[1].get(key) + if (obj === undefined) { + return 0 + } + + return value(obj); + }) + + const series = stackGenerator(indexed) + + // Prepare the x domain + const labels = new Set(); + this._data.forEach(d => labels.add(stackName(d))); + this.x!.domain(Array.from(labels)) + .range([0, 2 * Math.PI]) + .align(0); + + const innerRadius = this.innerRadius || (() => { + return (series.length * 25) + 20 + })() + + // Prepare the x domain + const outerRadius = Math.min(this.width, this.height) / 2; + const highest = d3.max(series, point => d3.max(point, point => point[1])!)! + this.y!.domain([0, highest]) + .range([innerRadius, outerRadius]); + + + const arc = d3.arc() + .innerRadius((d: any) => y(d[0])) + .outerRadius((d: any) => y(d[1])) + .startAngle((d: any) => x(d.data[0])!) + .endAngle((d: any) => x(d.data[0])! + x.bandwidth()) + .padAngle(0.01) + .padRadius(innerRadius) + + let color: (key: string) => string; + + if (!this.config?.series) { + const colorScale: d3.ScaleOrdinal = d3.scaleOrdinal() + .domain(series.map(d => d.key)) + .range(d3.schemeSpectral) + .unknown("#ccc") + + color = key => colorScale(key); + } else { + color = key => this.config!.series![key].color + } + + this.svg.select("g#chart") + .selectAll() + .data(series) + .join("g") + .call(g => { + if (this.config?.colorAsClass) { + g.attr("fill", "currentColor") + .attr("class", d => color(d.key)) + } else { + g.attr("fill", d => color(d.key)) + } + }) + .selectAll("path") + .data(D => D.map(d => ((d as any).key = D.key, d))) + .join("path") + .attr("d", arc as any) + .append("title") + .text(d => { + const stack = d.data[0] + const series = (d as any).key + const data = d.data[1].get(series); + const seriesValue = data ? value(data) : 0; + + return formatValue(stack, series, seriesValue, data); + }) + + const sumPerLabel = this._data.reduce((map, current) => { + const stack = stackName(current) + let sum = map.get(stack) || 0 + sum += value(current) + map.set(stack, sum) + + return map + }, new Map()); + + this.svg.select("g#text") + .attr("text-anchor", "middle") + .selectAll() + .data(x.domain()) + .join("g") + .attr("text-anchor", d => (x(d)! + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "end" : "start") + .attr("transform", d => "rotate(" + ((x(d)! + this.x!.bandwidth() / 2) * 180 / Math.PI - 90) + ")" + "translate(" + (y(sumPerLabel.get(d)!) + 10) + ",0)") + .append("g") + .attr("transform", d => (x(d)! + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)") + .style("font-size", "11px") + .attr("alignment-baseline", "middle") + .attr("fill", "currentColor") + .attr("class", "text-primary cursor-pointer") + .on("mouseenter", function (data) { + d3.select(this) + .classed("underline", true) + }) + .on("mouseleave", function (data) { + d3.select(this) + .classed("underline", false) + }) + .call(g => { + if (!this.config?.formatStack) { + return g.append("text") + .text(d => `${d}`) + } + + return this.config.formatStack(g as any, this._data) + }) + + // y axis + const tickCount = this.config?.ticks || Math.floor((outerRadius - innerRadius) / 20) + const tickFormat = this.config?.formatTick || y.tickFormat(tickCount, "s") + this.svg.select("g#ticks") + .attr("text-anchor", "middle") + .selectAll("g") + .data(y.ticks(tickCount).slice(1)) + .join("g") + .attr("fill", "none") + .call(g => g.append("circle") + .attr("stroke", "#fff") + .attr("stroke-opacity", 0.25) + .attr("r", y)) + .call(g => g.append("text") + .style("font-size", "0.6rem") + .attr("y", d => -y(d)) + .attr("dy", "0.35em") + .attr("fill", "currentColor") + .attr("class", "text-secondary") + .text(tickFormat)) + + // color legend + this.svg.select("g#legend") + .selectAll() + .data(series.map(s => s.key)) + .join("g") + .attr("transform", (d, i, nodes) => `translate(-40,${(nodes.length / 2 - i - 1) * 20})`) + .call(g => g.append("circle") + .attr("r", 5) + .call(g => { + if (this.config?.colorAsClass) { + g.attr("fill", "currentColor") + .attr("class", d => color(d)) + } else { + g.attr("fill", d => color(d)) + } + })) + .call(g => g.append("text") + .attr("x", 12) + .attr("y", 4) + .attr("font-size", "0.6rem") + .attr("fill", "#fff") + .text(d => { + if (!!this.config?.seriesLabel) { + return this.config.seriesLabel(d) + } + + return d + })); + } +} diff --git a/desktop/angular/src/app/shared/netquery/combined-menu.pipe.ts b/desktop/angular/src/app/shared/netquery/combined-menu.pipe.ts new file mode 100644 index 00000000..610e15a1 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/combined-menu.pipe.ts @@ -0,0 +1,16 @@ +import { KeyValue } from '@angular/common'; +import { Pipe, PipeTransform } from "@angular/core"; + +interface Model { + visible: boolean | 'combinedMenu'; +} + +@Pipe({ + pure: true, + name: 'combinedMenu' +}) +export class CombinedMenuPipe implements PipeTransform { + transform(value: KeyValue[], ...args: any[]) { + return value.filter(entry => entry.value?.visible === 'combinedMenu') + } +} diff --git a/desktop/angular/src/app/shared/netquery/connection-details/conn-details.html b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.html new file mode 100644 index 00000000..80d604f6 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.html @@ -0,0 +1,322 @@ +
+
+ + Started: + + {{ conn.started | date:'medium'}} + + + + + Ended: + + {{ conn.ended | date:'medium'}} + + + + + + + + Duration: + + {{ [conn.ended, conn.started] | duration }} + + + + + Profile Revision: + + {{ conn.profile_revision }} + + + + + Connection ID: + + {{ conn.id }} + + + + + Verdict: + + {{ verdict[conn.verdict] || 'N/A' }} + + + + + Internal Connection: + + {{ conn.internal ? 'Yes' : 'No' }} + + + + + Local Address: + + {{ conn.local_ip }} + {{ ':'+conn.local_port }} + + +
+ +
+ + Direction: + + + + {{ conn.direction === 'inbound' ? 'Incoming' : 'Outgoing' }} + + + + Protocol: + {{ Protocols[conn.ip_protocol] || 'N/A' }} + + + Encrypted: + {{ conn.encrypted ? 'yes' : 'no' }} + + + SPN Protected: + {{ conn.tunneled ? 'yes' : 'no' }} + + + + Data Received: + {{ conn.bytes_received | bytes }} + + + Data Sent: + {{ conn.bytes_sent | bytes }} + + + + + TLS Version: + {{ tls.Version }} + + + TLS SNI: + {{ tls.SNI }} + + + + + TLS Certificate: + {{ firstChain[0].Subject }} by {{ firstChain[0].Issuer }} + + + Trust-Chain + +
    +
  1. + {{ cert.Subject }} by {{ cert.Issuer }} +
  2. +
+
+
+
+
+
+
+ + +
+ + Domain: + {{dns.Domain}} + + + Query: + {{dns.Question}} + + + + Response: + {{dns.RCode}} + + + + Served from Cache: + {{dns.ServedFromCache ? 'yes' : 'no'}} + + + + Expires: + {{dns.Expires | date:'medium'}} + +
+
+ +
+ + Domain: + + + + + + Scope: + + Internet Peer-to-Peer + Internet Multicast + Device-Local + LAN Peer-to-Peer + LAN Multicast + LAN Peer-to-Peer + + N/A + N/A + N/A + + + {{ conn.direction === 'inbound' ? ' Incoming' : ' Outgoing'}} + + + + Remote Peer: + + + {{ conn.remote_ip || 'DNS Request'}} + {{ ':'+conn.remote_port }} + + + + Country: + {{ conn.country || 'N/A'}} + + + ASN: + {{ conn.asn || 'N/A' }} + + + AS Org: + {{ conn.as_owner || 'N/A' }} + +
+ +
+ + Binary Path: + {{ conn.path }} + + + Reason: + + {{conn.extra_data?.reason?.Msg}} + + + + Applied Setting: + + {{ helper.settings[option] || '' }}  +  from {{ + !!conn.extra_data?.reason?.Profile ? "App" : + "Global" }} Settings + + + + +
+ +
+

SPN Tunnel

+ + + This connection has not been routed through the Safing Privacy Network. + + + +
+
+ + + + +
+
+ + Path Costs: + {{ conn.extra_data?.tunnel?.PathCost }} + + + Routing Algorithm: + {{ conn.extra_data?.tunnel?.RoutingAlg }} + +
+
+ + + The connection was routed through the Safing Privacy Network, but the tunnel information is not available. Try + reloading the connections. + +
+
+ +
+

Data Usage

+ +
+
+ +
+ + + + + + + + +
diff --git a/desktop/angular/src/app/shared/netquery/connection-details/conn-details.scss b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.scss new file mode 100644 index 00000000..f850b003 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.scss @@ -0,0 +1,114 @@ +:host { + section { + display: grid; + + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + + width: 100%; + overflow: hidden; + gap: 1.5rem; + } +} + +section { + &>div { + @apply flex flex-col gap-2 items-start justify-start text-xxs; + + &>span { + @apply space-x-1 text-ellipsis block overflow-hidden w-full; + + &>span:first-child { + @apply text-secondary whitespace-nowrap; + } + + &>span:last-child { + @apply whitespace-nowrap; + } + } + } +} + + +.tunnel-path { + position: relative; + + .line { + position: absolute; + top: 10px; + bottom: 10px; + left: 8px; + width: 1px; + background-color: rgba(255, 255, 255, 0.1); + } + + .node-tag { + border-radius: 1px solid rgba(255, 255, 255, 0.2); + background-color: rgba(255, 255, 255, 0.1); + padding: 2px; + font-size: 85%; + border-radius: 2px; + transform: scale(0.85); + } + + ul { + position: relative; + padding-left: 20px; + + li:not(:last-of-type) { + padding-bottom: 0.35rem; + } + + .ip { + margin-left: 0.35rem; + } + + .hop-icon { + display: inline-block; + margin-left: -17px; + margin-right: 4px; + font-weight: 400; + + &.country { + margin-left: -20px; + } + } + + .hop-title { + margin-right: 2px; + } + + .country { + display: inline-block; + margin-left: -20px; + margin-right: 4px; + + &.unknown { + height: 14px; + width: 16px; + position: relative; + top: 3px; + border: 1px solid rgba(0, 0, 0, 0.25); + opacity: 0.5; + border-radius: 3px; + @apply bg-buttons-icon; + } + } + } +} + + +@keyframes arrow_move { + 0% { + top: 0%; + opacity: 1; + } + + 85% { + opacity: 1; + } + + 100% { + top: 95%; + opacity: 0; + } +} diff --git a/desktop/angular/src/app/shared/netquery/connection-details/conn-details.ts b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.ts new file mode 100644 index 00000000..78dfecda --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.ts @@ -0,0 +1,147 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, inject } from "@angular/core"; +import { BandwidthChartResult, ConnectionBandwidthChartResult, IPProtocol, IPScope, IsDenied, IsDNSRequest, Netquery, NetqueryConnection, PortapiService, Process, Verdict } from "@safing/portmaster-api"; +import { SfngDialogService } from '@safing/ui'; +import { Subscription } from "rxjs"; +import { ProcessDetailsDialogComponent } from '../../process-details-dialog'; +import { NetqueryHelper } from "../connection-helper.service"; +import { BytesPipe } from "../../pipes/bytes.pipe"; +import { formatDuration } from "../../pipes"; + + + +@Component({ + selector: 'sfng-netquery-conn-details', + styleUrls: ['./conn-details.scss'], + templateUrl: './conn-details.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngNetqueryConnectionDetailsComponent implements OnInit, OnDestroy, OnChanges { + helper = inject(NetqueryHelper) + private readonly portapi = inject(PortapiService) + private readonly dialog = inject(SfngDialogService) + private readonly cdr = inject(ChangeDetectorRef) + private readonly netquery = inject(Netquery) + + @Input() + conn: NetqueryConnection | null = null; + + process: Process | null = null; + + readonly IsDNS = IsDNSRequest; + readonly verdict = Verdict; + readonly Protocols = IPProtocol; + readonly scopes = IPScope; + private _subscription = Subscription.EMPTY; + + formatBytes = (n: d3.NumberValue, seriesKey?: string) => { + let prefix = ''; + if (seriesKey !== undefined) { + prefix = seriesKey === 'incoming' ? 'Received: ' : 'Sent: ' + } + return prefix + new BytesPipe().transform(n.valueOf()) + } + + formatTime = (n: Date) => { + const diff = Math.floor(new Date().getTime() - n.getTime()) + return formatDuration(diff, false, true) + " ago" + } + + tooltipFormat = (n: BandwidthChartResult) => { + const bytes = new BytesPipe().transform + const received = `Received: ${bytes(n?.incoming || 0)}`; + const sent = `Sent: ${bytes(n?.outgoing || 0)}` + + if ((n?.incoming || 0) > (n?.outgoing || 0)) { + return `${received}\n${sent}` + } + return `${sent}\n${received}` + } + + connectionNotice: string = ''; + bwData: ConnectionBandwidthChartResult[] = []; + + ngOnChanges(changes: SimpleChanges) { + if (!!changes?.conn) { + this.updateConnectionNotice(); + this.loadBandwidthChart(); + + if (this.conn?.extra_data?.pid !== undefined) { + this.portapi.get(`network:tree/${this.conn.extra_data.pid}-${this.conn.extra_data.processCreatedAt}`) + .subscribe({ + next: p => { + this.process = p; + this.cdr.markForCheck(); + }, + error: () => { + this.process = null; // the process does not exist anymore + this.cdr.markForCheck(); + } + }) + } else { + this.process = null; + } + } + } + + ngOnInit() { + this._subscription = this.helper.refresh.subscribe(() => { + this.updateConnectionNotice(); + this.loadBandwidthChart(); + + this.cdr.markForCheck(); + }) + } + + ngOnDestroy() { + this._subscription.unsubscribe(); + } + + openProcessDetails() { + this.dialog.create(ProcessDetailsDialogComponent, { + data: this.process, + backdrop: true, + autoclose: true, + }) + } + + private loadBandwidthChart() { + this.bwData = []; + + if (!this.conn) { + this.cdr.markForCheck() + + return; + } + + this.netquery.connectionBandwidthChart([this.conn!.id], 1) + .subscribe(result => { + if (!result[this.conn!.id]?.length) { + return; + } + + this.bwData = result[this.conn!.id]; + + this.cdr.markForCheck(); + }); + } + + private updateConnectionNotice() { + this.connectionNotice = ''; + if (!this.conn) { + return; + } + + if (this.conn!.verdict === Verdict.Failed) { + this.connectionNotice = 'Failed with previous settings.' + return; + } + + if (IsDenied(this.conn!.verdict)) { + this.connectionNotice = 'Blocked by previous settings.'; + } else { + this.connectionNotice = 'Allowed by previous settings.'; + } + + this.connectionNotice += ' You current settings could decide differently.' + } +} diff --git a/desktop/angular/src/app/shared/netquery/connection-details/index.ts b/desktop/angular/src/app/shared/netquery/connection-details/index.ts new file mode 100644 index 00000000..1740e308 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-details/index.ts @@ -0,0 +1 @@ +export * from './conn-details'; diff --git a/desktop/angular/src/app/shared/netquery/connection-helper.service.ts b/desktop/angular/src/app/shared/netquery/connection-helper.service.ts new file mode 100644 index 00000000..fbe1b769 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-helper.service.ts @@ -0,0 +1,537 @@ +import { DOCUMENT } from '@angular/common'; +import { Inject, Injectable, Renderer2, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { AppProfile, AppProfileService, ConfigService, IPScope, NetqueryConnection, Pin, PossilbeValue, QueryResult, SPNService, Verdict, deepClone, flattenProfileConfig, getAppSetting, setAppSetting } from '@safing/portmaster-api'; +import { BehaviorSubject, Observable, OperatorFunction, Subject, combineLatest } from 'rxjs'; +import { distinctUntilChanged, filter, map, switchMap, take, takeUntil } from 'rxjs/operators'; +import { ActionIndicatorService } from '../action-indicator'; +import { objKeys } from '../utils'; +import { SfngSearchbarFields } from './searchbar'; +import { INTEGRATION_SERVICE } from 'src/app/integration'; + +export const IPScopeNames: { [key in IPScope]: string } = { + [IPScope.Invalid]: "Invalid", + [IPScope.Undefined]: "Undefined", + [IPScope.HostLocal]: "Device Local", + [IPScope.LinkLocal]: "Link Local", + [IPScope.SiteLocal]: "LAN", + [IPScope.Global]: "Internet", + [IPScope.LocalMulticast]: "LAN Multicast", + [IPScope.GlobalMulitcast]: "Internet Multicast" +} + +export interface LocalAppProfile extends AppProfile { + FlatConfig: { [key: string]: any } +} + +@Injectable() +export class NetqueryHelper { + readonly settings: { [key: string]: string } = {}; + + refresh = new Subject(); + + private onShiftKey$ = new BehaviorSubject(false); + private onCtrlKey$ = new BehaviorSubject(false); + private addToFilter$ = new Subject(); + private destroy$ = new Subject(); + private appProfiles$ = new BehaviorSubject([]); + private spnMapPins$ = new BehaviorSubject(null); + private readonly integration = inject(INTEGRATION_SERVICE); + + readonly onShiftKey: Observable; + readonly onCtrlKey: Observable; + + constructor( + private router: Router, + private profileService: AppProfileService, + private configService: ConfigService, + private actionIndicator: ActionIndicatorService, + private renderer: Renderer2, + private spnService: SPNService, + @Inject(DOCUMENT) private document: Document, + ) { + const cleanupKeyDown = this.renderer.listen(this.document, 'keydown', (event: KeyboardEvent) => { + if (event.shiftKey) { + this.onShiftKey$.next(true) + } + if (event.ctrlKey) { + this.onCtrlKey$.next(true); + } + }); + + const cleanupKeyUp = this.renderer.listen(this.document, 'keyup', () => { + this.onShiftKey$.next(false); + this.onCtrlKey$.next(false); + }) + + const windowBlur = this.renderer.listen(window, 'blur', () => { + this.onShiftKey$.next(false); + this.onCtrlKey$.next(false); + }) + + this.destroy$.subscribe({ + complete: () => { + cleanupKeyDown(); + cleanupKeyUp(); + windowBlur(); + } + }) + + this.onShiftKey = this.onShiftKey$ + .pipe(distinctUntilChanged()); + + this.onCtrlKey = this.onCtrlKey$ + .pipe(distinctUntilChanged()); + + this.configService.query('') + .subscribe(settings => { + settings.forEach(setting => { + this.settings[setting.Key] = setting.Name; + }); + this.refresh.next(); + }); + + // watch all application profiles + this.profileService.watchProfiles() + .pipe(takeUntil(this.destroy$)) + .subscribe(profiles => { + this.appProfiles$.next((profiles || []).map(p => { + return { + ...p, + FlatConfig: flattenProfileConfig(p.Config), + } + })) + }); + + this.spnService.watchPins() + .pipe(takeUntil(this.destroy$)) + .subscribe(pins => { + this.spnMapPins$.next(pins); + }) + } + + decodePrettyValues(field: keyof NetqueryConnection, values: any[]): any[] { + if (field === 'verdict') { + return values.map(val => Verdict[val]).filter(value => value !== undefined); + } + + if (field === 'scope') { + return values.map(val => { + // check if it's a value of the IPScope enum + const scopeValue = IPScope[val]; + if (!!scopeValue) { + return scopeValue; + } + + // otherwise check if it's pretty name of the scope translation + val = `${val}`.toLocaleLowerCase(); + return objKeys(IPScopeNames).find(scope => IPScopeNames[scope].toLocaleLowerCase() === val) + }).filter(value => value !== undefined); + } + + if (field === 'allowed') { + return values.map(val => { + if (typeof val !== 'string') { + return val + } + + switch (val.toLocaleLowerCase()) { + case 'yes': + return true + case 'no': + return false + case 'n/a': + case 'null': + return null + default: + return val + } + }) + } + + if (field === 'exit_node') { + const lm = new Map(); + (this.spnMapPins$.getValue() || []) + .forEach(pin => lm.set(pin.Name, pin)); + + return values.map(val => lm.get(val)?.ID || val) + } + + return values; + } + + attachProfile(): OperatorFunction { + return source => combineLatest([ + source, + this.appProfiles$, + ]).pipe( + map(([items, profiles]) => { + let lm = new Map(); + profiles.forEach(profile => { + lm.set(`${profile.Source}/${profile.ID}`, profile) + }) + + return items.map(item => { + if ('profile' in item) { + item.__profile = lm.get(item.profile!) + } + + return item; + }) + }) + ) + } + + attachPins(): OperatorFunction { + return source => combineLatest([ + source, + this.spnMapPins$ + .pipe( + filter(result => result !== null), + take(1), + ), + ]).pipe( + map(([items, pins]) => { + let lm = new Map(); + pins!.forEach(pin => { + lm.set(pin.ID, pin) + }) + + return items.map(item => { + if ('exit_node' in item) { + item.__exitNode = lm.get(item.exit_node!) + } + + return item; + }) + }) + ) + } + + encodeToPossibleValues(field: string): OperatorFunction { + return source => combineLatest([ + source, + this.appProfiles$, + this.spnMapPins$, + ]).pipe( + map(([items, profiles, pins]) => { + // convert profile IDs to profile name + if (field === 'profile') { + let lm = new Map(); + profiles.forEach(profile => { + lm.set(`${profile.Source}/${profile.ID}`, profile) + }) + + return items.map((item: any) => { + const profile = lm.get(item.profile!) + return { + Name: profile?.Name || `${item.profile}`, + Value: item.profile!, + Description: '', + __profile: profile || null, + ...item, + } + }) + } + + // convert verdict identifiers to their pretty name. + if (field === 'verdict') { + return items.map(item => { + if (Verdict[item.verdict!] === undefined) { + return null + } + + return { + Name: Verdict[item.verdict!], + Value: item.verdict, + Description: '', + ...item + } + }) + } + + // convert the IP scope identifier to a pretty name + if (field === 'scope') { + return items.map(item => { + if (IPScope[item.scope!] === undefined) { + return null + } + + return { + Name: IPScopeNames[item.scope!], + Value: item.scope, + Description: '', + ...item + } + }) + } + + if (field === 'allowed') { + return items + // we remove any "null" value from allowed here as it may happen for a really short + // period of time and there's no reason to actually filter for them because + // from showing a "null" value to the user clicking it the connection will have been + // verdicted and thus no results will show up for "null". + .filter(item => typeof item.allowed === 'boolean') + .map(item => { + return { + Name: item.allowed ? 'Yes' : 'No', + Value: item.allowed, + Description: '', + ...item + } + }) + } + + if (field === 'exit_node') { + const lm = new Map(); + pins!.forEach(pin => lm.set(pin.ID, pin)); + + return items.map(item => { + const pin = lm.get(item.exit_node!); + return { + Name: pin?.Name || item.exit_node, + Value: item.exit_node, + Description: 'Operated by ' + (pin?.VerifiedOwner || 'N/A'), + ...item + } + }) + } + + // the rest is just converted into the {@link PossibleValue} form + // by using the value as the "Name". + return items.map(item => ({ + Name: `${item[field]}`, + Value: item[field], + Description: '', + ...item, + })) + }), + // finally, remove any values that have been mapped to null in the above stage. + // this may happen for values that are not valid for the given model field (i.e. using "Foobar" for "verdict") + map(results => { + return results.filter(val => !!val) + }) + ) + } + + dispose() { + this.onShiftKey$.complete(); + + this.destroy$.next(); + this.destroy$.complete(); + } + + /** Emits added fields whenever addToFilter is called */ + onFieldsAdded(): Observable { + return this.addToFilter$.asObservable(); + } + + /** Adds a new filter to the current query */ + addToFilter(key: string, value: any[]) { + this.addToFilter$.next({ + [key]: value, + }) + } + + /** + * @private + * Returns the class used to color the connection's + * verdict. + * + * @param conn The connection object + */ + getVerdictClass(conn: NetqueryConnection): string { + return Verdict[conn.verdict]?.toLocaleLowerCase() || `unknown-verdict<${conn.verdict}>`; + } + + /** + * @private + * Redirect the user to a settings key in the application + * profile. + * + * @param key The settings key to redirect to + */ + redirectToSetting(setting: string, conn: NetqueryConnection, globalSettings = false) { + const reason = conn.extra_data?.reason; + if (!reason) { + return; + } + + if (!setting) { + setting = reason.OptionKey; + } + + if (!setting) { + return; + } + + if (globalSettings) { + this.router.navigate( + ['/', 'settings'], { + queryParams: { + setting: setting, + } + }) + return; + } + + let profile = conn.profile + + if (!!reason.Profile) { + profile = reason.Profile; + } + + if (profile.startsWith("core:profiles/")) { + profile = profile.replace("core:profiles/", "") + } + + this.router.navigate( + ['/', 'app', ...profile.split("/")], { + queryParams: { + tab: 'settings', + setting: setting, + } + }) + } + + /** + * @private + * Redirect the user to "outgoing rules" setting in the + * application profile/settings. + */ + redirectToRules(conn: NetqueryConnection) { + if (conn.direction === 'inbound') { + this.redirectToSetting('filter/serviceEndpoints', conn); + } else { + this.redirectToSetting('filter/endpoints', conn); + } + } + + /** + * @private + * Dump a connection to the console + * + * @param conn The connection to dump + */ + async dumpConnection(conn: NetqueryConnection) { + // Copy to clip-board if supported + try { + await this.integration.writeToClipboard(JSON.stringify(conn, undefined, " ")) + this.actionIndicator.info("Copied to Clipboard") + } catch (err: any) { + this.actionIndicator.error("Copy to Clipboard Failed", err?.message || JSON.stringify(err)) + } + } + + /** + * @private + * Creates a new "block domain" outgoing rules + */ + blockAll(domain: string, conn: NetqueryConnection) { + /* Deactivate until exact behavior is specified. + if (this.isDomainBlocked(domain)) { + this.actionIndicator.info(domain + ' already blocked') + return; + } + */ + + domain = domain.replace(/\.+$/, ''); + const newRule = `- ${domain}`; + this.updateRules(newRule, true, conn) + } + + /** + * @private + * Removes a "block domain" rule from the outgoing rules + */ + unblockAll(domain: string, conn: NetqueryConnection) { + /* Deactivate until exact behavior is specified. + if (!this.isDomainBlocked(domain)) { + this.actionIndicator.info(domain + ' already allowed') + return; + } + */ + + domain = domain.replace(/\.+$/, ''); + const newRule = `+ ${domain}`; + this.updateRules(newRule, true, conn); + } + + /** + * Updates the outgoing rule set and either creates or deletes + * a rule. If a rule should be created but already exists + * it is moved to the top. + * + * @param newRule The new rule to create or delete. + * @param add Whether or not to create or delete the rule. + */ + private updateRules(newRule: string, add: boolean, conn: NetqueryConnection) { + if (!conn.profile) { + return + } + + let key = 'filter/endpoints'; + if (conn.direction === 'inbound') { + key = 'filter/serviceEndpoints' + } + + this.profileService.getAppProfile(conn.profile) + .pipe( + switchMap(profile => { + let rules = getAppSetting(profile.Config, key) || []; + rules = rules.filter(rule => rule !== newRule); + + if (add) { + rules.splice(0, 0, newRule) + } + + const newProfile = deepClone(profile); + + if (newProfile.Config === null || newProfile.Config === undefined) { + newProfile.Config = {} + } + + setAppSetting(newProfile.Config, key, rules); + + return this.profileService.saveProfile(newProfile) + }) + ) + .subscribe({ + next: () => { + if (add) { + this.actionIndicator.success('Rules Updated', 'Successfully created a new rule.') + } else { + this.actionIndicator.success('Rules Updated', 'Successfully removed matching rule.') + } + }, + error: err => { + this.actionIndicator.error('Failed to update rules', JSON.stringify(err)) + } + }); + } + + /** + * Iterates of all outgoing rules and collects which domains are blocked. + * It stops collecting domains as soon as the first "allow something" rule + * is hit. + */ + // FIXME + /* + private collectBlockedDomains() { + let blockedDomains = new Set(); + + const rules = getAppSetting(this.profile!.profile!.Config, 'filter/endpoints') || []; + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + if (rule.startsWith('+ ')) { + break; + } + + blockedDomains.add(rule.slice(2)) + } + + this.blockedDomains = Array.from(blockedDomains) + } + */ +} diff --git a/desktop/angular/src/app/shared/netquery/connection-row/conn-row.html b/desktop/angular/src/app/shared/netquery/connection-row/conn-row.html new file mode 100644 index 00000000..3c721b0b --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-row/conn-row.html @@ -0,0 +1,146 @@ +
+ + + + + + + + + + + + + + + Internet Peer-to-Peer + Internet Multicast + Device-Local + LAN Peer-to-Peer + LAN Multicast + LAN Peer-to-Peer + + N/A + N/A + N/A + + + {{ conn.direction === 'inbound' ? ' Incoming' : ' Outgoing'}} + + +
+ + +
+ + {{ conn.country | countryName }} +
+
+ + + +
+ + + {{ conn.__profile.Name }} + +
+ +
+ + + + + {{ conn.remote_ip }} :{{ conn.remote_port }} + + + + DNS Request + + +
+ + + + + +
+ + + + + App Setting + + + + Global Setting + + + + + + + Allow {{ conn.domain ? 'Domain' : 'IP'}} + + + + + Block {{ conn.domain ? 'Domain' : 'IP '}} + + + + + Copy JSON + +
diff --git a/desktop/angular/src/app/shared/netquery/connection-row/conn-row.scss b/desktop/angular/src/app/shared/netquery/connection-row/conn-row.scss new file mode 100644 index 00000000..0d737830 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-row/conn-row.scss @@ -0,0 +1,43 @@ +:host { + @apply w-full flex-grow gap-4 grid justify-start items-center overflow-hidden; + + grid-template-columns: + 1fr 1fr 1fr 2rem; + + grid-auto-rows: 1.5rem; + grid-template-rows: none; + + &>* { + @apply overflow-hidden whitespace-nowrap; + + &>*:last-child { + @apply overflow-hidden text-ellipsis; + } + } + + --app-icon-size: 20px; +} + +:host-context(.min-width-768px) { + :host { + grid-template-columns: + 1fr 4rem 1fr 1fr 5rem 2rem; + ; + } +} + +:host-context(.min-width-1024px) { + :host { + grid-template-columns: + 1fr 4rem 1fr 1fr 5rem 0.5fr 2rem; + ; + } +} + +:host-context(.min-width-1280px) { + :host { + grid-template-columns: + 1fr 4rem 1fr 1fr 8rem 1fr 2rem; + ; + } +} diff --git a/desktop/angular/src/app/shared/netquery/connection-row/conn-row.ts b/desktop/angular/src/app/shared/netquery/connection-row/conn-row.ts new file mode 100644 index 00000000..b841d116 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-row/conn-row.ts @@ -0,0 +1,78 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { AppProfile, IPScope, NetqueryConnection, Verdict } from "@safing/portmaster-api"; +import { interval, Subscription } from "rxjs"; +import { share, startWith } from "rxjs/operators"; +import { NetqueryHelper } from "../connection-helper.service"; + +interface ProfileAttachedConnection extends NetqueryConnection { + __profile?: AppProfile; +} + +@Component({ + selector: 'sfng-netquery-connection-row', + templateUrl: './conn-row.html', + styleUrls: [ + './conn-row.scss' + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngNetqueryConnectionRowComponent implements OnInit, OnDestroy { + readonly scopes = IPScope; + readonly verdicts = Verdict; + + @Input() + set conn(c: ProfileAttachedConnection) { + this._conn = c; + } + get conn() { return this._conn; } + _conn!: ProfileAttachedConnection; + + @Input() + activeRevision: number | undefined = 0; + + get isOutdated() { + // FIXME(ppacher) + return false; + /* + if (!this.conn || !this.helper.profile) { + return false; + } + if (this.helper.profile.currentProfileRevision === -1) { + // we don't know the revision counter yet ... + return false; + } + return this.conn.profile_revision !== this.helper.profile.currentProfileRevision; + */ + } + + /* timeAgoTicker ticks every 10000 seconds to force a refresh + of the timeAgo pipes */ + timeAgoTicker: number = 0; + + private _subscription = Subscription.EMPTY; + + constructor( + public helper: NetqueryHelper, + private changeDetectorRef: ChangeDetectorRef, + ) { } + + ngOnInit() { + this._subscription = new Subscription(); + + const tickerSub = interval(10000).pipe( + startWith(-1), + share() + ).subscribe(i => this.timeAgoTicker = i); + + const helperSub = this.helper.refresh.subscribe(() => { + this.changeDetectorRef.markForCheck(); + }) + + this._subscription.add(helperSub); + this._subscription.add(tickerSub); + } + + ngOnDestroy() { + this._subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/shared/netquery/connection-row/index.ts b/desktop/angular/src/app/shared/netquery/connection-row/index.ts new file mode 100644 index 00000000..adbe1933 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/connection-row/index.ts @@ -0,0 +1 @@ +export * from './conn-row'; diff --git a/desktop/angular/src/app/shared/netquery/index.ts b/desktop/angular/src/app/shared/netquery/index.ts new file mode 100644 index 00000000..84660587 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/index.ts @@ -0,0 +1,2 @@ +export * from './netquery.component'; +export * from './netquery.module'; diff --git a/desktop/angular/src/app/shared/netquery/line-chart/index.ts b/desktop/angular/src/app/shared/netquery/line-chart/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/desktop/angular/src/app/shared/netquery/line-chart/line-chart.ts b/desktop/angular/src/app/shared/netquery/line-chart/line-chart.ts new file mode 100644 index 00000000..5a12cb9d --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/line-chart/line-chart.ts @@ -0,0 +1,592 @@ +import { coerceBooleanProperty, coerceNumberProperty, coerceStringArray } from '@angular/cdk/coercion'; +import { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, ElementRef, Input, OnChanges, OnInit, SimpleChanges, inject } from '@angular/core'; +import { BandwidthChartResult, ChartResult } from '@safing/portmaster-api'; +import * as d3 from 'd3'; +import { Selection } from 'd3'; +import { AppComponent } from 'src/app/app.component'; +import { formatDuration, timeAgo } from '../../pipes'; +import { objKeys } from '../../utils'; +import { BytesPipe } from '../../pipes/bytes.pipe'; + +export interface SeriesConfig { + lineColor: string; + areaColor?: string; +} + +export interface Marker { + text: string; + time: Date | number | string; +} + +export interface ChartConfig { + series: { + [key in Exclude]?: SeriesConfig; + }, + time?: { + from: number | string | Date; + to?: number | string | Date; + }, + fromMargin?: number; + toMargin?: number; + valueFormat?: (n: d3.NumberValue, seriesKey?: string) => string, + tooltipFormat?: (data: T) => string; + timeFormat?: (n: Date) => string, + showDataPoints?: boolean; + fillEmptyTicks?: { + interval: number; + }, + verticalMarkers?: Marker[]; +} + +function coerceDate(d: Date | number | string): Date { + if (typeof d === 'string') { + return new Date(d) + } + + if (d instanceof Date) { + return d + } + + if (d < 0) { + return new Date((new Date()).getTime() + d * 1000) + } + + return new Date(d * 1000); +} + +export const DefaultChartConfig: ChartConfig = { + series: { + value: { + lineColor: 'text-green-200', + areaColor: 'text-green-100 text-opacity-25' + }, + countBlocked: { + lineColor: 'text-red-200', + areaColor: 'text-red-100 text-opacity-25' + } + }, +} + +export const DefaultBandwidthChartConfig: ChartConfig> = { + series: { + outgoing: { + lineColor: 'text-deepPurple-500', + areaColor: 'text-deepPurple-700 text-opacity-5', + }, + incoming: { + lineColor: 'text-cyan-800', + areaColor: 'text-cyan-700 text-opacity-5', + }, + }, + time: { + from: -10 * 60, + }, + valueFormat: (n: d3.NumberValue, seriesKey?: string) => { + let prefix = ''; + if (seriesKey !== undefined) { + prefix = seriesKey === 'incoming' ? 'Received: ' : 'Sent: ' + } + return prefix + new BytesPipe().transform(n.valueOf()) + }, + timeFormat: (n: Date) => { + const diff = Math.floor(new Date().getTime() - n.getTime()) + return formatDuration(diff, false, true) + " ago" + }, + tooltipFormat: (n: BandwidthChartResult) => { + const bytes = new BytesPipe().transform + const received = `Received: ${bytes(n?.incoming || 0)}`; + const sent = `Sent: ${bytes(n?.outgoing || 0)}` + + if ((n?.incoming || 0) > (n?.outgoing || 0)) { + return `${received}\n${sent}` + } + return `${sent}\n${received}` + }, + showDataPoints: true, + fillEmptyTicks: { + interval: 60 + }, +} + +export interface SeriesData { + timestamp: number; +} + +@Component({ + selector: 'sfng-netquery-line-chart', + styles: [ + ` + :host { + @apply block h-full w-full; + } + ` + ], + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngNetqueryLineChartComponent implements OnChanges, OnInit, AfterViewInit { + private destroyRef = inject(DestroyRef); + + @Input() + data: D[] = []; + + private preparedData: D[] = []; + + private width = 700; + private height = 250; + + @Input() + set margin(v: any) { + this._margin = coerceNumberProperty(v); + } + get margin() { return this._margin; } + private _margin = 0; + + @Input() + config!: ChartConfig; + + svg!: Selection; + svgInner!: Selection; + yScale!: d3.ScaleLinear; + xScale!: d3.ScaleTime; + xAxis!: Selection; + yAxis!: Selection; + + @Input() + set showAxis(v: any) { + this._showAxis = coerceBooleanProperty(v); + } + get showAxis() { + return this._showAxis; + } + private _showAxis = true; + + constructor( + public chartElem: ElementRef, + private app: AppComponent + ) { } + + ngOnInit() { + if (!this.config) { + this.config = DefaultChartConfig as any; + } + + const observer = new ResizeObserver(() => { + this.redraw(); + }) + + observer.observe(this.chartElem.nativeElement) + + this.destroyRef.onDestroy(() => observer.disconnect()) + + } + + ngAfterViewInit(): void { + requestAnimationFrame(() => { + this.redraw() + }) + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.hasOwnProperty('config') && this.config) { + this.redraw() + return + } + + if (changes.hasOwnProperty('data') && this.data) { + this.drawChart(); + } + } + + get yMargin() { + if (this.showAxis) { + return 16; + } + return 0; + } + + redraw(event?: Event) { + if (!!this.svg) { + this.svg.remove(); + } + + this.initializeChart(); + this.drawChart(); + } + + private initializeChart(): void { + this.width = this.chartElem.nativeElement.getBoundingClientRect().width; + this.height = this.chartElem.nativeElement.getBoundingClientRect().height; + + this.svg = d3 + .select(this.chartElem.nativeElement) + .append('svg') + + this.svg.attr('width', this.width); + this.svg.attr('height', this.height); + + this.svgInner = this.svg + .append('g') + .attr('height', '100%'); + + this.yScale = d3 + .scaleLinear() + + this.xScale = d3.scaleTime(); + + // setup event handlers to higlight the closest data points + let lastClosestIndex = -1; + + if (this.config.showDataPoints) { + const self = this; + this.svg + .on("mousemove", function (event: MouseEvent) { + let x = d3.pointer(event)[0]; + + let closest = self.data.reduce((best, value, idx) => { + let absx = Math.abs(self.xScale(new Date(value.timestamp * 1000)) - x); + if (absx < best.value) { + return { index: idx, value: absx, timestamp: self.data[idx].timestamp } + } + + return best + + }, { index: 0, value: Number.MAX_SAFE_INTEGER, timestamp: 0 }) + + if (lastClosestIndex === closest.index) { + return; + } + lastClosestIndex = closest.index; + + if (self.config.tooltipFormat) { + // append a title to the parent SVG, this is a quick-fix for showing some + // information on the highlighted points + // TODO(ppacher): actually render a nice tooltip there. + let tooltip = self.svg + .select('title.tooltip') + + if (tooltip.empty()) { + tooltip = self.svg.append("title") + .attr("class", "tooltip") + } + + tooltip + .text(self.config.tooltipFormat!(self.data[closest.index])) + } + + self.svgInner + .select(".vertical-marker") + .selectAll(".mouse-position") + .remove() + + self.svgInner + .select(".vertical-marker") + .append("line") + .classed("mouse-position", true) + .attr("x1", d => self.xScale(closest.timestamp * 1000)) + .attr("y1", -10) + .attr("x2", d => self.xScale(closest.timestamp * 1000)) + .attr("y2", self.height - self.yMargin) + .classed("text-secondary text-opacity-50", true) + .attr("stroke", "currentColor") + .attr("stroke-width", 1) + .attr("stroke-dasharray", 2) + + self.svgInner + .select(".points") + .selectAll("circle") + .classed("opacity-100", d => self.xScale.invert(d[0]).getTime() === closest.timestamp * 1000) + }) + .on("mouseleave", function () { + lastClosestIndex = -1; + + self.svg.select("title.tooltip") + .remove() + + self.svg.select("line.mouse-position") + .remove() + + self.svgInner + .select(".points") + .selectAll("circle") + .attr("r", 4) + .classed("opacity-100", false) + }) + } + + objKeys(this.config.series).forEach(seriesKey => { + const seriesConfig = this.config.series[seriesKey]!; + + if (seriesConfig.areaColor) { + this.svgInner + .append('path') + .attr("fill", "currentColor") + .attr("class", `area-${String(seriesKey)} ${(seriesConfig.areaColor || '')}`) + } + + this.svgInner + .append('g') + .append('path') + .style('fill', 'none') + .style('stroke', 'currentColor') + .style('stroke-width', '1') + .attr('class', `line-${String(seriesKey)} ${seriesConfig.lineColor}`) + }) + + this.svgInner.append("g") + .attr("class", "vertical-marker") + + this.svgInner.append("g") + .attr("class", "points") + + if (this.showAxis) { + this.yAxis = this.svgInner + .append('g') + .attr('id', 'y-axis') + .attr('class', 'text-secondary text-opacity-75 ') + .style('transform', 'translate(' + (this.width - this.yMargin) + 'px, 0)'); + + this.xAxis = this.svgInner + .append('g') + .attr('id', 'x-axis') + .attr('class', 'text-secondary text-opacity-50 ') + .style('transform', 'translate(0, ' + (this.height - this.yMargin) + 'px)'); + } + } + + private getTimeRange(): { from: Date, to: Date } { + const time = { + from: this.data[0]?.timestamp || 0, + to: this.data[this.data.length - 1]?.timestamp || 0, + }; + + if (!!this.config.time) { + time.from = coerceDate(this.config.time.from).getTime() / 1000 + + if (this.config.fromMargin) { + time.from = time.from - this.config.fromMargin + } + + if (this.config.time.to) { + time.to = coerceDate(this.config.time.to).getTime() / 1000 + + if (this.config.toMargin) { + time.to = time.to + this.config.toMargin + } + } + } + + return { + from: new Date(time.from * 1000), + to: new Date(time.to * 1000) + }; + } + + private prepareDataSet(data: D[], time: { from: Date, to: Date }) { + const toTimestamp = Math.round(time.to.getTime() / 1000) + const fromTimestamp = Math.round(time.from.getTime() / 1000) + + // first, filter out all elements that are before or after the to date + data = data.filter(d => { + return d.timestamp >= fromTimestamp && d.timestamp <= toTimestamp + }) + + // check if we need to fill empty ticks + if (!this.config.fillEmptyTicks) { + return data; + } + + const interval = this.config.fillEmptyTicks.interval; + + let filledData: D[] = []; + const addEmpty = (ts: number) => { + let empty: any = { + timestamp: ts, + } + + Object.keys(this.config.series) + .forEach(s => empty[s] = 0) + + filledData.push(empty) + } + + let firstElement = data[0]!.timestamp; + if (this.config.time?.from) { + firstElement = Math.round(coerceDate(this.config.time.from).getTime() / 1000) + } + + // add empty values for the start-time until the first element / or the start tme + let lastTimeStamp = fromTimestamp - interval; + for (let ts = lastTimeStamp; ts <= firstElement; ts += interval) { + addEmpty(ts) + } + + // add emepty vaues for each missing tick during the dataset + lastTimeStamp = firstElement; + for (let idx = 0; idx < data.length; idx++) { + const elem = data[idx] + const elemTs = elem.timestamp; + + for (let ts = lastTimeStamp + interval; ts < elemTs; ts += interval) { + addEmpty(ts) + } + + filledData.push(elem) + lastTimeStamp = elemTs + } + + // if there's a specified end-time, add empty ticks from the last datapoint + // to the end-time + if (this.config.time?.to) { + for (let ts = lastTimeStamp + interval; ts <= toTimestamp; ts += interval) { + addEmpty(ts) + } + } + + return filledData + } + + private drawChart(): void { + if (!this.svg) { + return; + } + + if (!this.data?.length) { + return; + } + + this.data.sort((a, b) => a.timestamp - b.timestamp) + + // determine the time range that should be displayed. + const time = this.getTimeRange(); + + // fill empty ticks depending on the configuration. + this.preparedData = this.prepareDataSet(this.data, time) + + this.xScale + .range([0, this.width - this.yMargin]) + .domain([time.from, time.to]); + + this.yScale + .range([0, this.height - this.yMargin]) + .domain([ + d3.max(this.preparedData.map(d => { + return d3.max( + objKeys(this.config.series) + .map(series => { + return d[series] as number + }) + )! + }))! * 1.3, // 30% margin to top + 0 + ]) + + if (this.showAxis) { + const xAxis = d3 + .axisBottom(this.xScale) + .ticks(5) + .tickFormat((val, idx) => { + if (!!this.config.timeFormat) { + return this.config.timeFormat(val as any) + } + return timeAgo(val as any); + }) + + this.xAxis.call(xAxis); + + const yAxis = d3 + .axisLeft(this.yScale) + .ticks(2) + .tickFormat(d => ((this.config.valueFormat || this.yScale.tickFormat(2)) as any)(d, undefined)) + + this.yAxis.call(yAxis); + } + + const line = d3 + .line() + .x(d => d[0]) + .y(d => d[1]) + .curve(d3.curveMonotoneX); + + // define the area + const area = d3.area() + .x(d => d[0]) + .y0(this.height - this.yMargin) + .y1(d => d[1]) + .curve(d3.curveMonotoneX) + + // render vertical markers + const markers = (this.config.verticalMarkers || []) + .filter(marker => !!marker.time) + .map(marker => ({ + text: marker.text, + time: coerceDate(marker.time) + })); + + this.svgInner.select('.vertical-marker') + .selectAll("line.marker") + .data(markers) + .join("line") + .classed("marker", true) + .attr("x1", d => this.xScale(d.time)) + .attr("y1", -10) + .attr("x2", d => this.xScale(d.time)) + .attr("y2", this.height - this.yMargin) + .classed("text-secondary text-opacity-50", true) + .attr("stroke", "currentColor") + .attr("stroke-width", 3) + .attr("stroke-dasharray", 4) + .append("title") + .text(d => d.text) + + // FIXME(ppacher): somehow d3 does not recognize which data points must be removed + // or re-placed. For now, just remove them all + this.svgInner + .select('.points') + .selectAll("circle") + .remove() + + objKeys(this.config.series) + .forEach(seriesKey => { + const config = this.config.series[seriesKey]!; + + let points: [number, number][] = this.preparedData + .map(d => [ + this.xScale(new Date(d.timestamp * 1000)), + this.yScale((d as any)[seriesKey] || 0), + ]) + + let data: [number, number][] = this.preparedData + .map(d => [ + this.xScale(new Date(d.timestamp * 1000)), + this.yScale((d as any)[seriesKey] || 0), + ]) + + if (config.areaColor) { + this.svgInner.selectAll(`.area-${String(seriesKey)}`) + .data([data]) + .attr('d', area(data)) + } + + this.svgInner.select(`.line-${String(seriesKey)}`) + .attr('d', line(data)) + + if (this.config?.showDataPoints) { + this.svgInner + .select('.points') + .selectAll(`circle.point-${String(seriesKey)}`) + .data(points) + .enter() + .append("circle") + .classed(`points-${String(seriesKey)}`, true) + .attr("r", "4") + .attr("fill", "currentColor") + .attr("class", `opacity-0 ${config.lineColor}`) + .attr("cx", d => d[0]) + .attr("cy", d => d[1]) + .append("title") + .text(d => ((this.config.valueFormat || this.yScale.tickFormat(2)) as any)(this.yScale.invert(d[1]), String(seriesKey))) + } + }) + } +} diff --git a/desktop/angular/src/app/shared/netquery/netquery.component.html b/desktop/angular/src/app/shared/netquery/netquery.component.html new file mode 100644 index 00000000..8a05984b --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/netquery.component.html @@ -0,0 +1,388 @@ +
+ + + + +
+ + + +
+ + + Clear All + +
+ +
+ + +
+ +
+ + + + + + Loading ... + + + + + + + + + {{ value.Name || 'N/A' }} + + + + #{{ value.count }} connections + + + + + + + + + + + + {{ item.value || 'N/A' }} + + + + + + + + + + + + + + + +
+

+ Filter by {{ model.value!.menuTitle || model.key }} + +

+
    +
  • + + + {{ value.Name }} + + + + + + +
  • +
+
+
+
+ + +
+ +
+ Search History: + + + + + Quick Settings +
    +
  • + {{ qds.name }} +
  • +
+
+
+ +
+ + + + + {{ keyTranslation[value] || value }} + + + + + + + + + {{ keyTranslation[value] || value }} + + + + +
+
+ +
+
+

Connections

+
+ +
+
+ +
+

Data Usage

+
+ +
+
+ +
+ Loading Chart +
+
+ + +
+ + + + + + + + + + + + {{ keyTranslation[key] || key }} + + + {{ data[key] || 'N/A' }} + + + + + + {{ data.__profile.Name }} + + + + + + ( + + ) + + {{ data.__exitNode.Name }} + + + + + + {{ data[key] || 'N/A' }} + + + + + + + +
+
+ + + Use as filter + App Settings + + +
+ + + + + + +
+
+ + + + + +
+
+ +
+ {{ totalResultCount }} Results + of {{totalConnCount}} total connections + + + + Last Reload: {{ lastReload | timeAgo:(lastReloadTicker|async) }} + +
+ + + + + + +
+ + + + + +
+ All connections ended more than 10 minutes ago and have been removed. +
+ + + + +
+ +
+
+
+
+
+
+ + + + + + + + + +
+
+
+
+
+ + + +
+ + + + + + Loading connections ... +
+
+ +
+ + + + Pro Tip: + + + +
+ + + Press +
CTRL + Space
+ on any page to bring up the quick search box. +
+ + + Use your keyboard arrows to navigate through the search suggestions. Press +
ENTER
to search for the suggestion or use +
Shift + Enter
to add it to the search text. +
+ + + Inside the search box, use +
Ctrl + Space
to force loading suggestions. +
+ + + Use +
Shift + Click
to add connection attributes to the current filter. +
+ + + Hold +
Shift
to highlight attributes that can be used in the filter. +
+ + + Hold +
CTRL
and click attributes to copy them to the clipboard. +
diff --git a/desktop/angular/src/app/shared/netquery/netquery.component.ts b/desktop/angular/src/app/shared/netquery/netquery.component.ts new file mode 100644 index 00000000..d4befb47 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/netquery.component.ts @@ -0,0 +1,1270 @@ +import { coerceArray } from "@angular/cdk/coercion"; +import { FormatWidth, formatDate, getLocaleDateFormat, getLocaleId } from "@angular/common"; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, EventEmitter, Input, LOCALE_ID, OnDestroy, OnInit, Output, QueryList, TemplateRef, TrackByFunction, ViewChildren, inject, isDevMode } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute, Router } from "@angular/router"; +import { BandwidthChartResult, ChartResult, Condition, Database, FeatureID, GreaterOrEqual, IPScope, LessOrEqual, Netquery, NetqueryConnection, OrderBy, Pin, PossilbeValue, Query, QueryResult, SPNService, Select, Verdict } from "@safing/portmaster-api"; +import { Datasource, DynamicItemsPaginator, SelectOption } from "@safing/ui"; +import { BehaviorSubject, Observable, Subject, combineLatest, forkJoin, interval, of } from "rxjs"; +import { catchError, debounceTime, filter, map, share, skip, switchMap, take, takeUntil } from "rxjs/operators"; +import { ActionIndicatorService } from "../action-indicator"; +import { ExpertiseService } from "../expertise"; +import { objKeys } from "../utils"; +import { fadeInAnimation } from './../animations'; +import { IPScopeNames, LocalAppProfile, NetqueryHelper } from "./connection-helper.service"; +import { SfngSearchbarFields } from "./searchbar"; +import { SfngTagbarValue } from "./tag-bar"; +import { Parser } from "./textql"; +import { connectionFieldTranslation, mergeConditions } from "./utils"; +import { DefaultBandwidthChartConfig } from "./line-chart/line-chart"; +import { INTEGRATION_SERVICE } from "src/app/integration"; + +interface Suggestion extends PossilbeValue { + count: number; + selected?: boolean; +} + +interface Model { + suggestions: Suggestion[]; + searchValues: any[]; + visible: boolean | 'combinedMenu'; + menuTitle?: string; + loading: boolean; + tipupKey?: string; + virtual?: boolean; +} + +const freeTextSearchFields: (keyof Partial)[] = [ + 'domain', + 'as_owner', + 'path', + 'profile_name', +] + +const groupByKeys: (keyof Partial)[] = [ + 'domain', + 'as_owner', + 'country', + 'direction', + 'path', + 'profile' +] + +const orderByKeys: (keyof Partial)[] = [ + 'domain', + 'as_owner', + 'country', + 'direction', + 'path', + 'started', + 'ended', + 'profile', +] + +interface LocalQueryResult extends QueryResult { + _chart: Observable | null; + _group: Observable> | null; + __profile?: LocalAppProfile; + __exitNode?: Pin; +} + +interface QuickDateSetting { + name: string; + apply: () => [Date, Date]; +} + +/** + * Netquery Viewer + * + * This component is the actual viewer component for the netquery subsystem of the Portmaster. + * It allows the user to specify connection filters in multiple different ways and allows + * to do a deep-dive into all connections seen by the Portmaster (that are still stored in + * the in-memory SQLite database of the netquery subsystem). + * + * The user is able to modify the filter query by either: + * - using the available drop-downs + * - using the searchbar which + * - supports typed searches for connection fields (i.e. country:AT domain:google.at) + * - free-text search across the list of supported "full-text" search fields (see freeTextSearchFields) + * - by shift-clicking any value that has a SfngAddToFilter directive + * - by removing values from the tag bar. + */ + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'sfng-netquery-viewer', + templateUrl: './netquery.component.html', + providers: [ + NetqueryHelper, + ], + styles: [ + ` + :host { + @apply flex flex-col gap-3 pr-3 min-h-full; + } + + .protip pre { + @apply inline-block text-xxs uppercase rounded-sm bg-gray-500 bg-opacity-25 font-mono border-gray-500 border px-0.5; + } + ` + ], + animations: [ + fadeInAnimation + ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +// eslint-disable-next-line @angular-eslint/component-class-suffix +export class SfngNetqueryViewer implements OnInit, OnDestroy, AfterViewInit { + /** @private Used to trigger a reload of the current filter */ + private search$ = new Subject(); + + /** @private The DestroyRef of the component, required for takeUntilDestroyed */ + private destroyRef = inject(DestroyRef); + + /** @private Used to trigger an update of all displayed values in the tag-bar. */ + private updateTagBar$ = new BehaviorSubject(undefined); + + /** @private Whether or not the next update on ActivatedRoute should be ignored */ + private skipNextRouteUpdate = false; + + /** @private Whether or not we should update the URL when performSearch() finishes */ + private skipUrlUpdate = false; + + /** @private The LOCALE_ID to format dates. */ + private localeId = inject(LOCALE_ID); + + private integration = inject(INTEGRATION_SERVICE); + + /** @private the date format for the nz-range-picker */ + dateFormat = getLocaleDateFormat(getLocaleId(this.localeId), FormatWidth.Medium) + + /** @private A list of quick-date settings for the nz-range-picker */ + quickDateSettings: QuickDateSetting[] = [ + { + name: 'Today', + apply: () => { + const now = new Date(); + return [ + new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0), + new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, -1), + ] + } + }, + { + name: 'Last 24 Hours', + apply: () => { + const now = new Date(); + return [ + new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours() - 24, now.getMinutes(), now.getSeconds()), + now + ] + } + }, + { + name: 'Last 7 Days', + apply: () => { + const now = new Date(); + return [ + new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7, now.getHours(), now.getMinutes(), now.getSeconds()), + now, + ] + } + }, + { + name: 'Last Month', + apply: () => { + const now = new Date(); + return [ + new Date(now.getFullYear(), now.getMonth() - 1, now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds()), + now, + ] + } + }, + ] + + applyQuickDateSetting(qds: QuickDateSetting) { + const [from, to] = qds.apply() + + const fromStr = formatDate(from, 'medium', this.localeId) + const toStr = formatDate(to, 'medium', this.localeId) + + this.onFieldsParsed({ + from: [fromStr], + to: [toStr] + }, true) + } + + /** @private - The paginator used for the result set */ + paginator!: DynamicItemsPaginator; + + /** @private - The total amount of connections without the filter applied */ + totalConnCount: number = 0; + + /** @private - The total amount of connections with the filter applied */ + totalResultCount: number = 0; + + /** The value of the free-text search */ + textSearch: string = ''; + + /** The date filter */ + dateFilter: Date[] = [] + + /** a list of allowed group-by keys */ + readonly allowedGroupBy = groupByKeys; + + /** a list of allowed order-by keys */ + readonly allowedOrderBy = orderByKeys; + + /** @private Whether or not we are currently loading data */ + loading = false; + + /** @private The connection chart data */ + connectionChartData: ChartResult[] = []; + + /** @private The bandwidth chart data */ + bwChartData: BandwidthChartResult[] = []; + + /** @private The configuration for the bandwidth chart */ + readonly bwChartConfig = DefaultBandwidthChartConfig; + + /** @private The list of "pro-tips" that are defined in the template. Only one pro-tip will be rendered depending on proTipIdx */ + @ViewChildren('proTip', { read: TemplateRef }) + proTips!: QueryList> + + /** @private The index of the pro-tip that is currently rendered. */ + proTipIdx = 0; + + /** @private The last time the connections were loaded */ + lastReload: Date = new Date(); + + /** @private Used to refresh the "Last reload xxx ago" message */ + lastReloadTicker = interval(2000) + .pipe( + takeUntilDestroyed(this.destroyRef), + map(() => Math.floor((new Date()).getTime() - this.lastReload.getTime()) / 1000), + share() + ) + + // whether or not the history database should be queried as well. + get useHistory() { + return this.dateFilter?.length; + } + + private get databases(): Database[] { + if (!this.useHistory) { + return [Database.Live]; + } + + return [Database.Live, Database.History]; + } + + // whether or not the current use has the history feature available. + canUseHistory$ = inject(SPNService).profile$ + .pipe( + map(profile => { + if (!profile) { + return false; + } + + return profile.current_plan?.feature_ids?.includes(FeatureID.History) || false; + }) + ); + + featureBw$ = inject(SPNService).profile$ + .pipe( + map(profile => { + if (!profile) { + return false; + } + + return profile.current_plan?.feature_ids?.includes(FeatureID.Bandwidth) || false; + }) + ); + + trackPageItem: TrackByFunction = (_, r) => { + if (this.groupByKeys?.length) { + return this.groupByKeys.map(key => r[key]).join('-') + } + return r.id! + } + + trackConnection: TrackByFunction = (_, c) => c.id + + constructor( + private netquery: Netquery, + private helper: NetqueryHelper, + private expertise: ExpertiseService, + private cdr: ChangeDetectorRef, + private actionIndicator: ActionIndicatorService, + private route: ActivatedRoute, + public router: Router, + ) { } + + @Input() + set filters(v: any | keyof this['models'] | (keyof this['models'])[]) { + v = coerceArray(v); + objKeys(this.models).forEach(key => { + // ignore any models that are marked as being shown in the combined-menu. + if (this.models[key]?.visible !== 'combinedMenu') { + this.models[key]!.visible = false; + } + }) + + v.forEach((val: any) => { + if (typeof val !== 'string') { + throw new Error("invalid value for @Input() filters") + } + + if (!this.isValidFilter(val)) { + throw new Error('invalid filter key ' + val) + } + + this.models[val]!.visible = true; + }) + } + + /** + * mergeFilter input can be used to apply an additional filter condition that cannot be modified by + * the user (like forcing a "profile" filter for the App View) + */ + @Input() + mergeFilter: Condition | null = null; + + /** The filter preset that will be used if no filter is configured otherwise */ + @Input() + filterPreset: string | null = null; + + @Output() + filterChange: EventEmitter = new EventEmitter(); + + /** @private Holds the value displayed in the tag-bar */ + tagbarValues: SfngTagbarValue[] = []; + + private updateDateRangeState() { + const values = [ + this.models.from.searchValues[0], + this.models.to.searchValues[0], + ] + + let fromValueTs = Date.parse(values[0]) + let toValueTs = Date.parse(values[1]) + + // if we failed to parse the date from a string, the user might + // just entered the timestamp in seconds + if (isNaN(fromValueTs)) { + fromValueTs = Number(values[0]) * 1000 + } + if (isNaN(toValueTs)) { + toValueTs = Number(values[1]) * 1000 + } + + const fromValid = !isNaN(fromValueTs) + const toValid = !isNaN(toValueTs) + + + let fromValue = new Date(fromValueTs) + let toValue = new Date(toValueTs); + + if (fromValid && toValid && fromValue.getTime() === toValue.getTime()) { + fromValue = new Date(fromValue.getFullYear(), fromValue.getMonth(), fromValue.getDate(), 0, 0, 0) + toValue = new Date(toValue.getFullYear(), toValue.getMonth(), toValue.getDate() + 1, 0, 0, -1) + } + + this.dateFilter = []; + + if (fromValid) { + this.dateFilter.push(fromValue) + this.models.from.searchValues = [ + formatDate(fromValue, 'medium', this.localeId) + ] + } + + if (toValid) { + if (!fromValid) { + this.dateFilter.push(new Date(2000, 0, 1)) + } + + this.dateFilter.push(toValue) + this.models.to.searchValues = [ + formatDate(toValue, 'medium', this.localeId) + ] + } + + this.cdr.markForCheck(); + } + + private getDateRangeCondition(): Condition | null { + this.updateDateRangeState() + + if (!this.dateFilter.length) { + return null + } + + const cond: GreaterOrEqual & Partial = { + $ge: Math.floor(this.dateFilter[0].getTime() / 1000), + } + + if (this.dateFilter.length >= 2) { + cond['$le'] = Math.floor(this.dateFilter[1].getTime() / 1000) + } + + return { + started: cond + } + } + + models: { [key: string]: Model } = initializeModels({ + domain: { + visible: true, + }, + as_owner: { + visible: true, + }, + country: { + visible: true, + }, + profile: { + visible: true + }, + allowed: { + visible: true, + }, + path: {}, + internal: {}, + type: {}, + encrypted: {}, + scope: { + visible: 'combinedMenu', + menuTitle: 'Network Scope', + suggestions: objKeys(IPScopeNames) + .sort() + .filter(key => key !== IPScope.Undefined) + .map(scope => { + return { + Name: IPScopeNames[scope], + Value: scope, + count: 0, + Description: '' + } + }) + }, + verdict: {}, + started: {}, + ended: {}, + profile_revision: {}, + remote_ip: {}, + remote_port: {}, + local_ip: {}, + local_port: {}, + ip_protocol: {}, + direction: { + visible: 'combinedMenu', + menuTitle: 'Direction', + suggestions: [ + { + Name: 'Inbound', + Value: 'inbound', + Description: '', + count: 0, + }, + { + Name: 'Outbound', + Value: 'outbound', + Description: '', + count: 0, + } + ] + }, + exit_node: {}, + asn: {}, + active: { + visible: 'combinedMenu', + menuTitle: 'Active', + suggestions: booleanSuggestionValues(), + }, + tunneled: { + visible: 'combinedMenu', + menuTitle: 'SPN', + suggestions: booleanSuggestionValues(), + tipupKey: 'spn' + }, + from: { + virtual: true + }, + to: { + virtual: true, + }, + }) + + /** Translations for the connection field names */ + keyTranslation = connectionFieldTranslation; + + /** A list of keys for group-by */ + groupByKeys: string[] = []; + + /** A list of keys for sorting */ + orderByKeys: string[] = []; + + ngOnInit(): void { + // Prepare the datasource that is used to initialize the DynamicItemPaginator. + // It basically has a "view" function that executes the current page query + // but with page-number and page-size applied. + // This is used by the paginator to support lazy-loading the different + // result pages. + const dataSource: Datasource = { + view: (page: number, pageSize: number) => { + const query = this.getQuery(); + query.page = page - 1; // UI starts at page 1 while the backend is 0-based + query.pageSize = pageSize; + + return this.netquery.query(query, 'netquery-viewer') + .pipe( + this.helper.attachProfile(), + this.helper.attachPins(), + map(results => { + return (results || []).map(r => { + const grpFilter: Condition = { + ...query.query, + }; + this.groupByKeys.forEach(key => { + grpFilter[key] = r[key]; + }) + + let page = { + ...r, + _chart: !!this.groupByKeys.length ? this.getGroupChart(grpFilter) : null, + _group: !!this.groupByKeys.length ? this.lazyLoadGroup(grpFilter) : null, + } + + return page; + }); + }) + ); + } + } + + // create a new paginator that will use the datasource from above. + this.paginator = new DynamicItemsPaginator(dataSource) + + // subscribe to the search observable that emits a value each time we want to perform + // a new query. + // The actual searching is debounced by second so we don't flood the Portmaster service + // with queries while the user is still configuring their filters. + this.search$ + .pipe( + debounceTime(1000), + switchMap(() => { + this.loading = true; + this.connectionChartData = []; + this.bwChartData = []; + + this.cdr.detectChanges(); + + const query = this.getQuery(); + + // we only load the overall connection chart, the total connection count for the filter result + // as well the the total connection count without any filters here. The actual results are + // loaded by the DynamicItemsPaginator using the "view" function defined above. + return forkJoin({ + query: of(query), + response: this.netquery.batch({ + totalCount: { + ...query, + select: { $count: { field: '*', as: 'totalCount' } }, + }, + + totalConnCount: { + ...query, + select: { + $count: { field: '*', as: 'totalConnCount' } + }, + } + }) + .pipe( + map(response => { + // the the correct resulsts here which depend on whether or not + // we're applying a group by. + let totalCount = 0; + if (this.groupByKeys.length === 0) { + totalCount = response.totalCount[0].totalCount; + } else { + totalCount = response.totalCount.length; + } + + return { + totalCount, + totalConnCount: response.totalConnCount, + } + }) + ), + }) + }), + ) + .subscribe(result => { + this.paginator.pageLoading$ + .pipe( + skip(1), + takeUntil(this.search$), // skip loading the chart if the user trigger a subsequent search + filter(loading => !loading), + take(1), + switchMap(() => forkJoin({ + connectionChart: this.netquery.activeConnectionChart(result.query.query!) + .pipe( + catchError(err => { + this.actionIndicator.error( + 'Internal Error', + 'Failed to load chart: ' + this.actionIndicator.getErrorMessgae(err) + ); + + return of([] as ChartResult[]); + }), + ), + bwChart: this.netquery.bandwidthChart(result.query.query!, [], 60) + })), + ) + .subscribe(chart => { + this.connectionChartData = chart.connectionChart; + this.bwChartData = chart.bwChart; + + this.cdr.markForCheck(); + }) + + // reset the paginator with the new total result count and + // open the first page. + this.paginator.reset(result.response.totalCount); + this.totalConnCount = result.response.totalConnCount[0].totalConnCount; + this.totalResultCount = result.response.totalCount; + + // update the current URL to include the new search + // query and make sure we skip the parameter-update emitted by + // router. + if (!this.skipUrlUpdate) { + this.skipNextRouteUpdate = true; + + const queryText = this.getQueryString(); + + this.filterChange.next(queryText); + + // note that since we only update the query parameters and stay on + // the current route this component will not get re-created but will + // rather receive an update on the queryParamMap (see below). + this.router.navigate([], { + relativeTo: this.route, + queryParams: { + ...this.route.snapshot.queryParams, + q: queryText, + }, + }) + } + this.skipUrlUpdate = false; + + this.loading = false; + this.cdr.markForCheck(); + }) + + // subscribe to router updates so we apply the filter that is part of + // the current query parameter "q". + // We might ignore updates here depending on the value of "skipNextRouterUpdate". + // This is required as we keep the route parameters in sync with the current filter. + this.route.queryParamMap + .pipe( + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(params => { + if (this.skipNextRouteUpdate) { + this.skipNextRouteUpdate = false; + return; + } + + const query = params.get("q") + + if (query !== null) { + objKeys(this.models).forEach(key => { + this.models[key]!.searchValues = []; + }) + + const result = Parser.parse(query!) + + this.onFieldsParsed({ + ...result.conditions, + groupBy: result.groupBy, + orderBy: result.orderBy, + }); + this.textSearch = result.textQuery; + } + + this.skipUrlUpdate = true; + this.performSearch(); + }) + + // we might get new search values from our helper service + // in case the user "SHIFT-Clicks" a SfngAddToFilter directive. + this.helper.onFieldsAdded() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(fields => this.onFieldsParsed(fields)) + + // updateTagBar$ always emits a value when we need to update the current tag-bar values. + // This must always be done if the current search filter has been modified in either of + // the supported ways. + this.updateTagBar$ + .pipe( + takeUntilDestroyed(this.destroyRef), + switchMap(() => { + const obs: Observable<{ [key: string]: (PossilbeValue & QueryResult)[] }>[] = []; + + // for the tag bar we try to show some pretty names for values that are meant to be + // internal (like the number-constants for the verdicts or using the profile name instead + // of the profile ID). Since we might need to load data from the Portmaster for this (like + // for profile names) we construct a list of observables using helper.encodeToPossibleValues + // and use the result for the tagbar. + Object.keys(this.models) + .sort() // make sure we always output values in a constant order + .forEach(modelKey => { + const values = this.models[modelKey]!.searchValues; + + if (values.length > 0) { + obs.push( + of(values.map(val => ({ + [modelKey]: val, + }))) + .pipe( + this.helper.encodeToPossibleValues(modelKey), + map(result => ({ + [modelKey]: result, + })) + ) + ) + } + }) + + if (obs.length === 0) { + return of([]); + } + + return combineLatest(obs); + }) + ) + .subscribe(tagBarValues => { + this.tagbarValues = []; + + // reset the "selected" field of each model that is shown in the "combinedMenu". + // we'll set the correct ones as "selected" again in the next step. + objKeys(this.models).forEach(key => { + if (this.models[key]?.visible === 'combinedMenu') { + this.models[key]?.suggestions.forEach(val => val.selected = false); + } + }) + + // finally construct a new list of tag-bar values and update the "selected" field of + // suggested-values for the "combinedMenu" items based on the actual search values. + tagBarValues.forEach(obj => { + objKeys(obj).forEach(key => { + if (obj[key].length > 0) { + this.tagbarValues.push({ + key: key as string, + values: obj[key], + }) + + // update the `selected` field of suggested-values for each model that is displayed in the combined-menu + const modelsKey = key as keyof NetqueryConnection; + if (this.models[modelsKey]?.visible === 'combinedMenu') + this.models[modelsKey]?.suggestions.forEach(suggestedValue => { + suggestedValue.selected = obj[key].some(val => val.Value === suggestedValue.Value); + }) + } + }) + }) + + this.cdr.markForCheck(); + }) + + // handle any filter preset + // + if (!!this.filterPreset) { + try { + const result = Parser.parse(this.filterPreset); + this.onFieldsParsed({ + ...result.conditions, + groupBy: result.groupBy, + orderBy: result.orderBy, + }); + } catch (err) { + // only log the error in dev mode as this is most likely + // just bad user input + if (isDevMode()) { + console.error(err); + } + } + } + } + + ngAfterViewInit(): void { + // once we are initialized decide which pro-tip we want to show this time... + this.proTipIdx = Math.floor(Math.random() * this.proTips.length); + } + + ngOnDestroy() { + this.paginator.clear(); + this.search$.complete(); + this.helper.dispose(); + } + + // lazyLoadGroup returns an observable that will emit a DynamicItemsPaginator once subscribed. + // This is used in "group-by" views to lazy-load the content of the group once the user + // expands it. + lazyLoadGroup(groupFilter: Condition): Observable> { + return new Observable(observer => { + this.netquery.query({ + query: groupFilter, + select: [ + { $count: { field: "*", as: "totalCount" } } + ], + orderBy: [ + { field: 'started', desc: true }, + { field: 'ended', desc: true } + ], + databases: this.databases, + }, 'netquery-viewer-load-group') + .subscribe(result => { + const paginator = new DynamicItemsPaginator({ + view: (pageNumber: number, pageSize: number) => { + return this.netquery.query({ + query: groupFilter, + orderBy: [ + { field: 'started', desc: true }, + { field: 'ended', desc: true } + ], + page: pageNumber - 1, + pageSize: pageSize, + databases: this.databases, + }, 'netquery-viewer-group-paginator') as Observable; + } + }, 25) + + paginator.reset(result[0]?.totalCount || 0) + + observer.next(paginator) + }) + }) + } + + // Returns an observable that loads the current active connection chart using the + // current page query but only for the condition of the displayed group. + getGroupChart(groupFilter: Condition): Observable { + return this.netquery.activeConnectionChart(groupFilter) + } + + // loadSuggestion loads possible values for a given connection field + // and updates the "suggestions" field of the correct models entry. + // It also uses helper.encodeToPossibleValues to make sure we show + // pretty names for otherwise "internal" values like verdict constants + // or profile IDs. + loadSuggestion(field: string): void; + loadSuggestion(field: T) { + const search = this.getQuery([field]); + + this.models[field]!.loading = !this.models[field]!.suggestions?.length; + + this.netquery.query({ + select: [ + field, + { + $count: { + field: "*", + as: "count" + }, + } + ], + query: search.query, + groupBy: [ + field, + ], + orderBy: [{ field: "count", desc: true }, { field, desc: true }], + databases: this.databases, + }, 'netquery-viewer-load-suggestions') + .pipe(this.helper.encodeToPossibleValues(field)) + .subscribe(result => { + this.models[field]!.loading = false; + + // create a set that we can use to lookup if a value + // is currently selected. + // This is needed to ensure selected values are sorted to the top. + let currentlySelected = new Set(); + this.models[field]!.searchValues.forEach( + val => currentlySelected.add(val) + ); + + this.models[field]!.suggestions = + result + .sort((a, b) => { + const hasA = currentlySelected.has(a.Value); + const hasB = currentlySelected.has(b.Value); + + if (hasA && !hasB) { + return -1; + } + if (hasB && !hasA) { + return 1; + } + + return b.count - a.count; + }) as any; + + this.cdr.markForCheck(); + }) + } + + sortByCount(a: SelectOption, b: SelectOption) { + return b.data - a.data + } + + /** @private Callback for keyboard events on the search-input */ + onFieldsParsed(fields: SfngSearchbarFields, replace = false) { + const allowedKeys = new Set(Object.keys(this.models)) + + objKeys(fields).forEach(key => { + if (key === 'groupBy') { + this.groupByKeys = (fields.groupBy || this.groupByKeys) + .filter(val => { + // an empty value is just filtered out without an error as this is the only + // way to specify "I don't want grouping" via the filter + if (val === '') { + return false; + } + + if (!allowedKeys.has(val as any)) { + this.actionIndicator.error("Invalid search query", "Column " + val + " is not allowed for groupby") + return false; + } + return true; + }) + + return; + } + + if (key === 'orderBy') { + this.orderByKeys = (fields.orderBy || this.orderByKeys) + .filter(val => { + if (!allowedKeys.has(val as any)) { + this.actionIndicator.error("Invalid search query", "Column " + val + " is not allowed for orderby") + return false; + } + return true; + }) + + return; + } + + if (!allowedKeys.has(key)) { + this.actionIndicator.error("Invalid search query", "Column " + key + " is not allowed for filtering"); + return; + } + + if (fields[key]?.length === 0 && replace) { + this.models[key].searchValues = []; + } else { + fields[key]!.forEach(val => { + // quick fix to make sure domains always end in a period. + if (key === 'domain' && typeof val === 'string' && val.length > 0 && !val.endsWith('.')) { + val = `${val}.` + } + + if (typeof val === 'object' && '$ne' in val) { + this.actionIndicator.error("NOT conditions are not yet supported") + return; + } + + // avoid duplicates + if (this.models[key]!.searchValues.includes(val)) { + return; + } + + if (!replace) { + this.models[key]!.searchValues = [ + ...this.models[key]!.searchValues, + val, + ] + } else { + this.models[key]!.searchValues = [val] + } + }) + } + + this.updateDateRangeState() + }) + + this.cdr.markForCheck(); + + this.performSearch(); + } + + /** @private Query the portmaster service for connections matching the current settings */ + performSearch() { + this.loading = true; + this.lastReload = new Date(); + this.paginator.clear() + this.search$.next(); + this.updateTagbarValues(); + } + + /** @private Returns the current query in it's string representation */ + getQueryString(): string { + let result = ''; + + objKeys(this.models).forEach(key => { + this.models[key]?.searchValues.forEach(val => { + // we use JSON.stringify here to make sure the value is + // correclty quoted. + result += `${key}:${JSON.stringify(val)} `; + }) + }) + + if (result.length > 0 && this.textSearch.length > 0) { + result += ' ' + } + + this.groupByKeys.forEach(key => { + result += `groupby:"${key}" ` + }) + this.orderByKeys.forEach(key => { + result += `orderby:"${key}" ` + }) + + if (result.length > 0 && this.textSearch.length > 0) { + result += ' ' + } + + result += `${this.textSearch}` + + return result; + } + + /** @private Copies the current query into the user clipboard */ + copyQuery() { + this.integration.writeToClipboard(this.getQueryString()) + .then(() => { + this.actionIndicator.success("Query copied to clipboard", 'Go ahead and share your query!') + }) + .catch((err) => { + this.actionIndicator.error('Failed to copy to clipboard', this.actionIndicator.getErrorMessgae(err)) + }) + } + + /** @private Clears the current query */ + clearQuery() { + objKeys(this.models).forEach(key => { + this.models[key]!.searchValues = []; + }) + this.textSearch = ''; + + this.updateTagbarValues(); + this.performSearch(); + } + + /** @private Constructs a query from the current page settings. Supports excluding certain fields from the query. */ + getQuery(excludeFields: string[] = []): Query { + let query: Condition = {} + let textSearch: Query['textSearch']; + + const dateQuery = this.getDateRangeCondition() + if (dateQuery !== null) { + query = mergeConditions(query, dateQuery) + } + + // create the query conditions for all keys on this.models + Object.keys(this.models).forEach((key: string) => { + if (excludeFields.includes(key)) { + return; + } + + if (this.models[key]!.searchValues.length > 0) { + // check if model is virtual, and if, skip adding it to the query + if (this.models[key].virtual) { + return + } + + query[key] = { + $in: this.models[key]!.searchValues, + } + } + }) + + if (this.expertise.currentLevel !== 'developer') { + query["internal"] = { + $eq: false, + } + } + + if (this.textSearch !== '') { + textSearch = { + fields: freeTextSearchFields, + value: this.textSearch + } + } + + let select: Query['select'] | undefined = undefined; + if (!!this.groupByKeys.length) { + // we always want to show the total and the number of allowed connections + // per group so we need to add those to the select part of the query + select = [ + { + $count: { + field: "*", + as: "totalCount", + }, + }, + { + $sum: { + condition: { + verdict: { + $in: [ + Verdict.Accept, + Verdict.RerouteToNs, + Verdict.RerouteToTunnel + ], + } + }, + as: "countAllowed" + } + }, + ...this.groupByKeys, + ] + } + + let normalizedQuery = mergeConditions(query, this.mergeFilter || {}) + + let orderBy: string[] | OrderBy[] = this.orderByKeys; + if (!orderBy || orderBy.length === 0) { + orderBy = [ + { + field: 'started', + desc: true, + }, + { + field: 'ended', + desc: true, + } + ] + } + + return { + select: select, + query: normalizedQuery, + groupBy: this.groupByKeys, + orderBy: orderBy, + textSearch, + databases: this.databases, + } + } + + /** @private Updates the current model form all values emited by the tag-bar. */ + onTagbarChange(tagKinds: SfngTagbarValue[]) { + objKeys(this.models).forEach(key => { + this.models[key]!.searchValues = []; + }); + + tagKinds.forEach(kind => { + const key = kind.key as keyof NetqueryConnection; + this.models[key]!.searchValues = kind.values.map(possibleValue => possibleValue.Value); + + if (this.models[key]?.visible === 'combinedMenu') + this.models[key]?.suggestions.forEach(val => { + val.selected = this.models[key]!.searchValues.find(searchValue => searchValue === val.Value) + }) + }) + + this.updateDateRangeState(); + + this.performSearch(); + } + + onDateRangeChange(event: Date[]) { + if (event.length >= 1) { + event[0] = new Date(event[0].getFullYear(), event[0].getMonth(), event[0].getDate(), 0, 0, 0) + this.onFieldsParsed({ from: [formatDate(event[0], 'medium', this.localeId)] }, true) + } else { + this.onFieldsParsed({ from: [] }, true) + } + + if (event.length >= 2) { + event[1] = new Date(event[1].getFullYear(), event[1].getMonth(), event[1].getDate() + 1, 0, 0, -1) + this.onFieldsParsed({ to: [formatDate(event[1], 'medium', this.localeId)] }, true) + } else { + this.onFieldsParsed({ to: [] }, true) + } + } + + /** Updates the {@link tagbarValues} from {@link models}*/ + private updateTagbarValues() { + this.updateTagBar$.next(); + } + + private isValidFilter(key: string): key is keyof NetqueryConnection { + return Object.keys(this.models).includes(key); + } + + useAsFilter(rec: QueryResult) { + const keys = new Set(objKeys(this.models)) + + // reset the search values + keys.forEach(key => { + this.models[key]!.searchValues = []; + }) + + objKeys(rec).forEach(key => { + if (keys.has(key as keyof NetqueryConnection)) { + this.models[key as keyof NetqueryConnection]!.searchValues = [rec[key]]; + } + }) + + // reset the group-by-keys since they don't make any sense anymore. + this.groupByKeys = []; + this.performSearch(); + } + + /** @private - used by the combined filter menu */ + toggleCombinedMenuFilter(key: string, value: Suggestion) { + const k = key as keyof NetqueryConnection; + if (value.selected) { + this.models[k]!.searchValues = this.models[k]?.searchValues.filter(val => val !== value.Value) || []; + } else { + this.models[k]!.searchValues.push(value.Value) + } + + this.updateTagbarValues(); + this.performSearch(); + } + + trackSuggestion: TrackByFunction = (_: number, s: Suggestion) => s.Name + '::' + s.Value; +} + +function initializeModels(models: { [key: string]: Partial> }): { [key: string]: Model } { + objKeys(models).forEach(key => { + models[key] = { + suggestions: [], + searchValues: [], + visible: false, + loading: false, + ...models[key], + } + }) + + return models as any; +} + +function booleanSuggestionValues(): Suggestion[] { + return [ + { + Name: 'Yes', + Value: true, + Description: '', + count: 0, + }, + { + Name: 'No', + Value: false, + Description: '', + count: 0, + }, + ] +} diff --git a/desktop/angular/src/app/shared/netquery/netquery.module.ts b/desktop/angular/src/app/shared/netquery/netquery.module.ts new file mode 100644 index 00000000..5a433666 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/netquery.module.ts @@ -0,0 +1,88 @@ +import { A11yModule } from "@angular/cdk/a11y"; +import { OverlayModule } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; +import { inject, NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; +import { SfngAccordionModule, SfngDropDownModule, SfngPaginationModule, SfngSelectModule, SfngTipUpModule, SfngToggleSwitchModule, SfngTooltipModule } from "@safing/ui"; +import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'; +import { SfngAppIconModule } from "../app-icon"; +import { CountIndicatorModule } from "../count-indicator"; +import { CountryFlagModule } from "../country-flag"; +import { ExpertiseModule } from "../expertise/expertise.module"; +import { SfngFocusModule } from "../focus"; +import { SfngMenuModule } from "../menu"; +import { CommonPipesModule } from "../pipes"; +import { SPNModule } from './../../pages/spn/spn.module'; +import { SfngNetqueryAddToFilterDirective } from "./add-to-filter"; +import { CombinedMenuPipe } from "./combined-menu.pipe"; +import { SfngNetqueryConnectionDetailsComponent } from "./connection-details"; +import { SfngNetqueryConnectionRowComponent } from "./connection-row"; +import { SfngNetqueryLineChartComponent } from "./line-chart/line-chart"; +import { SfngNetqueryViewer } from "./netquery.component"; +import { CanShowConnection, CanUseRulesPipe, ConnectionLocationPipe, CountryNamePipe, CountryNameService, IsBlockedConnectionPipe } from "./pipes"; +import { SfngNetqueryScopeLabelComponent } from "./scope-label"; +import { SfngNetquerySearchOverlayComponent } from "./search-overlay"; +import { SfngNetquerySearchbarComponent, SfngNetquerySuggestionDirective } from "./searchbar"; +import { SfngNetqueryTagbarComponent } from "./tag-bar"; +import { CircularBarChartComponent } from './circular-bar-chart/circular-bar-chart.component'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + CountryFlagModule, + SfngDropDownModule, + SfngSelectModule, + SfngTooltipModule, + SfngAccordionModule, + SfngMenuModule, + SfngPaginationModule, + SfngFocusModule, + SfngAppIconModule, + SfngTipUpModule, + SfngToggleSwitchModule, + A11yModule, + ExpertiseModule, + OverlayModule, + CountIndicatorModule, + FontAwesomeModule, + CommonPipesModule, + SPNModule, + NzDatePickerModule, + ], + exports: [ + SfngNetqueryViewer, + SfngNetqueryLineChartComponent, + SfngNetquerySearchOverlayComponent, + SfngNetqueryScopeLabelComponent, + CircularBarChartComponent, + ], + declarations: [ + SfngNetqueryViewer, + SfngNetqueryConnectionRowComponent, + SfngNetqueryLineChartComponent, + SfngNetqueryTagbarComponent, + SfngNetquerySearchbarComponent, + SfngNetquerySearchOverlayComponent, + SfngNetquerySuggestionDirective, + SfngNetqueryScopeLabelComponent, + SfngNetqueryConnectionDetailsComponent, + SfngNetqueryAddToFilterDirective, + ConnectionLocationPipe, + IsBlockedConnectionPipe, + CanUseRulesPipe, + CanShowConnection, + CombinedMenuPipe, + CircularBarChartComponent, + CountryNamePipe, + ], + providers: [ + CountryNameService + ] +}) +export class NetqueryModule { + private _unusedBootstrap = [ + inject(CountryNameService), // make sure country names are loaded on bootstrap + ] +} diff --git a/desktop/angular/src/app/shared/netquery/pipes/can-show.pipe.ts b/desktop/angular/src/app/shared/netquery/pipes/can-show.pipe.ts new file mode 100644 index 00000000..35f93628 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/pipes/can-show.pipe.ts @@ -0,0 +1,22 @@ +import { Pipe, PipeTransform } from "@angular/core"; +import { ExpertiseLevel, NetqueryConnection } from "@safing/portmaster-api"; + + +@Pipe({ + name: "canShowConnection", + pure: true, +}) +export class CanShowConnection implements PipeTransform { + transform(conn: NetqueryConnection, level: ExpertiseLevel) { + if (!conn) { + return false; + } + if (level === ExpertiseLevel.Developer) { + // we show all connections for developers + return true; + } + // if we are in advanced or simple mode we should + // hide internal connections. + return !conn.internal; + } +} diff --git a/desktop/angular/src/app/shared/netquery/pipes/can-use-rules.pipe.ts b/desktop/angular/src/app/shared/netquery/pipes/can-use-rules.pipe.ts new file mode 100644 index 00000000..d4b5d4d3 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/pipes/can-use-rules.pipe.ts @@ -0,0 +1,32 @@ + +// the following settings are stronger than rules +// and cannot be "fixed" by creating a new allow/deny + +import { Pipe, PipeTransform } from "@angular/core"; +import { IsDenied, NetqueryConnection } from "@safing/portmaster-api"; + +// rule. +let optionKeys = new Set([ + "filter/blockInternet", + "filter/blockLAN", + "filter/blockLocal", + "filter/blockP2P", + "filter/blockInbound" +]) + +@Pipe({ + name: "canUseRules", + pure: true, +}) +export class CanUseRulesPipe implements PipeTransform { + transform(conn: NetqueryConnection): boolean { + if (!conn) { + return false; + } + if (!!conn.extra_data?.reason?.OptionKey && IsDenied(conn.verdict)) { + return !optionKeys.has(conn.extra_data.reason.OptionKey); + } + return true; + } +} + diff --git a/desktop/angular/src/app/shared/netquery/pipes/country-name.pipe.ts b/desktop/angular/src/app/shared/netquery/pipes/country-name.pipe.ts new file mode 100644 index 00000000..93e6bc61 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/pipes/country-name.pipe.ts @@ -0,0 +1,59 @@ +import { HttpClient } from '@angular/common/http'; +import { Pipe, PipeTransform, Injectable, inject } from '@angular/core'; +import { GeoCoordinates, SPNService } from '@safing/portmaster-api'; +import { environment } from 'src/environments/environment'; +import { ActionIndicatorService } from '../../action-indicator'; +import { objKeys } from '../../utils'; + +export interface CountryListResponse { + [countryKey: string]: { + Code: string; + Name: string; + Center: GeoCoordinates; + Continent: { + Code: string; + Region: string; + Name: string; + } + } +} + +@Injectable() +export class CountryNameService { + private readonly spn = inject(SPNService); + private readonly http = inject(HttpClient); + private readonly uai = inject(ActionIndicatorService); + + private map: Map = new Map(); + + constructor() { + this.http.get(`${environment.httpAPI}/v1/intel/geoip/countries`) + .subscribe({ + next: response => { + objKeys(response) + .forEach(key => { + this.map.set(key as string, response[key].Name); + }); + }, + error: err => { + this.uai.error('Failed to fetch country data', this.uai.getErrorMessage(err)); + } + }) + } + + resolveName(code: string): string { + return this.map.get(code) || ''; + } +} + +@Pipe({ + name: 'countryName', + pure: true, +}) +export class CountryNamePipe implements PipeTransform { + private countryService = inject(CountryNameService); + + transform(countryCode: string) { + return this.countryService.resolveName(countryCode); + } +} diff --git a/desktop/angular/src/app/shared/netquery/pipes/index.ts b/desktop/angular/src/app/shared/netquery/pipes/index.ts new file mode 100644 index 00000000..9b429e59 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/pipes/index.ts @@ -0,0 +1,5 @@ +export * from './location.pipe'; +export * from './can-show.pipe'; +export * from './can-use-rules.pipe'; +export * from './is-blocked.pipe'; +export * from './country-name.pipe'; diff --git a/desktop/angular/src/app/shared/netquery/pipes/is-blocked.pipe.ts b/desktop/angular/src/app/shared/netquery/pipes/is-blocked.pipe.ts new file mode 100644 index 00000000..fcb6dc0d --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/pipes/is-blocked.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IsDenied, NetqueryConnection } from '@safing/portmaster-api'; + +@Pipe({ + name: "isBlocked", + pure: true +}) +export class IsBlockedConnectionPipe implements PipeTransform { + transform(conn: NetqueryConnection): boolean { + return IsDenied(conn?.verdict); + } +} diff --git a/desktop/angular/src/app/shared/netquery/pipes/location.pipe.ts b/desktop/angular/src/app/shared/netquery/pipes/location.pipe.ts new file mode 100644 index 00000000..522ed86a --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/pipes/location.pipe.ts @@ -0,0 +1,33 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IsGlobalScope, IsLANScope, IsLocalhost, NetqueryConnection } from '@safing/portmaster-api'; + +@Pipe({ + name: 'connectionLocation', + pure: true, +}) +export class ConnectionLocationPipe implements PipeTransform { + transform(conn: NetqueryConnection): string { + if (conn.type === 'dns') { + return ''; + } + if (!!conn.country) { + return conn.country; + } + + const scope = conn.scope; + + if (IsGlobalScope(scope)) { + return 'Internet' + } + + if (IsLANScope(scope)) { + return 'LAN'; + } + + if (IsLocalhost(scope)) { + return 'Device' + } + + return ''; + } +} diff --git a/desktop/angular/src/app/shared/netquery/scope-label/index.ts b/desktop/angular/src/app/shared/netquery/scope-label/index.ts new file mode 100644 index 00000000..8f481940 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/scope-label/index.ts @@ -0,0 +1 @@ +export * from './scope-label'; diff --git a/desktop/angular/src/app/shared/netquery/scope-label/scope-label.html b/desktop/angular/src/app/shared/netquery/scope-label/scope-label.html new file mode 100644 index 00000000..0df11b57 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/scope-label/scope-label.html @@ -0,0 +1,8 @@ + + {{subdomain}}. + {{domain}} + + + {{ scopeTranslation[scope || ''] || 'N/A' }} + diff --git a/desktop/angular/src/app/shared/netquery/scope-label/scope-label.ts b/desktop/angular/src/app/shared/netquery/scope-label/scope-label.ts new file mode 100644 index 00000000..8bb64c83 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/scope-label/scope-label.ts @@ -0,0 +1,34 @@ +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { ScopeTranslation } from '@safing/portmaster-api'; +import { parseDomain } from '../../utils'; + +@Component({ + selector: 'sfng-netquery-scope-label', + templateUrl: 'scope-label.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SfngNetqueryScopeLabelComponent implements OnChanges { + readonly scopeTranslation = ScopeTranslation; + + @Input() + scope?: string = '' + + @Input() + set leftRightFix(v: any) { + console.warn("deprecated @Input usage") + } + get leftRightFix() { return false } + + domain: string = ''; + subdomain: string = ''; + + ngOnChanges(change: SimpleChanges) { + if (!!change['scope']) { + //this.label = change.label.currentValue; + const result = parseDomain(change.scope.currentValue || '') + + this.domain = result?.domain || ''; + this.subdomain = result?.subdomain || ''; + } + } +} diff --git a/desktop/angular/src/app/shared/netquery/search-overlay/index.ts b/desktop/angular/src/app/shared/netquery/search-overlay/index.ts new file mode 100644 index 00000000..ffad6a32 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/search-overlay/index.ts @@ -0,0 +1 @@ +export * from './search-overlay'; diff --git a/desktop/angular/src/app/shared/netquery/search-overlay/search-overlay.html b/desktop/angular/src/app/shared/netquery/search-overlay/search-overlay.html new file mode 100644 index 00000000..49eaa84b --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/search-overlay/search-overlay.html @@ -0,0 +1,2 @@ + diff --git a/desktop/angular/src/app/shared/netquery/search-overlay/search-overlay.ts b/desktop/angular/src/app/shared/netquery/search-overlay/search-overlay.ts new file mode 100644 index 00000000..eaae1a08 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/search-overlay/search-overlay.ts @@ -0,0 +1,81 @@ +import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; +import { Router } from "@angular/router"; +import { SfngDialogRef, SFNG_DIALOG_REF } from "@safing/ui"; +import { objKeys } from "../../utils"; +import { NetqueryHelper } from "../connection-helper.service"; +import { SfngSearchbarFields } from "../searchbar"; +import { connectionFieldTranslation } from "../utils"; + +@Component({ + selector: 'sfng-netquery-search-overlay', + templateUrl: './search-overlay.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + NetqueryHelper, + ], + styles: [ + ` + :host { + @apply block; + width: 700px; + } + + ::ng-deep sfng-netquery-search-overlay sfng-netquery-searchbar input { + border: 1px solid theme("colors.gray.200") !important; + } + ` + ] +}) +export class SfngNetquerySearchOverlayComponent { + keyTranslation = connectionFieldTranslation; + + textSearch = ''; + + fields: SfngSearchbarFields = {}; + + constructor( + @Inject(SFNG_DIALOG_REF) private dialogRef: SfngDialogRef, + private router: Router, + ) { } + + performSearch() { + let query = ""; + const fields = objKeys(this.fields) + + // if there's only one profile key directly navigate the user to the app view + if (fields.length === 1 && fields[0] === 'profile' && this.fields.profile!.length === 1) { + let profileName: string = this.fields.profile![0] || ''; + if (!profileName.includes("/")) { + profileName = "local/" + profileName + } + this.router.navigate(['/app/' + profileName || '']) + this.dialogRef.close(); + return; + } + + fields.forEach(field => { + this.fields[field]?.forEach(value => { + query += `${field}:${JSON.stringify(value)} ` + }) + }) + + if (query !== '' && this.textSearch !== '') { + query += " " + } + query += this.textSearch; + + this.router.navigate(['/monitor'], { + queryParams: { + q: query, + } + }) + + this.dialogRef.close(); + } + + onFieldsParsed(fields: SfngSearchbarFields) { + objKeys(fields).forEach(field => { + this.fields[field] = [...(this.fields[field] || []), ...(fields[field] || [])]; + }) + } +} diff --git a/desktop/angular/src/app/shared/netquery/searchbar/index.ts b/desktop/angular/src/app/shared/netquery/searchbar/index.ts new file mode 100644 index 00000000..2520d4d6 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/searchbar/index.ts @@ -0,0 +1 @@ +export * from './searchbar'; diff --git a/desktop/angular/src/app/shared/netquery/searchbar/searchbar.html b/desktop/angular/src/app/shared/netquery/searchbar/searchbar.html new file mode 100644 index 00000000..ef2dedf4 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/searchbar/searchbar.html @@ -0,0 +1,76 @@ +
+
+ + + +
+ +
+ + + +
+ +
+
+ + +
    +
  • + Full-Text Search: {{ textSearch }} +
  • +
+ +
+ + +
+

+ Filter by {{ labels[sug.field] || sug.field }} +

+
    +
  • + {{ val.display || (val.value === '' ? 'N/A' : val.value) }} + #{{ val.count }} connections +
  • +
+
+
+ +
+ + + + + + Loading suggestions ... +
+ + + + There are no suggestions for your query. Press +
Enter
+ to + perform a full text search. +
+
+
+
diff --git a/desktop/angular/src/app/shared/netquery/searchbar/searchbar.ts b/desktop/angular/src/app/shared/netquery/searchbar/searchbar.ts new file mode 100644 index 00000000..46a42f51 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/searchbar/searchbar.ts @@ -0,0 +1,437 @@ +import { ListKeyManager } from "@angular/cdk/a11y"; +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { CdkOverlayOrigin } from "@angular/cdk/overlay"; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Directive, ElementRef, EventEmitter, forwardRef, HostBinding, HostListener, inject, Input, OnDestroy, OnInit, Output, QueryList, TrackByFunction, ViewChild, ViewChildren } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { Condition, ExpertiseLevel, Netquery, NetqueryConnection } from "@safing/portmaster-api"; +import { SfngDropdownComponent } from "@safing/ui"; +import { combineLatest, Observable, of, Subject } from "rxjs"; +import { catchError, debounceTime, map, switchMap } from "rxjs/operators"; +import { fadeInAnimation, fadeInListAnimation } from "../../animations"; +import { ExpertiseService } from "../../expertise"; +import { objKeys } from "../../utils"; +import { NetqueryHelper } from "../connection-helper.service"; +import { Parser, ParseResult } from "../textql"; + +export type SfngSearchbarFields = { + [key in keyof Partial]: any[]; +} & { + groupBy?: string[]; + orderBy?: string[]; + from?: string[]; + to?: string[]; +} + +export type SfngSearchbarSuggestionValue = { + value: NetqueryConnection[K]; + display: string; + count: number; +} + +export type SfngSearchbarSuggestion = { + start?: number; + field: K | '_textsearch'; + values: SfngSearchbarSuggestionValue[]; +} + +@Directive({ + selector: '[sfngNetquerySuggestion]', + exportAs: 'sfngNetquerySuggestion' +}) +export class SfngNetquerySuggestionDirective { + constructor() { } + + @Input() + sfngSuggestion?: SfngSearchbarSuggestion; + + @Input() + sfngNetquerySuggestion?: SfngSearchbarSuggestionValue | string; + + set active(v: any) { + this._active = coerceBooleanProperty(v); + } + get active() { + return this._active; + } + private _active: boolean = false; + + getLabel(): string { + if (typeof this.sfngNetquerySuggestion === 'string') { + return this.sfngNetquerySuggestion; + } + return '' + this.sfngNetquerySuggestion?.value; + } +} + +@Component({ + selector: 'sfng-netquery-searchbar', + templateUrl: './searchbar.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInAnimation, + fadeInListAnimation + ], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SfngNetquerySearchbarComponent), + multi: true, + } + ] +}) +export class SfngNetquerySearchbarComponent implements ControlValueAccessor, OnInit, OnDestroy, AfterViewInit { + private loadSuggestions$ = new Subject(); + private triggerDropdownClose$ = new Subject(); + private keyManager!: ListKeyManager>; + private destroyRef = inject(DestroyRef); + + /** Whether or not we are currently loading suggestions */ + loading = false; + + @ViewChild(CdkOverlayOrigin, { static: true }) + searchBoxOverlayOrigin!: CdkOverlayOrigin; + + @ViewChild(SfngDropdownComponent) + suggestionDropDown?: SfngDropdownComponent; + + @ViewChild('searchBar', { static: true, read: ElementRef }) + searchBar!: ElementRef; + + @ViewChildren(SfngNetquerySuggestionDirective) + suggestionValues!: QueryList>; + + @Output() + fieldsParsed = new EventEmitter(); + + @Input() + labels: { [key: string]: string } = {} + + /** Controls whether or not suggestions are shown as a drop-down or inline */ + @Input() + mode: 'inline' | 'default' = 'default'; + + suggestions: SfngSearchbarSuggestion[] = []; + + textSearch = ''; + + @HostListener('focus') + onFocus() { + // move focus forward to the input element + this.searchBar.nativeElement.focus(); + } + + @Input() + @HostBinding('tabindex') + tabindex = 0; + + writeValue(val: string): void { + if (typeof val === 'string') { + const result = Parser.parse(val); + this.textSearch = result.textQuery; + } else { + this.textSearch = ''; + } + this.cdr.markForCheck(); + } + + _onChange: (val: string) => void = () => { } + registerOnChange(fn: any): void { + this._onChange = fn; + } + + _onTouched: () => void = () => { } + registerOnTouched(fn: any): void { + this._onTouched = fn + } + + ngAfterViewInit(): void { + this.keyManager = new ListKeyManager(this.suggestionValues) + .withVerticalOrientation() + .withTypeAhead() + .withHomeAndEnd() + .withWrap(); + + this.keyManager.change + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(idx => { + if (!this.suggestionValues.length) { + return + } + + this.suggestionValues.forEach(val => val.active = false); + this.suggestionValues.get(idx)!.active = true; + this.cdr.markForCheck(); + }); + } + + ngOnInit(): void { + this.loadSuggestions$ + .pipe( + debounceTime(500), + switchMap(() => { + let fields: (keyof NetqueryConnection)[] = [ + 'profile', + 'domain', + 'as_owner', + 'remote_ip', + ]; + let limit = 3; + + const parser = new Parser(this.textSearch); + const parseResult = parser.process(); + + const queries: Observable>[] = []; + const queryKeys: (keyof Partial)[] = []; + + // FIXME(ppacher): confirm .type is an actually allowed field + if (!!parser.lastUnterminatedCondition) { + fields = [parser.lastUnterminatedCondition.type as keyof NetqueryConnection]; + limit = 0; + } + + fields.forEach(field => { + let queryField = field; + + // if we are searching the profiles we use the profile name + // rather than the profile_id for searching. + if (field === 'profile') { + queryField = 'profile_name'; + } + + const query: Condition = { + [queryField]: { + $like: `%${!!parser.lastUnterminatedCondition ? parser.lastUnterminatedCondition.value : parseResult.textQuery}%` + }, + } + + // hide internal connections if the user is not a developer + if (this.expertiseService.currentLevel !== ExpertiseLevel.Developer) { + query.internal = { + $eq: false + } + } + + const obs = this.netquery.query({ + select: [ + field, + { + $count: { + field: "*", + as: "count" + }, + } + ], + query: query, + groupBy: [ + field, + ], + page: 0, + pageSize: limit, + orderBy: [{ field: "count", desc: true }] + }, 'netquery-searchbar-get-counts') + .pipe( + this.helper.encodeToPossibleValues(field), + map(results => { + let val: SfngSearchbarSuggestion = { + field: field, + values: [], + start: parser.lastUnterminatedCondition ? parser.lastUnterminatedCondition.start : undefined, + } + + results.forEach(res => { + val.values.push({ + value: res.Value, + display: res.Name, + count: res.count, + }) + }) + + return val; + }), + catchError(err => { + console.error(err); + + return of({ + field: field, + values: [], + }) + }) + ) + + queries.push(obs) + queryKeys.push(field) + }) + + return combineLatest(queries) + }), + ) + .subscribe(result => { + this.loading = false; + + this.suggestions = result + .filter((sug: SfngSearchbarSuggestion) => sug.values?.length > 0) + + this.keyManager.setActiveItem(0); + + this.cdr.markForCheck(); + }) + + this.triggerDropdownClose$ + .pipe(debounceTime(100)) + .subscribe(shouldClose => { + if (shouldClose) { + this.suggestionDropDown?.close(); + } + }) + + if (this.mode === 'inline') { + this.loadSuggestions(); + } + } + + ngOnDestroy(): void { + this.loadSuggestions$.complete(); + this.triggerDropdownClose$.complete(); + } + + cancelDropdownClose() { + this.triggerDropdownClose$.next(false); + } + + onSearchModelChange(value: string) { + if (value.length >= 3 || this.mode === 'inline') { + this.loadSuggestions(); + } else if (this.suggestionDropDown?.isOpen) { + // close the suggestion dropdown if the search input contains less than + // 3 characters and we're currently showing the dropdown + this.suggestionDropDown?.close(); + } + } + + /** @private Callback for keyboard events on the search-input */ + onSearchKeyDown(event: KeyboardEvent) { + if (event.key === ' ' && event.ctrlKey) { + this.loadSuggestions(); + event.preventDefault(); + event.stopPropagation() + return; + } + + if (event.key === 'Enter') { + + const selectedSuggestion = this.suggestionValues.toArray().findIndex(val => val.active); + if (selectedSuggestion > 0) { // we must skip 0 here as well as that's the dummy element + const sug = this.suggestionValues.get(selectedSuggestion); + this.applySuggestion(sug?.sfngSuggestion?.field, sug?.sfngNetquerySuggestion, event, sug?.sfngSuggestion?.start) + + return; + } + + this.suggestionDropDown?.close(); + this.parseAndEmit(); + this.cdr.markForCheck(); + + return; + } + + this.keyManager.onKeydown(event); + } + + onFocusLost(event: FocusEvent) { + this._onTouched(); + } + + private parseAndEmit() { + const result = Parser.parse(this.textSearch); + this.textSearch = result.textQuery; + + const keys = objKeys(result.conditions) + const meta = { + groupBy: result.groupBy || undefined, + orderBy: result.orderBy || undefined, + } + if (keys.length > 0 || meta.groupBy?.length || meta.orderBy?.length) { + let updatedConditions: ParseResult['conditions'] = {}; + keys.forEach(key => { + updatedConditions[key] = this.helper.decodePrettyValues(key as keyof NetqueryConnection, result.conditions[key]) + }) + this.fieldsParsed.next({ ...updatedConditions, ...meta }); + } + + this._onChange(this.textSearch); + } + + applySuggestion(field: keyof NetqueryConnection | '_textsearch', val: any, event: { shiftKey: boolean }, start?: number) { + // this is a full-text search so just emit the value, close the dropdown and we're done + if (field === '_textsearch') { + this._onChange(this.textSearch); + this.suggestionDropDown?.close(); + + return + } + + if (start !== undefined) { + this.textSearch = this.textSearch.slice(0, start) + } else if (!event.shiftKey) { + this.textSearch = ''; + } else { + // the user pressed shift-key and used free-text search so we remove + // the remaining part + const parseRes = Parser.parse(this.textSearch); + let query = ""; + objKeys(parseRes.conditions).forEach(field => { + parseRes.conditions[field]?.forEach(value => { + query += `${field}:${JSON.stringify(value)} ` + }) + }) + this.textSearch = query; + } + + if (event.shiftKey) { + const textqlVal = `${field}:${JSON.stringify(val)}` + if (!this.textSearch.includes(textqlVal)) { + if (this.textSearch !== '') { + this.textSearch += " " + } + this.textSearch += textqlVal + " " + this.triggerDropdownClose$.next(false) + // load new suggestions based on the new input + this.loadSuggestions(); + } + + return; + } + + // directly emit the new value and reset the text search + this.fieldsParsed.next({ + [field]: [val] + }) + + // parse and emit the current search field but without the suggestion value + this.parseAndEmit(); + + this.suggestionDropDown?.close(); + + this.cdr.markForCheck(); + } + + resetKeyboardSelection() { + this.keyManager.setActiveItem(0); + } + + loadSuggestions() { + this.loading = true; + this.loadSuggestions$.next(); + this.suggestionDropDown?.show(this.searchBoxOverlayOrigin) + } + + trackSuggestion: TrackByFunction> = (_: number, val: SfngSearchbarSuggestion) => val.field; + + constructor( + private cdr: ChangeDetectorRef, + private expertiseService: ExpertiseService, + private netquery: Netquery, + private helper: NetqueryHelper, + ) { } +} diff --git a/desktop/angular/src/app/shared/netquery/tag-bar/index.ts b/desktop/angular/src/app/shared/netquery/tag-bar/index.ts new file mode 100644 index 00000000..3439acb3 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/tag-bar/index.ts @@ -0,0 +1 @@ +export * from './tag-bar'; diff --git a/desktop/angular/src/app/shared/netquery/tag-bar/tag-bar.html b/desktop/angular/src/app/shared/netquery/tag-bar/tag-bar.html new file mode 100644 index 00000000..f1161e58 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/tag-bar/tag-bar.html @@ -0,0 +1,26 @@ +
+ +
+ + + {{labels[cat.key] || cat.key}}: + + + + + {{ val.Name || (val.Value === '' ? 'N/A' : val) }} + + + + + + + + +
+
+
diff --git a/desktop/angular/src/app/shared/netquery/tag-bar/tag-bar.ts b/desktop/angular/src/app/shared/netquery/tag-bar/tag-bar.ts new file mode 100644 index 00000000..bbff7417 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/tag-bar/tag-bar.ts @@ -0,0 +1,136 @@ +import { coerceBooleanProperty, coerceCssPixelValue } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, HostBinding, Input } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { PossilbeValue } from '@safing/portmaster-api'; +import { fadeInListAnimation } from '../../animations'; +import { NetqueryHelper } from '../connection-helper.service'; + +export interface SfngTagbarValue { + key: string; + values: PossilbeValue[]; +} + +@Component({ + selector: 'sfng-netquery-tagbar', + templateUrl: 'tag-bar.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styles: [ + ` + :host { + @apply flex flex-row gap-3 w-auto items-center text-xxs flex-wrap; + } + ` + ], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SfngNetqueryTagbarComponent), + multi: true + } + ], + animations: [ + fadeInListAnimation + ] +}) +export class SfngNetqueryTagbarComponent implements ControlValueAccessor { + @HostBinding('@fadeInList') + get itemsLength() { + return this.values?.length || 0; + } + + /** @private the current tag bar values */ + values: SfngTagbarValue[] = []; + + /** Whether or not the user can interact with the component */ + @Input() + set disabled(v: any) { + this.setDisabledState(v) + } + get disabled() { + return this._disabled; + } + private _disabled = false; + + /** Translations for the value keys */ + @Input() + labels: { [key: string]: string } = {} + + /** The maximum width of the tag text before being truncated using left-side ellipsis */ + @Input() + set maxTagWidth(width: any) { + this._maxTagWidth = coerceCssPixelValue(width) + } + get maxTagWidth() { + return this._maxTagWidth + } + private _maxTagWidth: string = '8rem' + + /** @private A {@link TrackByFunction} for {@link SfngTagbarValue} */ + trackValue(_: number, vl: SfngTagbarValue) { + return vl.key; + } + + /** Implements the {@link ControlValueAccessor} */ + writeValue(obj: SfngTagbarValue[]): void { + this.values = obj; + this.cdr.markForCheck(); + } + + /** Implements the {@link ControlValueAccessor} */ + registerOnChange(fn: any): void { + this._onChange = fn; + } + + /** @private - callback registered via registerOnChange */ + _onChange: (val: SfngTagbarValue[]) => void = () => { } + + /** Implements the {@link ControlValueAccessor} */ + registerOnTouched(fn: any): void { + this._onTouched = fn + } + + /** @private - callback registered via registerOnTouched */ + _onTouched: () => void = () => { } + + /** Implements the {@link ControlValueAccessor} */ + setDisabledState(v: any) { + this._disabled = coerceBooleanProperty(v) + this.cdr.markForCheck(); + } + + /** + * remove removes the value at index from the {@link SfngTagbarValue} + * that matches key. + */ + remove(key: string, index: number) { + if (this.disabled) { + return; + } + + console.log(this.values); + + let cpy: SfngTagbarValue[] = []; + + this.values.forEach(val => { + if (val.key === key) { + val.values = [...val.values]; + val.values.splice(index, 1) + } + cpy.push({ + ...val, + }) + }); + + this.values = cpy; + + console.log(this.values); + + this._onChange(this.values); + this.cdr.markForCheck(); + } + + constructor( + private cdr: ChangeDetectorRef, + private helper: NetqueryHelper, + ) { } +} diff --git a/desktop/angular/src/app/shared/netquery/textql/helper.ts b/desktop/angular/src/app/shared/netquery/textql/helper.ts new file mode 100644 index 00000000..8f523aaa --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/textql/helper.ts @@ -0,0 +1,21 @@ +import { Token, TokenType } from "./token"; + +export function isValueToken(tok: Token): tok is Token { + return [TokenType.STRING, TokenType.BOOL, TokenType.NUMBER].includes(tok.type) +} + +export function isDigit(x: string): boolean { + return /[0-9]+/.test(x); +} + +export function isWhitespace(ch: string): boolean { + return /\s/.test(ch) +} + +export function isLetter(ch: string): boolean { + return new RegExp('[\/a-zA-Z0-9\._-]').test(ch) +} + +export function isIdentChar(ch: string): boolean { + return /[a-zA-Z_]/.test(ch); +} diff --git a/desktop/angular/src/app/shared/netquery/textql/index.ts b/desktop/angular/src/app/shared/netquery/textql/index.ts new file mode 100644 index 00000000..75aa8b93 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/textql/index.ts @@ -0,0 +1 @@ +export * from './parser'; diff --git a/desktop/angular/src/app/shared/netquery/textql/input.ts b/desktop/angular/src/app/shared/netquery/textql/input.ts new file mode 100644 index 00000000..4180d193 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/textql/input.ts @@ -0,0 +1,41 @@ +/** Input stream returns one character at a time */ +export class InputStream { + private _pos: number = 0; + private _line: number = 0; + + constructor(private _input: string) { } + + /** Returns the next character and removes it from the stream */ + next(): string | null { + const ch = this._input.charAt(this._pos++); + return ch; + } + + get pos() { + return this._pos; + } + + /** Revert moves the current stream position back by `num` characters */ + revert(num: number) { + this._pos -= num; + } + + /** Returns the next character in the stream but does not remove it */ + peek(): string { + return this._input.charAt(this._pos); + } + + /** Returns true if we reached the end of the stream */ + eof(): boolean { + return this.peek() == ''; + } + + get left(): string { + return this._input.slice(this._pos) + } + + /** Throws an error with the current line and column */ + croak(msg: string): never { + throw new Error(`${msg} at ${this._line}:${this.pos}`); + } +} diff --git a/desktop/angular/src/app/shared/netquery/textql/lexer.ts b/desktop/angular/src/app/shared/netquery/textql/lexer.ts new file mode 100644 index 00000000..008cbd6e --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/textql/lexer.ts @@ -0,0 +1,255 @@ +import { isDigit, isIdentChar, isLetter, isWhitespace } from "./helper"; +import { InputStream } from "./input"; +import { Token, TokenType } from "./token"; + +export class Lexer { + private _current: Token | null = null; + private _input: InputStream; + + constructor(input: string) { + this._input = new InputStream(input); + } + + /** peek returns the token at the current position in input. */ + public peek(): Token | null { + return this._current || (this._current = this.readNextToken()); + } + + /** next returns either the current token in input or reads the next one */ + public next(): Token | null { + let tok = this._current; + this._current = null; + return tok || this.readNextToken(); + } + + /** eof returns true if the lexer reached the end of the input stream */ + public eof(): boolean { + return this.peek() === null; + } + + /** croak throws and error message at the current position in the input stream */ + public croak(msg: string): never { + return this._input.croak(`${msg}. Current token is "${!!this.peek() ? this.peek()!.literal : null}"`); + } + + /** consumes the input stream as long as predicate returns true */ + private readWhile(predicate: (ch: string) => boolean): string { + let str = ''; + while (!this._input.eof() && predicate(this._input.peek())) { + str += this._input.next(); + } + + return str; + } + + /** reads a number token */ + private readNumber(): Token { + const start = this._input.pos; + + let has_dot = false; + let number = this.readWhile((ch: string) => { + if (ch === '.') { + if (has_dot) { + return false; + } + + has_dot = true; + return true; + } + return isDigit(ch); + }); + + if (!this._input.eof() && isIdentChar(this._input.peek())) { + this._input.revert(number.length + 1); + this._input.croak("invalid number character") + } + + return { + type: TokenType.NUMBER, + literal: number, + value: has_dot ? parseFloat(number) : parseInt(number), + start + } + } + + private readIdent(): Token { + const start = this._input.pos; + + const id = this.readWhile(ch => isIdentChar(ch)); + if (id === 'true' || id === 'yes') { + return { + type: TokenType.BOOL, + literal: id, + value: true, + start + } + } + if (id === 'false' || id === 'no') { + return { + type: TokenType.BOOL, + literal: id, + value: false, + start + } + } + if (id === 'groupby') { + return { + type: TokenType.GROUPBY, + literal: id, + value: id, + start + } + } + if (id === 'orderby') { + return { + type: TokenType.ORDERBY, + literal: id, + value: id, + start + } + } + + return { + type: TokenType.IDENT, + literal: id, + value: id, + start + }; + } + + private readEscaped(end: string | RegExp, skipStart: boolean): string { + let escaped = false; + let str = ''; + + if (skipStart) { + this._input.next(); + } + + while (!this._input.eof()) { + let ch = this._input.next()!; + if (escaped) { + str += ch; + escaped = false; + } else if (ch === '\\') { + escaped = true; + } else if ((typeof end === 'string' && ch === end) || (end instanceof RegExp && end.test(ch))) { + break; + } else { + str += ch; + } + } + return str; + } + + private readString(quote: string | RegExp, skipStart: boolean): Token { + const start = this._input.pos; + const value = this.readEscaped(quote, skipStart) + return { + type: TokenType.STRING, + literal: value, + value: value, + start + } + } + + private readWhitespace(): Token { + const start = this._input.pos; + const value = this.readWhile(ch => isWhitespace(ch)); + return { + type: TokenType.WHITESPACE, + literal: value, + value: value, + start, + } + } + + private readNextToken(): Token | null { + const start = this._input.pos; + const ch = this._input.peek(); + if (ch === '') { + return null; + } + + if (isWhitespace(ch)) { + return this.readWhitespace() + } + + if (ch === '"') { + return this.readString('"', true); + } + + if (ch === '\'') { + return this.readString('\'', true); + } + + try { + if (isDigit(ch)) { + return this.readNumber(); + } + } catch (err) { + // we ignore that error here as it may only happen for unqoted strings + // that start with a number. + } + + if (ch === ':') { + this._input.next(); + return { + type: TokenType.COLON, + value: ':', + literal: ':', + start + } + } + + if (ch === '!') { + this._input.next(); + return { + type: TokenType.NOT, + value: '!', + literal: '!', + start + } + } + + if (isIdentChar(ch)) { + const ident = this.readIdent(); + + const next = this._input.peek(); + if (!this._input.eof() && (!isWhitespace(next) && next !== ':')) { + + // identifiers should always end in a colon or with a whitespace. + // if neither is the case we are in the middle of a token and are + // likely parsing a string without quotes. + this._input.revert(ident.literal.length); + + // read the string and revert by one as we terminate the string + // at the next WHITESPACE token + const tok = this.readString(new RegExp('\\s'), false) + this.revertWhitespace(); + + return tok; + } + + return ident; + } + + if (isLetter(ch)) { + const tok = this.readString(new RegExp('\\s'), false) + // read the string and revert by one as we terminate the string + // at the next WHITESPACE token + this.revertWhitespace(); + + return tok + } + + // Failed to handle the input character + return this._input.croak(`Can't handle character: ${ch}`); + } + + private revertWhitespace() { + this._input.revert(1) + if (!isWhitespace(this._input.peek())) { + this._input.next(); + } + } +} diff --git a/desktop/angular/src/app/shared/netquery/textql/parser.ts b/desktop/angular/src/app/shared/netquery/textql/parser.ts new file mode 100644 index 00000000..5cf492f7 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/textql/parser.ts @@ -0,0 +1,204 @@ +import { isDevMode } from '@angular/core'; +import { isValueToken, isWhitespace } from './helper'; +import { Lexer } from './lexer'; +import { Token, TokenType } from './token'; + + +export interface ParseResult { + conditions: { + [key: string]: (any | { $ne: any })[]; + }; + textQuery: string; + groupBy?: string[]; + orderBy?: string[]; +} + +export class Parser { + /** The underlying lexer used to tokenize the input */ + private lexer: Lexer; + + /** Holds the parsed conditions */ + private conditions: { + [key: string]: any[]; + } = {}; + + /** The last condition that has not yet been terminated. Used for scope-based suggestions */ + private _lastUnterminatedCondition: { + start: number; + type: string; + value: any; + } | null = null; + + /** A list of remaining strings/identifiers that are not part of a condition */ + private remaining: string[] = []; + + /** Returns the last condition that has not yet been terminated. */ + get lastUnterminatedCondition() { + return this._lastUnterminatedCondition; + } + + constructor(input: string) { + this.lexer = new Lexer(input); + } + + static aliases: { [key: string]: string } = { + 'provider': 'as_owner', + 'app': 'profile', + 'ip': 'remote_ip', + 'port': 'remote_port' + } + + /** parse is a shortcut for new Parser(input).process() */ + static parse(input: string): ParseResult { + return new Parser(input).process(); + } + + /** Process the whole input stream and return the parsed result */ + process(): ParseResult { + let lastIdent: Token | null = null; + let hasColon = false; + let not = false; + let groupBy: string[] = []; + let orderBy: string[] = []; + + while (true) { + const tok = this.lexer.next() + if (tok === null) { + break; + } + + if (isDevMode()) { + console.log(tok) + } + + // if we find a whitespace token we count it as a termination character + // for the last unterminated condition. + if (tok.type === TokenType.WHITESPACE) { + this._lastUnterminatedCondition = null; + } + + // Since we allow the user to enter values without quotes the + // lexer might wrongly declare a "string value" as an IDENT. + // If we have the pattern we re-classify + // the last IDENT as a STRING value + if (!!lastIdent && hasColon && tok.type === TokenType.IDENT) { + tok.type = TokenType.STRING; + } + + if (tok.type === TokenType.IDENT || tok.type === TokenType.GROUPBY || tok.type === TokenType.ORDERBY) { + // if we had an IDENT token before and got a new one now the + // previous one is pushed to the remaining list + if (!!lastIdent) { + this._lastUnterminatedCondition = null; + this.remaining.push(lastIdent.value) + } + lastIdent = tok; + this._lastUnterminatedCondition = { + start: tok.start, + type: Parser.aliases[lastIdent.value] || lastIdent.value, + value: '', + } + + continue + } + + // if we don't have an preceding IDENT token + // this must be part of remaingin + if (!lastIdent) { + this.remaining.push(tok.literal); + this._lastUnterminatedCondition = null; + + continue + } + + // we would expect a colon now + if (!hasColon) { + if (tok.type !== TokenType.COLON) { + // we expected a colon but got something else. + // this means the last IDENT is part of remaining + this.remaining.push(lastIdent.value); + lastIdent = null; + this._lastUnterminatedCondition = null; + + continue + } + + // we have a colon now so proceed to the next token + hasColon = true; + not = false; + + continue + } + + if (lastIdent.type === TokenType.GROUPBY) { + groupBy.push(Parser.aliases[tok.literal] || tok.literal) + lastIdent = null + hasColon = false + + continue + } + + if (lastIdent.type == TokenType.ORDERBY) { + orderBy.push(Parser.aliases[tok.literal] || tok.literal) + lastIdent = null + hasColon = false + + continue + } + + if (tok.type === TokenType.NOT && not === false) { + not = true + + continue + } + + if (isValueToken(tok)) { + let identValue = Parser.aliases[lastIdent.value] || lastIdent.value; + + if (!this.conditions[identValue]) { + this.conditions[identValue] = []; + } + + if (!not) { + this.conditions[identValue].push(tok.value) + } else { + this.conditions[identValue].push({ $ne: tok.value }) + } + this._lastUnterminatedCondition!.value = tok.value; + + lastIdent = null + hasColon = false + not = false + + continue + } + + this.remaining.push(lastIdent.value); + lastIdent = null; + hasColon = false; + not = false; + this._lastUnterminatedCondition = null; + } + + if (!!lastIdent) { + this.remaining.push(lastIdent.value); + + if (hasColon) { + this._lastUnterminatedCondition = { + start: lastIdent.start, + type: Parser.aliases[lastIdent.value] || lastIdent.value, + value: '' + }; + } else { + this._lastUnterminatedCondition = null; + } + } + + return { + groupBy: groupBy.length > 0 ? groupBy : undefined, + orderBy: orderBy.length > 0 ? orderBy : undefined, + conditions: this.conditions, + textQuery: this.remaining.filter(tok => !isWhitespace(tok)).join(" "), + } + } +} diff --git a/desktop/angular/src/app/shared/netquery/textql/token.ts b/desktop/angular/src/app/shared/netquery/textql/token.ts new file mode 100644 index 00000000..ae9039d7 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/textql/token.ts @@ -0,0 +1,46 @@ + +/** + * Language Definition: + * + * input: + * + * [EXPR] [EXPR]... + * + * with: + * + * EXPR = [IDENT][COLON][NOT?][VALUE] + * NOT = "!" + * VALUE = [STRING][BOOL][NUMBER] + * STRING = [a-zA-Z\.0-9] + * BOOL = true | false + * NUMBER = [0-9]+ + * COLON = ":" + * + */ + +export enum TokenType { + WHITESPACE = 'WHITESPACE', + IDENT = 'IDENT', + COLON = 'COLON', + STRING = 'STRING', + NUMBER = 'NUMBER', + BOOL = 'BOOL', + NOT = 'NOT', + GROUPBY = 'GROUPBY', + ORDERBY = 'ORDERBY' +} + +export type TokenValue = + T extends TokenType.NUMBER ? number : + T extends TokenType.STRING ? string : + T extends TokenType.BOOL ? boolean : + T extends TokenType.NOT ? '!' : + T extends TokenType.GROUPBY ? 'string' : + string; + +export interface Token { + type: T; + literal: string; + value: TokenValue; + start: number; +} diff --git a/desktop/angular/src/app/shared/netquery/utils.ts b/desktop/angular/src/app/shared/netquery/utils.ts new file mode 100644 index 00000000..95f15034 --- /dev/null +++ b/desktop/angular/src/app/shared/netquery/utils.ts @@ -0,0 +1,63 @@ +import { Condition, Matcher } from "@safing/portmaster-api"; +import { objKeys } from "../utils"; + +export const connectionFieldTranslation: { [key: string]: string } = { + domain: "Domain", + profile: "App", + path: 'Binary Path', + scope: 'Scope', + as_owner: "Provider", + country: "Country", + direction: 'Direction', + started: 'Started', + ended: 'Ended', + remote_ip: 'Remote IP', + verdict: 'Verdict', + encrypted: 'Encrypted', + internal: 'Internal', + asn: 'ASN', + tunneled: 'SPN Active', + active: 'Active', + allowed: 'Allowed', + from: 'From', + to: 'To', + remote_port: 'Port', + bytes_sent: 'Bytes Sent', + bytes_received: 'Bytes Received' +} + +export function isMatcher(v: any | Matcher): v is Matcher { + return typeof v === 'object' && ('$eq' in v || '$ne' in v || '$like' in v || '$in' in v || '$notin' in v); +} + +export function mergeConditions(cond1: Condition, cond2: Condition): Condition { + const result: Condition = {}; + + objKeys(cond1).forEach(key => { + let val = cond1[key]; + if (Array.isArray(val)) { + result[key] = val; + } else { + result[key] = [val]; + } + }) + + objKeys(cond2).forEach(key => { + let val = cond2[key]; + if (!Array.isArray(val)) { + val = [val] + } + + if (!(key in result)) { + result[key] = val; + } else { + result[key] = [ + ...(result[key] as any), // this must be an array here + ...val, + ] + } + }) + + + return result; +} diff --git a/desktop/angular/src/app/shared/network-scout/index.ts b/desktop/angular/src/app/shared/network-scout/index.ts new file mode 100644 index 00000000..fa8417ee --- /dev/null +++ b/desktop/angular/src/app/shared/network-scout/index.ts @@ -0,0 +1 @@ +export * from './network-scout'; diff --git a/desktop/angular/src/app/shared/network-scout/network-scout.html b/desktop/angular/src/app/shared/network-scout/network-scout.html new file mode 100644 index 00000000..b314d86b --- /dev/null +++ b/desktop/angular/src/app/shared/network-scout/network-scout.html @@ -0,0 +1,182 @@ +
+
+ + + + + + {{allProfiles.length}} Apps + +
+ + + + + + + + +
+

Sort By

+ + + + {{ sortMethod }} + + +
+
+ + + + + + + + + +
+ + + +
+ {{ profile.exitPins.length }} + IDENTITIES +
+ + + Connections from {{ profile.Name }} have not been routed through the SPN. + + +
    +
  • + + + + {{ entity.IP }} + + +
    + + {{ identity.count }} + Connections + + + HOPS: + {{ identity.HopDistance }} + +
    +
  • +
+ + + {{ profile.showMore ? 'Show Less Identities' : 'Show More Identities'}} + +
+
+ + +
+ + + + + + + + + {{ data.Name }} + + + + +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + +
+ +
+ + + +
+ + + + + {{ data.bytes_sent | bytes:"1.0-0" }} + + + + + + + {{ data.bytes_received | bytes:"1.0-0" }} + +
+
+
+
diff --git a/desktop/angular/src/app/shared/network-scout/network-scout.scss b/desktop/angular/src/app/shared/network-scout/network-scout.scss new file mode 100644 index 00000000..9f24cfef --- /dev/null +++ b/desktop/angular/src/app/shared/network-scout/network-scout.scss @@ -0,0 +1,3 @@ +:host { + @apply w-full p-2 flex flex-col gap-2; +} diff --git a/desktop/angular/src/app/shared/network-scout/network-scout.ts b/desktop/angular/src/app/shared/network-scout/network-scout.ts new file mode 100644 index 00000000..8c3c7d88 --- /dev/null +++ b/desktop/angular/src/app/shared/network-scout/network-scout.ts @@ -0,0 +1,322 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, OnInit, TrackByFunction, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BoolSetting, Condition, ConfigService, ExpertiseLevel, IProfileStats, Netquery, Pin, SPNService } from "@safing/portmaster-api"; +import { Subject, combineLatest, debounceTime, filter, finalize, interval, retry, startWith, switchMap, take, takeUntil } from "rxjs"; +import { UIStateService } from "src/app/services"; +import { fadeInListAnimation } from "../animations"; +import { ExpertiseService } from './../expertise/expertise.service'; + +interface _Pin extends Pin { + count: number; +} + +interface _Profile extends IProfileStats { + exitPins: _Pin[]; + showMore: boolean; + expanded: boolean; +} + +export enum SortTypes { + static = 'Static', + aToZ = "A-Z", + zToA = "Z-A", + totalConnections = "Total Connections", + connectionsDenied = "Denied Connections", + connectionsAllowed = "Allowed Connections", + spnIdentities = "SPN Identities", + bytesSent = "Bytes Sent", + bytesReceived = "Bytes Received", + totalBytes = "Total Bytes" +} + +const bandwidthSorts: SortTypes[] = [ + SortTypes.bytesReceived, + SortTypes.bytesSent, + SortTypes.totalBytes +] + +@Component({ + selector: 'app-network-scout', + templateUrl: './network-scout.html', + styleUrls: ['./network-scout.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInListAnimation, + ] +}) +export class NetworkScoutComponent implements OnInit { + private destroyRef = inject(DestroyRef); + + sortTypes = [ + SortTypes.static, + SortTypes.aToZ, + SortTypes.zToA, + SortTypes.totalConnections, + SortTypes.connectionsDenied, + SortTypes.connectionsAllowed, + SortTypes.spnIdentities + ] + + readonly sortMethods = new Map([ + // there's not entry for "Static" here on purpose because we'll use the sort order + // returned by netquery. + [SortTypes.aToZ, (a: _Profile, b: _Profile) => a.Name.localeCompare(b.Name)], + [SortTypes.zToA, (a: _Profile, b: _Profile) => b.Name.localeCompare(a.Name)], + [SortTypes.totalConnections, (a: _Profile, b: _Profile) => (b.countAllowed + b.countUnpermitted) - (a.countAllowed + a.countUnpermitted)], + [SortTypes.connectionsAllowed, (a: _Profile, b: _Profile) => b.countAllowed - a.countAllowed], + [SortTypes.connectionsDenied, (a: _Profile, b: _Profile) => b.countUnpermitted - a.countUnpermitted], + [SortTypes.spnIdentities, (a: _Profile, b: _Profile) => a.identities.length - b.identities.length], + [SortTypes.bytesReceived, (a: _Profile, b: _Profile) => b.bytes_received - a.bytes_received], + [SortTypes.bytesSent, (a: _Profile, b: _Profile) => b.bytes_sent - a.bytes_sent], + [SortTypes.totalBytes, (a: _Profile, b: _Profile) => (b.bytes_received + b.bytes_sent) - (a.bytes_received + a.bytes_sent)] + ]); + + /** The current sort order */ + sortOrder: SortTypes = SortTypes.static; + + get isByteSortOrder() { + return bandwidthSorts.includes(this.sortOrder); + } + + /** Used to trigger a debounced search from the template */ + triggerSearch = new Subject(); + + /** The current search term as entered in the input[type="text"] */ + searchTerm: string = ''; + + /** A list of all active profiles without any search applied */ + allProfiles: _Profile[] = []; + + /** Defines if new elements should be expanded or collapsed */ + expandCollapseState: 'expand' | 'collapse' = 'expand'; + + /** Whether or not the SPN is enabled */ + spnEnabled = false; + + /** + * Emits when the user clicks the "expand all" or "collapse all" buttons. + * Once the user did that we stop updating the default state depending on whether the + * SPN is enabled or not. + */ + private userChangedState = new Subject(); + + /** + * A list of profiles that are currently displayed. This is basically allProfiles but with + * text search applied. + */ + profiles: _Profile[] = []; + + /** TrackByFunction for the profiles. */ + trackProfile: TrackByFunction<_Profile> = (_, profile) => profile.ID; + + /** TrackByFunction for the exit pins */ + trackPin: TrackByFunction<_Pin> = (_, pin) => pin.ID; + + constructor( + private netquery: Netquery, + private spn: SPNService, + private configService: ConfigService, + private stateService: UIStateService, + private expertise: ExpertiseService, + private cdr: ChangeDetectorRef, + ) { } + + searchProfiles(term: string) { + term = term.trim(); + + if (term === '') { + this.profiles = [ + ...this.allProfiles + ]; + + this.sortProfiles(this.profiles); + + return; + } + + const lowerCaseTerm = term.toLocaleLowerCase() + this.profiles = this.allProfiles.filter(p => { + if (p.ID.toLocaleLowerCase().includes(lowerCaseTerm)) { + return true; + } + + if (p.Name.toLocaleLowerCase().includes(lowerCaseTerm)) { + return true; + } + + if (p.exitPins.some(pin => pin.Name.toLocaleLowerCase().includes(lowerCaseTerm))) { + return true; + } + + return false; + }) + + this.sortProfiles(this.profiles); + } + + sortProfiles(profiles: _Profile[]) { + const method = this.sortMethods.get(this.sortOrder); + if (!method) { + return; + } + + profiles.sort(method) + + this.cdr.markForCheck(); + } + + updateSortOrder(newOrder: SortTypes) { + this.sortOrder = newOrder; + this.searchProfiles(this.searchTerm); + + this.stateService.set('netscoutSortOrder', newOrder) + .subscribe({ + error: err => { + console.error(err); + } + }) + } + + expandAll() { + this.expandCollapseState = 'expand'; + this.allProfiles.forEach(profile => profile.expanded = profile.identities.length > 0) + this.searchProfiles(this.searchTerm) + this.userChangedState.next(); + + this.cdr.markForCheck() + } + + collapseAll() { + this.expandCollapseState = 'collapse'; + this.allProfiles.forEach(profile => profile.expanded = false) + this.searchProfiles(this.searchTerm) + this.userChangedState.next(); + + this.cdr.markForCheck() + } + + ngOnInit(): void { + this.stateService.uiState() + .pipe(take(1)) + .subscribe(state => { + this.sortOrder = state.netscoutSortOrder; + + this.searchProfiles(this.searchTerm); + }) + + this.configService.watch('spn/enable') + .pipe( + takeUntilDestroyed(this.destroyRef), + takeUntil(this.userChangedState), + ) + .subscribe(enabled => { + // if the SPN is enabled and the user did not yet change the + // collapse/expand state we switch to "expand" for the default. + // Otherwise, there will be no identities so there's no reason + // to expand them at all so we switch to collapse + if (enabled) { + this.expandCollapseState = 'expand' + } else { + this.expandCollapseState = 'collapse' + } + + this.spnEnabled = enabled; + }); + + let updateInProgress = false; + + combineLatest([ + combineLatest([ + interval(5000) + .pipe( + filter(() => !updateInProgress) + ), + this.expertise.change, + ]) + .pipe( + startWith(-1), + switchMap(() => { + let query: Condition = {}; + if (this.expertise.currentLevel !== ExpertiseLevel.Developer) { + query["internal"] = { $eq: false } + } + + updateInProgress = true + + return this.netquery.getProfileStats(query) + .pipe( + finalize(() => updateInProgress = false) + ) + }), + retry({ delay: 5000 }) + ), + + this.spn.watchPins() + .pipe( + debounceTime(100), + startWith([]), + ), + + this.triggerSearch + .pipe( + debounceTime(100), + startWith(''), + ), + ]) + .pipe( + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(([res, pins, searchTerm]) => { + // create a lookup map for the the SPN map pins + const pinLookupMap = new Map(); + pins.forEach(p => pinLookupMap.set(p.ID, p)) + + // create a lookup map from already known profiles so we can + // inherit states like "showMore". + const profileLookupMap = new Map(); + this.allProfiles.forEach(p => profileLookupMap.set(p.ID, p)) + + // map the list of profile statistics to include the exit Pin information + // as well. + this.allProfiles = res.map(s => { + const existing = profileLookupMap.get(s.ID); + return { + ...s, + exitPins: s.identities + .map(ident => { + const pin = pinLookupMap.get(ident.exit_node); + if (!pin) { + return null; + } + + return { + count: ident.count, + ...pin + } + }) + .filter(pin => !!pin), + showMore: existing?.showMore ?? false, + expanded: existing?.expanded ?? (this.expandCollapseState === 'expand' && s.identities.length > 1 /* there's always the "direct" identity */), + } as _Profile + }); + + this.searchProfiles(searchTerm); + + // check if we have profiles with bandwidth data and + // make sure our sort methods are updated. + if (this.profiles.some(p => p.bytes_received > 0 || p.bytes_sent > 0)) { + if (!this.sortTypes.includes(SortTypes.bytesReceived)) { + this.sortTypes.push.apply(this.sortTypes, bandwidthSorts) + } + + this.sortTypes = [...this.sortTypes]; + } else { + this.sortTypes = this.sortTypes.filter(type => { + return !bandwidthSorts.includes(type) + }) + } + + this.cdr.markForCheck(); + }) + } +} diff --git a/desktop/angular/src/app/shared/notification-list/index.ts b/desktop/angular/src/app/shared/notification-list/index.ts new file mode 100644 index 00000000..3fa640e3 --- /dev/null +++ b/desktop/angular/src/app/shared/notification-list/index.ts @@ -0,0 +1 @@ +export { NotificationListComponent as NotificationWidgetComponent, NotificationWidgetConfig } from './notification-list.component'; diff --git a/desktop/angular/src/app/shared/notification-list/notification-list.component.html b/desktop/angular/src/app/shared/notification-list/notification-list.component.html new file mode 100644 index 00000000..23f2cb08 --- /dev/null +++ b/desktop/angular/src/app/shared/notification-list/notification-list.component.html @@ -0,0 +1,24 @@ +Notifications + +
+
+
+ + + +
+
+ + {{notif.Title || notif.Message}} + +
+ +
+
+
+
diff --git a/desktop/angular/src/app/shared/notification-list/notification-list.component.scss b/desktop/angular/src/app/shared/notification-list/notification-list.component.scss new file mode 100644 index 00000000..e99c059e --- /dev/null +++ b/desktop/angular/src/app/shared/notification-list/notification-list.component.scss @@ -0,0 +1,186 @@ +:host { + @apply flex flex-col justify-start items-center gap-2; + @apply w-full px-2; + + @apply border-b border-gray-400 pb-2; + + &>* { + /* do not allow to shrink */ + flex-shrink: 0; + } +} + +.row, +div.placeholder { + display: flex; + flex-direction: column; + width: 100%; + margin: 0; + border: none; +} + +.row { + @apply overflow-hidden w-full flex flex-row rounded; + @apply h-8; + + .type { + display: flex; + justify-content: center; + align-items: center; + width: .5rem; + flex-shrink: 0; + flex-grow: 0; + background-color: #202020; + + &.info { + background-color: #727272; + } + + &.warning { + background-color: theme("colors.info.yellow"); + } + + &.error { + background-color: theme("colors.info.red"); + } + + &.broadcast { + width: 2rem; + color: #00000080; + } + } + + .preview { + background-color: #292929; + cursor: pointer; + overflow: hidden; + flex-grow: 1; + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 1rem; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + position: relative; + + span { + flex-grow: 1; + text-overflow: ellipsis; + overflow: hidden; + word-wrap: none; + white-space: nowrap; + + font-size: 0.7rem; + font-weight: 500; + + color: #cacaca; + + .category { + padding-left: 8px; + font-size: 0.65rem; + font-weight: 700; + text-transform: capitalize; + color: #999999c9; + } + } + + &:hover { + background-color: #303030; + + .buttons { + opacity: 1; + transition: all .05s ease-in-out; + transform: translateX(-100%); + } + } + + .buttons { + opacity: 0; + transition: all .05s ease-in-out; + height: 100%; + position: absolute; + left: 100%; + display: flex; + white-space: nowrap; + background-color: #303030; + + button { + outline: none; + @apply bg-transparent; + font-size: 0.6rem; + background-color: #3a3a3a; + padding-left: 1.25rem; + padding-right: 1.25rem; + text-transform: capitalize; + border-radius: 0; + font-weight: 500; + outline: none; + color: hsla(0, 0%, 100%, 0.548); + height: 100%; + + &:hover { + background-color: #363636; + color: #ffffff; + } + + &:first-of-type { + margin-left: .5rem; + } + + &:last-of-type { + background: transparent; + color: hsla(0, 0%, 100%, 0.562); + @apply ml-1; + transition: all cubic-bezier(0.175, 0.885, 0.32, 1.275) .2s; + + &:hover { + color: #ffffff; + } + } + } + } + } +} + +/* +.notification-body { + @apply bg-cards-tertiary; + flex-grow: 1; + @apply rounded-b; + position: absolute; + top: var(--slot-size); + bottom: 0; + + .broadcast-info { + background-color: #00000040; + width: 100%; + padding: 0.5rem; + color: white !important; + font-weight: 400; + bottom: 0; + position: absolute; + flex-grow: 1; + @apply flex items-center justify-center gap-1; + } +} +*/ + +div.placeholder { + @apply font-medium; + @apply text-tertiary; + @apply flex-grow; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + user-select: none; +} + +app-loading { + opacity: .5; + margin-left: auto; + margin-right: auto; + position: relative; + top: 5px; +} diff --git a/desktop/angular/src/app/shared/notification-list/notification-list.component.ts b/desktop/angular/src/app/shared/notification-list/notification-list.component.ts new file mode 100644 index 00000000..d54e2b98 --- /dev/null +++ b/desktop/angular/src/app/shared/notification-list/notification-list.component.ts @@ -0,0 +1,138 @@ +import { animate, style, transition, trigger } from '@angular/animations'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, OnDestroy, OnInit, TrackByFunction, inject } from '@angular/core'; +import { SfngDialogService } from '@safing/ui'; +import { Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Action, Notification, NotificationType, NotificationsService } from 'src/app/services'; +import { moveInOutAnimation, moveInOutListAnimation } from 'src/app/shared/animations'; +import { NotificationComponent } from '../notification/notification'; + +export interface NotificationWidgetConfig { + markdown: boolean; +} + +export interface _Notification extends Notification { + isBroadcast: boolean +} + +@Component({ + selector: 'app-notification-list', + templateUrl: './notification-list.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: [ + './notification-list.component.scss' + ], + animations: [ + trigger( + 'fadeIn', + [ + transition( + ':enter', + [ + style({ opacity: 0 }), + animate('.2s .2s ease-in', + style({ opacity: 1 })) + ] + ), + ] + ), + moveInOutAnimation, + moveInOutListAnimation + ] +}) +export class NotificationListComponent implements OnInit, OnDestroy { + readonly types = NotificationType; + readonly dialog = inject(SfngDialogService); + readonly cdr = inject(ChangeDetectorRef); + + /** Used to set a fixed height when a notification is expanded. */ + @HostBinding('style.height') + height: null | string = null; + + /** Sets the overflow to hidden when a notification is expanded. */ + @HostBinding('style.overflow') + get overflow() { + if (this.height === null) { + return null; + } + return 'hidden'; + } + + @HostBinding('class.empty') + get isEmpty() { + return this.notifications.length === 0; + } + + @HostBinding('@moveInOutList') + get length() { return this.notifications.length } + + /** Subscription to notification updates. */ + private notifSub = Subscription.EMPTY; + + /** All active notifications. */ + notifications: _Notification[] = []; + + trackBy: TrackByFunction<_Notification> = this.notifsService.trackBy; + + constructor( + public elementRef: ElementRef, + public notifsService: NotificationsService, + ) { } + + ngOnInit(): void { + this.notifSub = this.notifsService + .new$ + .pipe( + // filter out any prompts as they are handled by a different widget. + map(notifs => { + return notifs.filter(notif => !notif.SelectedActionID && !(notif.Type === NotificationType.Prompt && notif.EventID.startsWith("filter:prompt"))) + }) + ) + .subscribe(list => { + this.notifications = list.map(notification => { + return { + ...notification, + isBroadcast: notification.EventID.startsWith("broadcasts:"), + } + }); + + this.cdr.markForCheck(); + }); + } + + ngOnDestroy() { + this.notifSub.unsubscribe(); + } + + /** + * @private + * + * Executes a notification action and updates the "expanded-notification" + * view if required. + * + * @param n The notification object. + * @param actionId The ID of the action to execute. + * @param event The mouse click event. + */ + execute(n: _Notification, action: Action, event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); + + this.notifsService.execute(n, action) + .subscribe() + } + + /** + * @private + * Toggles between list mode and notification-view mode. + * + * @param notif The notification that has been clicked. + */ + toggelView(notif: _Notification) { + const ref = this.dialog.create(NotificationComponent, { + backdrop: 'light', + autoclose: true, + data: notif, + }); + } +} diff --git a/desktop/angular/src/app/shared/notification/notification.html b/desktop/angular/src/app/shared/notification/notification.html new file mode 100644 index 00000000..c3d7bcf6 --- /dev/null +++ b/desktop/angular/src/app/shared/notification/notification.html @@ -0,0 +1,27 @@ +
+ Notification + + + Broadcast Notification + + + + + + + +

{{notification.Title}}

+ + + +
+ +
+
diff --git a/desktop/angular/src/app/shared/notification/notification.scss b/desktop/angular/src/app/shared/notification/notification.scss new file mode 100644 index 00000000..5142a8c0 --- /dev/null +++ b/desktop/angular/src/app/shared/notification/notification.scss @@ -0,0 +1,48 @@ +:host { + @apply block; + max-width: 24rem; +} + +caption { + @apply text-xxs; + opacity: .6; +} + +h1 { + @apply text-base font-normal my-4; +} + +.message, +h1 { + flex-shrink: 0; + text-overflow: ellipsis; + word-break: normal; +} + +.message { + flex-grow: 1; + padding: 0; +} + +.close-icon { + position: absolute; + top: 1rem; + right: 1rem; + opacity: .7; + cursor: pointer; + + &:hover { + opacity: 1; + } +} + +.buttons { + width: 100%; + display: flex; + + @apply flex flex-row justify-end gap-2; +} + +a { + text-decoration: underline; +} diff --git a/desktop/angular/src/app/shared/notification/notification.ts b/desktop/angular/src/app/shared/notification/notification.ts new file mode 100644 index 00000000..a50dcdee --- /dev/null +++ b/desktop/angular/src/app/shared/notification/notification.ts @@ -0,0 +1,65 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, OnInit, Output, inject } from '@angular/core'; +import { SFNG_DIALOG_REF } from '@safing/ui'; +import { Action, NotificationState, NotificationsService, getNotificationTypeString } from '../../services'; +import { _Notification } from '../notification-list/notification-list.component'; + +@Component({ + selector: 'app-notification', + templateUrl: './notification.html', + styleUrls: ['./notification.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NotificationComponent implements OnInit { + readonly ref = inject(SFNG_DIALOG_REF); + readonly notification: _Notification = inject(SFNG_DIALOG_REF).data; + + /** + * The host tag of the notification component has the notification type + * and the notification state as a class name set. + * Examples: + * + * notif-action-required notif-prompt + */ + @HostBinding('class') + get hostClass(): string { + let cls = `notif-${this.state}`; + if (!!this.notification) { + cls = `${cls} notif-${getNotificationTypeString(this.notification.Type)}` + } + return cls + } + + state: NotificationState = NotificationState.Invalid; + + ngOnInit() { + if (!!this.notification) { + this.state = this.notification.State || NotificationState.Invalid; + } else { + this.state = NotificationState.Invalid; + } + } + + @Input() + set allowMarkdown(v: any) { + this._markdown = coerceBooleanProperty(v); + } + get allowMarkdown() { return this._markdown; } + private _markdown: boolean = true; + + @Output() + actionExecuted: EventEmitter = new EventEmitter(); + + constructor(private notifService: NotificationsService) { } + + execute(n: _Notification, action: Action) { + this.notifService.execute(n, action) + .subscribe( + () => { + this.actionExecuted.next(action) + this.ref.close(); + }, + err => console.error(err), + ) + } +} diff --git a/desktop/angular/src/app/shared/pipes/bytes.pipe.ts b/desktop/angular/src/app/shared/pipes/bytes.pipe.ts new file mode 100644 index 00000000..a0be5486 --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/bytes.pipe.ts @@ -0,0 +1,28 @@ +import { DecimalPipe } from "@angular/common"; +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + pure: true, + name: 'bytes', +}) +export class BytesPipe implements PipeTransform { + transform(value: any, decimal: string = '1.0-2', ...args: any[]) { + value = +value; // convert to number + + const ceilings = [ + 'B', + 'kB', + 'MB', + 'GB', + 'TB' + ] + + let idx = 0; + while (value > 1024 && idx < ceilings.length - 1) { + value = value / 1024; + idx++ + } + + return (new DecimalPipe('en-US')).transform(value, decimal) + ' ' + ceilings[idx]; + } +} diff --git a/desktop/angular/src/app/shared/pipes/common-pipes.module.ts b/desktop/angular/src/app/shared/pipes/common-pipes.module.ts new file mode 100644 index 00000000..c64df8a1 --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/common-pipes.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from "@angular/core"; +import { BytesPipe } from "./bytes.pipe"; +import { TimeAgoPipe } from "./time-ago.pipe"; +import { ToAppProfilePipe } from "./to-profile.pipe"; +import { DurationPipe } from "./duration.pipe"; +import { RoundPipe } from "./round.pipe"; +import { ToSecondsPipe } from "./to-seconds.pipe"; + +@NgModule({ + declarations: [ + TimeAgoPipe, + BytesPipe, + ToAppProfilePipe, + DurationPipe, + RoundPipe, + ToSecondsPipe + ], + exports: [ + TimeAgoPipe, + BytesPipe, + ToAppProfilePipe, + DurationPipe, + RoundPipe, + ToSecondsPipe + ] +}) +export class CommonPipesModule { } diff --git a/desktop/angular/src/app/shared/pipes/duration.pipe.ts b/desktop/angular/src/app/shared/pipes/duration.pipe.ts new file mode 100644 index 00000000..df33c283 --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/duration.pipe.ts @@ -0,0 +1,103 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +const millisecond = 1; +const second = 1000 * millisecond; +const minute = 60 * second; +const hour = 60 * minute; +const day = 24 * hour; + +export function formatDuration(millis: number, skipDays = false, skipMillis = false): string { + const sign = millis < 0 ? '-' : ''; + let val = Math.abs(millis); + let str = ''; + + if (millis === 0) { + return '0'; + } + + if (!skipDays) { + const days = Math.floor(val / day) + if (days > 0) { + str += days.toString() + 'd '; + val -= days * day; + } + } + + const hours = Math.floor(val / hour); + if (hours > 0) { + str += hours.toString() + 'h '; + val -= hours * hour; + } + + const minutes = Math.floor(val / minute); + if (minutes > 0) { + str += minutes.toString() + 'm '; + val -= minutes * minute; + } + + const seconds = Math.floor(val / second); + if (seconds > 0) { + str += seconds.toString() + 's '; + val -= seconds * second; + } + + if (!skipMillis) { + const ms = Math.floor(val / millisecond) + if (ms > 0) { + str += ms.toString() + 'ms ' + val -= ms * millisecond + } + } + + if (str.endsWith("")) { + str = str.substring(0, str.length - 1) + } + + return sign + str; +} + +@Pipe({ + name: 'duration', + pure: true +}) +export class DurationPipe implements PipeTransform { + transform(value: number | [string, string] | [Date, Date] | [number, number], ...args: any[]) { + if (Array.isArray(value)) { + let firstNum: number; + let secondNum: number; + + let [first, second] = value; + if (first instanceof Date || typeof first === 'string') { + first = new Date(first) + firstNum = first.getTime() + } else { + firstNum = first + } + if (second instanceof Date || typeof second === 'string') { + second = new Date(second); + secondNum = second.getTime() + } else { + secondNum = second + } + + if (secondNum < firstNum) { + const t = firstNum; + firstNum = secondNum + secondNum = t + } + + value = secondNum - firstNum + } + + if (value < second) { + + } + + const result = formatDuration(value); + if (result === '0') { + return '< 1s' + } + + return result + } +} diff --git a/desktop/angular/src/app/shared/pipes/index.ts b/desktop/angular/src/app/shared/pipes/index.ts new file mode 100644 index 00000000..6eddfdd2 --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/index.ts @@ -0,0 +1,6 @@ +export * from './common-pipes.module'; +export * from './time-ago.pipe'; +export * from './to-profile.pipe'; +export * from './duration.pipe'; +export * from './to-seconds.pipe'; +export * from './round.pipe'; diff --git a/desktop/angular/src/app/shared/pipes/round.pipe.ts b/desktop/angular/src/app/shared/pipes/round.pipe.ts new file mode 100644 index 00000000..104ca0ac --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/round.pipe.ts @@ -0,0 +1,15 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'round', + pure: true, +}) +export class RoundPipe implements PipeTransform { + transform(value: number, roundBy: number) { + if (isNaN(value)) { + return NaN + } + + return Math.floor(value / roundBy) * roundBy + } +} diff --git a/desktop/angular/src/app/shared/pipes/time-ago.pipe.ts b/desktop/angular/src/app/shared/pipes/time-ago.pipe.ts new file mode 100644 index 00000000..25f53ac7 --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/time-ago.pipe.ts @@ -0,0 +1,56 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'timeAgo', + pure: true +}) +export class TimeAgoPipe implements PipeTransform { + transform(value: number | Date | string, ticker?: any): string { + return timeAgo(value); + } +} + +export const timeCeilings = [ + { ceiling: 1, text: "" }, + { ceiling: 60, text: "sec" }, + { ceiling: 3600, text: "min" }, + { ceiling: 86400, text: "hour" }, + { ceiling: 2629744, text: "day" }, + { ceiling: 31556926, text: "month" }, + { ceiling: Infinity, text: "year" } +] + +export function timeAgo(value: number | Date | string) { + if (typeof value === 'string') { + value = new Date(value) + } + + if (value instanceof Date) { + value = value.valueOf() / 1000; + } + + let suffix = 'ago' + + let diffInSeconds = Math.floor(((new Date()).valueOf() - (value * 1000)) / 1000); + if (diffInSeconds < 0) { + diffInSeconds = diffInSeconds * -1; + suffix = '' + } + + for (let i = timeCeilings.length - 1; i >= 0; i--) { + const f = timeCeilings[i]; + let n = Math.floor(diffInSeconds / f.ceiling); + if (n > 0) { + if (i < 1) { + return `< 1 min ` + suffix; + } + let text = timeCeilings[i + 1].text; + if (n > 1) { + text += 's'; + } + return `${n} ${text} ` + suffix + } + } + + return "< 1 min" + suffix // actually just now (diffInSeconds == 0) +} diff --git a/desktop/angular/src/app/shared/pipes/to-profile.pipe.ts b/desktop/angular/src/app/shared/pipes/to-profile.pipe.ts new file mode 100644 index 00000000..217e3b35 --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/to-profile.pipe.ts @@ -0,0 +1,35 @@ +import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform, inject } from "@angular/core"; +import { AppProfile, AppProfileService } from "@safing/portmaster-api"; +import { Subscription } from "rxjs"; + +@Pipe({ + name: 'toAppProfile', + pure: false +}) +export class ToAppProfilePipe implements PipeTransform, OnDestroy { + profileService = inject(AppProfileService); + cdr = inject(ChangeDetectorRef); + + private _lastProfile: AppProfile | null = null; + private _lastKey: string | null = null; + private _subscription = Subscription.EMPTY; + + transform(key: string): AppProfile | null { + if (key !== this._lastKey) { + this._lastKey = key; + + this._subscription.unsubscribe(); + this._subscription = this.profileService.watchAppProfile(key) + .subscribe(value => { + this._lastProfile = value; + this.cdr.markForCheck(); + }) + } + + return this._lastProfile || null; + } + + ngOnDestroy(): void { + this._subscription.unsubscribe(); + } +} diff --git a/desktop/angular/src/app/shared/pipes/to-seconds.pipe.ts b/desktop/angular/src/app/shared/pipes/to-seconds.pipe.ts new file mode 100644 index 00000000..166fbf17 --- /dev/null +++ b/desktop/angular/src/app/shared/pipes/to-seconds.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + name: 'toSeconds', + pure: true, +}) +export class ToSecondsPipe implements PipeTransform { + transform(value: Date | string, ...args: any[]) { + if (value === null || value === undefined) { + return NaN + } + + if (typeof value === 'string') { + value = new Date(value); + } + + return Math.floor(value.getTime() / 1000) + } +} diff --git a/desktop/angular/src/app/shared/process-details-dialog/index.ts b/desktop/angular/src/app/shared/process-details-dialog/index.ts new file mode 100644 index 00000000..cceeb5f8 --- /dev/null +++ b/desktop/angular/src/app/shared/process-details-dialog/index.ts @@ -0,0 +1 @@ +export * from './process-details-dialog'; diff --git a/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.html b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.html new file mode 100644 index 00000000..85cfa1bb --- /dev/null +++ b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.html @@ -0,0 +1,131 @@ +

+ Process Details +

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name +
+ + {{ process.Name }} +
+
User{{ process.UserName }} ({{ process.UserID + }})
Process ID{{ process.Pid }}
Process Group ID{{ process.Pgid }}
Parent Process ID{{ process.ParentPid }}
Path{{ process.Path }} ({{ process.MatchingPath + }}) + +
Executable Name{{ process.ExecName }}
Command Line{{ process.CmdLine }} + +
+
+ Tags + +
+
+ This process does not have any tags. +
    +
  • + {{ tag.Key }} + {{ tag.Value }} +
  • +
+
+
+
+ + +
+
+ This process does not have any environment variables. +
+ + + + + + + + + +
{{ env.key }}{{ env.value }} + +
+
+
+
+ +
+ +
\ No newline at end of file diff --git a/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.scss b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.scss new file mode 100644 index 00000000..609e4e1f --- /dev/null +++ b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.scss @@ -0,0 +1,32 @@ +:host { + @apply flex flex-col gap-4 max-w-2xl; + min-width: 500px; + width: 60vw; + + min-height: 500px; + height: 60vh; + max-height: 80vh; + overflow: hidden; +} + +table.custom { + @apply w-full overflow-hidden; + + th, + td { + @apply px-2 align-top py-2; + } + + th { + text-align: left; + @apply w-32 text-secondary; + } + + td { + @apply whitespace-normal break-all; + } + + td:last-of-type { + @apply p-0; + } +} diff --git a/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.ts b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.ts new file mode 100644 index 00000000..1f0daa0c --- /dev/null +++ b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.ts @@ -0,0 +1,102 @@ +import { KeyValue } from '@angular/common'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { AppProfile, AppProfileService, FingerpringOperation, Fingerprint, FingerprintType, PortapiService, Process } from '@safing/portmaster-api'; +import { SfngDialogRef, SfngDialogService, SFNG_DIALOG_REF } from '@safing/ui'; +import { EditProfileDialog } from '../edit-profile-dialog'; + +@Component({ + selector: 'app-process-details', + templateUrl: './process-details-dialog.html', + styleUrls: ['./process-details-dialog.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ProcessDetailsDialogComponent { + process: (Process & { ID: string }); + + constructor( + @Inject(SFNG_DIALOG_REF) private dialogRef: SfngDialogRef, + private dialog: SfngDialogService, + private portapi: PortapiService, + private profileService: AppProfileService + ) { + this.process = { + ...this.dialogRef.data, + ID: this.dialogRef.data.PrimaryProfileID, + } + } + + close() { + this.dialogRef.close(); + } + + createProfileForPath() { + this.createProfileWithFingerprint({ + Type: FingerprintType.Path, + Key: '', + Value: this.process.MatchingPath || this.process.Path, + Operation: FingerpringOperation.Equal, + }) + } + + createProfileForCmdline() { + this.createProfileWithFingerprint({ + Type: FingerprintType.Cmdline, + Key: '', + Value: this.process.CmdLine, + Operation: FingerpringOperation.Equal, + }) + } + + createProfileForEnv(env: KeyValue) { + const fp: Fingerprint = { + Type: FingerprintType.Env, + Key: env.key, + Value: env.value, + Operation: FingerpringOperation.Equal, + } + + this.createProfileWithFingerprint(fp) + } + + openParent() { + if (!!this.process.ParentPid) { + this.portapi.get(`network:tree/${this.process.ParentPid}-${this.process.ParentCreatedAt}`) + .subscribe(process => { + this.process = { + ...process, + ID: process.PrimaryProfileID, + }; + }) + } + } + + openGroup() { + this.profileService.getProcessByPid(this.process.Pid) + .subscribe(result => { + if (!result) { + return; + } + + this.process = { + ...result, + ID: result.PrimaryProfileID + }; + }) + } + + private createProfileWithFingerprint(fp: Fingerprint) { + let profilePreset: Partial = { + Fingerprints: [ + fp + ] + }; + + this.dialog.create(EditProfileDialog, { + data: profilePreset, + backdrop: true, + autoclose: false, + }) + + this.dialogRef.close(); + } +} diff --git a/desktop/angular/src/app/shared/prompt-list/index.ts b/desktop/angular/src/app/shared/prompt-list/index.ts new file mode 100644 index 00000000..b76fae32 --- /dev/null +++ b/desktop/angular/src/app/shared/prompt-list/index.ts @@ -0,0 +1 @@ +export { PromptListComponent as PromptWidgetComponent } from './prompt-list.component'; diff --git a/desktop/angular/src/app/shared/prompt-list/prompt-list.component.html b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.html new file mode 100644 index 00000000..4d32a21e --- /dev/null +++ b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.html @@ -0,0 +1,68 @@ +
+
+
+ + {{ profile.Name }} + {{ profile.prompts.length }} + + + Per Connection + Allow All + Block All + + Default Action + Allow App + Block App + + Change Default + + + App Settings + + +
+ +
+
+
+
+ + + {{ prompt.EventData?.Entity?.IP || 'N/A' }} + + + {{prompt.subdomain}}.{{prompt.domain}} + + + + + + + +
+
+ {{ profile.prompts.length - profile.promptsLimited.length }} + more +
+ +
+ Show less +
+
+
+
+ +
+ + + + + No Prompts +
+
\ No newline at end of file diff --git a/desktop/angular/src/app/shared/prompt-list/prompt-list.component.scss b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.scss new file mode 100644 index 00000000..90b34aad --- /dev/null +++ b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.scss @@ -0,0 +1,204 @@ +:host { + overflow: hidden; + max-height: 50vh; + display: flex; + flex-direction: column; + min-height: 10rem; + @apply w-80; + @apply bg-gray-300; + + padding-top: 1px; + padding-bottom: 3px; +} + +app-icon { + --app-icon-size: 13px; +} + +.scrollable { + @apply p-0; +} + +.group { + @apply mb-3; + + .group-header { + @apply px-2; + display: flex; + align-items: center; + margin-left: 4px; + height: 2rem; + + .app-name { + flex-grow: 1; + font-size: 0.7rem; + font-weight: 500; + color: #cacaca; + } + + span.prompt-count { + @apply mr-1; + font-size: 0.6rem; + font-weight: 600; + color: #cacaca; + transform: scale(0.95); + user-select: none; + } + } +} + +app-menu-item.item-seperator { + @apply border-t; + @apply border-buttons-dark +} + +.no-prompts { + @apply text-tertiary flex-grow; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + user-select: none; +} + +.prompts { + display: flex; + + .border { + margin-left: calc(0.5rem + 9px); + width: 0.5rem; + border-left-width: 2px; + border-bottom-width: 2px; + border-color: #292929; + } + + .prompt-container, + .prompt, + .actions { + display: flex; + } + + .prompt-container { + flex-grow: 1; + flex-direction: column; + padding-left: 0.6rem; + padding-right: 0.5rem; + padding-top: 0.4rem; + padding-bottom: 1rem; + + .prompt { + padding-left: 0.75rem; + margin-bottom: 4px; + background-color: #292929; + height: auto; + border-radius: 2px; + align-items: center; + overflow: hidden; + position: relative; + + &:hover { + background-color: #303030; + + .actions { + animation: .07s slidein-left ease-in-out; + opacity: 1; + transition: all .05s ease-in-out; + } + } + + .entity { + flex-grow: 1; + word-break: break-all; + white-space: normal; + font-size: 0.7rem; + font-weight: 500; + padding-top: 0.6rem; + padding-bottom: 0.6rem; + padding-left: 2px; + padding-right: 9px; + color: #cacaca; + + .subdomain { + font-size: 0.7rem; + font-weight: 500; + color: #999999; + } + } + + .actions { + min-width: 5rem; + flex-wrap: wrap; + height: 100%; + opacity: 0; + transition: all .05s ease-in-out; + position: absolute; + right: 0; + background-color: #292929; + + button { + outline: none; + @apply bg-transparent; + font-size: 0.6rem; + background-color: #3a3a3a; + padding-left: 1.25rem; + padding-right: 1.25rem; + text-transform: capitalize; + border-radius: 0; + font-weight: 500; + outline: none; + color: hsla(0, 0%, 100%, 0.548); + + padding-left: 1.25rem; + padding-right: 1.25rem; + text-transform: capitalize; + border-radius: 0; + font-weight: 500; + outline: none; + color: hsla(0, 0%, 100%, 0.548); + + &:hover { + background-color: #363636; + color: #ffffff; + } + + &:last-of-type { + background: transparent; + color: hsla(0, 0%, 100%, 0.562); + @apply ml-1; + transition: all cubic-bezier(0.175, 0.885, 0.32, 1.275) .2s; + + &:hover { + color: #ffffff; + } + } + } + } + } + } + + .more-available { + position: relative; + top: 1.4rem; + margin-top: -1rem; + cursor: pointer; + font-size: 0.7rem; + font-weight: 500; + color: #999999; + user-select: none; + + &:hover { + color: #cacaca; + } + } +} + +@keyframes slidein-left { + 0% { + transform: translateX(100%); + } + + 100% { + transform: translateX(0); + } +} diff --git a/desktop/angular/src/app/shared/prompt-list/prompt-list.component.ts b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.ts new file mode 100644 index 00000000..d6a3ca36 --- /dev/null +++ b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.ts @@ -0,0 +1,236 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, OnDestroy, OnInit, TrackByFunction } from '@angular/core'; +import { AppProfile, AppProfileService, deepClone, setAppSetting } from '@safing/portmaster-api'; +import { combineLatest, forkJoin, Observable, Subscription } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { Action, ConnectionPrompt, NotificationsService, NotificationType } from 'src/app/services'; +import { moveInOutAnimation, moveInOutListAnimation } from 'src/app/shared/animations'; +import { ParsedDomain, parseDomain } from 'src/app/shared/utils'; +import { ActionIndicatorService } from '../action-indicator'; + +// ExtendedConnectionPrompt extends the normal connection prompt +// with parsed domain information. +interface ExtendedConnectionPrompt extends ConnectionPrompt, ParsedDomain { } + +// ProfilePrompts extends an application profile with prompt +// information mainly used for paginagtion. +interface ProfilePrompts extends AppProfile { + promptsLimited: ExtendedConnectionPrompt[]; + prompts: ExtendedConnectionPrompt[]; + showAll: boolean; +} + +// Number of prompts to display per application profile +// before we start to paginate the list of prompts. +const PromptLimit = 3; + +@Component({ + selector: 'app-prompt-list', + templateUrl: './prompt-list.component.html', + styleUrls: [ + './prompt-list.component.scss' + ], + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + moveInOutAnimation, + moveInOutListAnimation + ] +}) +export class PromptListComponent implements OnInit, OnDestroy { + profiles: ProfilePrompts[] = []; + + /** + * @private + * Sets "empty" class on the host element if no prompts are displayed + */ + @HostBinding('class.empty') + get isEmpty() { + return this.profiles.length === 0; + } + + // Subscription to new prompts and profile updates. + private subscription = Subscription.EMPTY; + + constructor( + private changeDetectorRef: ChangeDetectorRef, + private profileService: AppProfileService, + public notifService: NotificationsService, + public uai: ActionIndicatorService + ) { } + + trackPrompts: TrackByFunction = this.notifService.trackBy; + + ngOnInit() { + // filter the stream of all notifications to only emit + // prompts that are used by the privacy filter (filter:prompt prefix). + const prompts$: Observable = this.notifService + .new$ + .pipe( + map(notifs => notifs.filter(notif => { + return notif.Type === NotificationType.Prompt && + notif.EventID.startsWith("filter:prompt"); + })), + ); + + // each time the notification list is emitted make sure we have an + // up-to-date copy of the linked application profile as well. + const profiles$ = prompts$ + .pipe( + switchMap(notifs => { + // collect all profile keys in a distict set so we don't load + // them more that once. + var profileKeys = new Set(); + notifs.forEach(n => profileKeys.add( + this.profileService.getKey(n.EventData!.Profile.Source, n.EventData!.Profile.ID) + )); + // load all of them in parallel + return forkJoin( + Array.from(profileKeys).map(key => this.profileService.getAppProfileFromKey(key)) + ) + }) + ); + + // subscribe to updates on the prompt list and the related profiles. + this.subscription = + combineLatest([ + prompts$, + profiles$, + ]).subscribe(([prompts, profiles]) => { + + let promptsByProfile = new Map(); + + // for each prompt, make an "extended" connection prompt by parsing the + // domain and index them by profile key + prompts.forEach(prompt => { + // prompts must have the connection data attached. If not, ignore it + // here. + if (!prompt.EventData) { + return; + } + + // get the list of prompts indexed by the profile ID. if this is + // the first prompt for that profile create a new array and place + // it at the index. + let entries = promptsByProfile.get(prompt.EventData.Profile.ID); + if (!entries) { + entries = []; + promptsByProfile.set(prompt.EventData.Profile.ID, entries); + } + + // Create an "extended" version of the prompt by parsing + // and assigning the domain and subdomain values. + let copy: ExtendedConnectionPrompt = { + ...prompt, + domain: null, + subdomain: null, + } + Object.assign(copy, parseDomain(prompt.EventData.Entity.Domain)) + entries.push(copy) + }); + + // Convert the list of application profiles into a set of ProfilePrompts + // objects that we can use to actually display the prompts with pagination + // applied. + this.profiles = profiles + .filter(profile => !!promptsByProfile.get(profile.ID)) + .map(profile => { + const prompts = promptsByProfile.get(profile.ID)!; + return { + ...profile, + showAll: prompts.length < PromptLimit, + promptsLimited: prompts.slice(0, PromptLimit), + prompts: prompts, + }; + }) + .sort((a, b) => { + if (a.ID > b.ID) { + return 1; + } + if (a.ID < b.ID) { + return -1; + } + return 0; + }); + + this.changeDetectorRef.markForCheck(); + }) + } + + allow(prompt: ConnectionPrompt) { + let allowActions = [ + 'allow-domain-all', + 'allow-serving-ip', + 'allow-ip', + ]; + + for (let i = 0; i < allowActions.length; i++) { + const action = prompt.AvailableActions.find(a => a.ID === allowActions[i]) + if (!!action) { + this.execute(prompt, action); + return; + } + } + } + + block(prompt: ConnectionPrompt) { + let permitActions = [ + 'block-domain-all', + 'block-serving-ip', + 'block-ip', + ]; + + for (let i = 0; i < permitActions.length; i++) { + const action = prompt.AvailableActions.find(a => a.ID === permitActions[i]) + if (!!action) { + this.execute(prompt, action); + return; + } + } + } + + changeDefault(profile: ProfilePrompts, newDefault: 'permit' | 'block') { + + this.profileService + .getAppProfile(profile.Source, profile.ID) + .pipe( + map(rawProfile => { + const copy = deepClone(rawProfile); + setAppSetting(copy.Config || {}, 'filter/defaultAction', newDefault) + + return copy + }), + switchMap(updatedProfile => this.profileService.saveProfile(updatedProfile)), + ) + .subscribe({ + error: (err) => { + this.uai.error('Failed to change App Settings', this.uai.getErrorMessage(err)); + } + }) + + + setAppSetting(profile.Config || {}, 'filter/defaultAction', newDefault) + } + + allowAll(profile: ProfilePrompts) { + profile.prompts.forEach(prompt => this.allow(prompt)); + } + + denyAll(profile: ProfilePrompts) { + profile.prompts.forEach(prompt => this.block(prompt)); + } + + execute(prompt: ConnectionPrompt, action: Action) { + this.notifService.execute(prompt, action) + .subscribe({ + error: console.error, + }); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + + /** @private - {@link TrackByFunction} for profile prompts */ + trackProfile(_: number, p: ProfilePrompts) { + return p.ID; + } +} diff --git a/desktop/angular/src/app/shared/security-lock/index.ts b/desktop/angular/src/app/shared/security-lock/index.ts new file mode 100644 index 00000000..71561750 --- /dev/null +++ b/desktop/angular/src/app/shared/security-lock/index.ts @@ -0,0 +1 @@ +export * from './security-lock'; diff --git a/desktop/angular/src/app/shared/security-lock/security-lock.html b/desktop/angular/src/app/shared/security-lock/security-lock.html new file mode 100644 index 00000000..146fb34a --- /dev/null +++ b/desktop/angular/src/app/shared/security-lock/security-lock.html @@ -0,0 +1,25 @@ +
+ + + + + + + + + + +

{{lockLevel?.displayText}}

+ + See Notifications + +
+
diff --git a/desktop/angular/src/app/shared/security-lock/security-lock.scss b/desktop/angular/src/app/shared/security-lock/security-lock.scss new file mode 100644 index 00000000..12533ded --- /dev/null +++ b/desktop/angular/src/app/shared/security-lock/security-lock.scss @@ -0,0 +1,120 @@ +svg.shield { + width: 100%; + max-width: 7.25rem; + + transform: scale(0.95); + + path { + top: 0px; + left: 0px; + transform-origin: center center; + } + + .shield-one { + transform: scale(.62); + } + + .shield-two { + animation-delay: -1.2s; + opacity: .6; + transform: scale(.8); + } + + .shield-three { + animation-delay: -2.5s; + opacity: .4; + transform: scale(1); + } + + &.text-green-300 { + filter: saturate(1.4); + + .shield-one { + fill: var(--protection-ok-primary); + } + + + .shield-two { + fill: var(--protection-ok-secondary); + } + + .shield-three { + fill: var(--protection-ok-tertiary); + } + + .shield-warn, + .shield-fail { + display: none; + } + + .shield-ok { + stroke: var(--background); + fill: none; + transform: scale(.5); + } + } + + &.text-yellow-300 { + filter: saturate(1.3); + + .shield-one { + fill: var(--protection-warn-primary); + } + + .shield-three, + .shield-two { + //animation: shield-pulse 3s linear; + } + + .shield-two { + fill: var(--protection-warn-secondary); + } + + .shield-three { + fill: var(--protection-warn-tertiary); + } + + .shield-ok, + .shield-fail { + display: none; + } + + .shield-warn { + stroke: var(--background); + fill: none; + transform: scale(.5); + } + } + + &.text-red-300 { + filter: saturate(1.3); + + .shield-one { + fill: var(--protection-fail-primary); + } + + .shield-three, + .shield-two { + //animation: shield-pulse 3s linear reverse; + } + + .shield-two { + fill: var(--protection-fail-secondary); + } + + .shield-three { + fill: var(--protection-fail-tertiary); + } + + .shield-warn, + .shield-ok { + display: none; + } + + .shield-fail { + stroke: var(--background); + fill: none; + transform: scale(.45); + } + } +} diff --git a/desktop/angular/src/app/shared/security-lock/security-lock.ts b/desktop/angular/src/app/shared/security-lock/security-lock.ts new file mode 100644 index 00000000..7b6922c3 --- /dev/null +++ b/desktop/angular/src/app/shared/security-lock/security-lock.ts @@ -0,0 +1,97 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Input, OnInit, inject } from "@angular/core"; +import { SecurityLevel } from "@safing/portmaster-api"; +import { combineLatest } from "rxjs"; +import { FailureStatus, StatusService, Subsystem } from "src/app/services"; +import { fadeInAnimation, fadeOutAnimation } from "../animations"; + +interface SecurityOption { + level: SecurityLevel; + displayText: string; + class: string; + subText?: string; +} + +@Component({ + selector: 'app-security-lock', + templateUrl: './security-lock.html', + changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['./security-lock.scss'], + animations: [ + fadeInAnimation, + fadeOutAnimation + ] +}) +export class SecurityLockComponent implements OnInit { + private destroyRef = inject(DestroyRef); + + lockLevel: SecurityOption | null = null; + + /** The display mode for the security lock */ + @Input() + mode: 'small' | 'full' = 'full' + + constructor( + private statusService: StatusService, + private cdr: ChangeDetectorRef, + ) { } + + ngOnInit(): void { + combineLatest([ + this.statusService.status$, + this.statusService.watchSubsystems() + ]) + .subscribe(([status, subsystems]) => { + const activeLevel = status.ActiveSecurityLevel; + const suggestedLevel = status.ThreatMitigationLevel; + + // By default the lock is green and we are "Secure" + this.lockLevel = { + level: SecurityLevel.Normal, + class: 'text-green-300', + displayText: 'Secure', + } + + // Find the highest failure-status reported by any module + // of any subsystem. + const failureStatus = subsystems.reduce((value: FailureStatus, system: Subsystem) => { + if (system.FailureStatus != 0) { + console.log(system); + } + return system.FailureStatus > value + ? system.FailureStatus + : value; + }, FailureStatus.Operational) + + // update the failure level depending on the highest + // failure status. + switch (failureStatus) { + case FailureStatus.Warning: + this.lockLevel = { + level: SecurityLevel.High, + class: 'text-yellow-300', + displayText: 'Warning' + } + break; + case FailureStatus.Error: + this.lockLevel = { + level: SecurityLevel.Extreme, + class: 'text-red-300', + displayText: 'Insecure' + } + break; + } + + // if the auto-pilot would suggest a higher (mitigation) level + // we are always Insecure + if (activeLevel < suggestedLevel) { + this.lockLevel = { + level: SecurityLevel.High, + class: 'high', + displayText: 'Insecure' + } + } + + this.cdr.markForCheck(); + }); + } +} diff --git a/desktop/angular/src/app/shared/spn-account-details/index.ts b/desktop/angular/src/app/shared/spn-account-details/index.ts new file mode 100644 index 00000000..623342d5 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-account-details/index.ts @@ -0,0 +1 @@ +export * from './spn-account-details'; diff --git a/desktop/angular/src/app/shared/spn-account-details/spn-account-details.html b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.html new file mode 100644 index 00000000..3b594057 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.html @@ -0,0 +1,101 @@ + +

Account Details

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Your Package{{ currentUser.current_plan?.name }}
Access Until{{ currentUser.subscription.ends_at | date:'medium' }}
Your Subscription{{ currentUser.current_plan?.name }}
Status{{ currentUser.subscription.state }}
Next Payment Date + {{ currentUser.subscription.next_billing_date | date:'medium' }} + via + {{ currentUser.subscription.payment_provider }} +
Access Paid Until{{ currentUser.subscription.ends_at | date:'medium' }}
Username{{ currentUser.username }}
Device Name{{ currentUser.device?.name }}
Account State{{ currentUser.state }}
Features{{ currentUser.current_plan?.feature_ids?.join(", ") }}
Device ID{{currentUser.device?.id}}
Logged in Since{{ currentUser.LoggedInAt | date:'medium' }}
+ +
+ + +
+ + + Open Account Page + + + + +
+
+ + diff --git a/desktop/angular/src/app/shared/spn-account-details/spn-account-details.scss b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.scss new file mode 100644 index 00000000..f1c7dc0d --- /dev/null +++ b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.scss @@ -0,0 +1,7 @@ +table tr { + background-color: transparent !important; +} + +table .table-section-start { + border-top: 1.5rem solid transparent; +} diff --git a/desktop/angular/src/app/shared/spn-account-details/spn-account-details.ts b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.ts new file mode 100644 index 00000000..512f6674 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.ts @@ -0,0 +1,83 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Inject, OnInit, Optional, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { SPNService, UserProfile } from "@safing/portmaster-api"; +import { SFNG_DIALOG_REF, SfngDialogRef } from "@safing/ui"; +import { catchError, delay, of, tap } from "rxjs"; +import { ActionIndicatorService } from "../action-indicator"; + +@Component({ + templateUrl: './spn-account-details.html', + styleUrls: ['./spn-account-details.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SPNAccountDetailsComponent implements OnInit { + private destroyRef = inject(DestroyRef); + + /** Whether or not we're currently refreshing the user profile from the customer agent */ + refreshing = false; + + /** Whether or not we're still waiting for the user profile to be fetched from the backend */ + loadingProfile = true; + + currentUser: UserProfile | null = null; + + constructor( + private spnService: SPNService, + private cdr: ChangeDetectorRef, + private uai: ActionIndicatorService, + @Inject(SFNG_DIALOG_REF) @Optional() public dialogRef: SfngDialogRef, + ) { } + + /** + * Force a refresh of the local user account + * + * @private - template only + */ + refreshAccount() { + this.refreshing = true; + this.spnService.userProfile(true) + .pipe( + delay(1000), + tap(() => { + this.refreshing = false; + this.cdr.markForCheck(); + }), + ) + .subscribe() + } + + /** + * Logout of your safing account + * + * @private - template only + */ + logout() { + this.spnService.logout() + .pipe(tap(() => this.dialogRef?.close())) + .subscribe(this.uai.httpObserver('SPN Logout', 'SPN Logout')) + } + + ngOnInit(): void { + this.loadingProfile = false; + this.spnService.profile$ + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(err => of(null)), + ) + .subscribe({ + next: (profile) => { + this.loadingProfile = false; + this.currentUser = profile || null; + + this.cdr.markForCheck(); + }, + complete: () => { + // Database entry deletion will complete the observer. + this.loadingProfile = false; + this.currentUser = null; + + this.cdr.markForCheck(); + }, + }) + } +} diff --git a/desktop/angular/src/app/shared/spn-login/index.ts b/desktop/angular/src/app/shared/spn-login/index.ts new file mode 100644 index 00000000..25850856 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-login/index.ts @@ -0,0 +1 @@ +export * from './spn-login'; diff --git a/desktop/angular/src/app/shared/spn-login/spn-login.html b/desktop/angular/src/app/shared/spn-login/spn-login.html new file mode 100644 index 00000000..8f59d86c --- /dev/null +++ b/desktop/angular/src/app/shared/spn-login/spn-login.html @@ -0,0 +1,70 @@ + +
+

+
+ + + + + + + + + + + + + + + + +
+ + Safing Account Login + + Unlock powerful features. + + +

+ + +
+ You have been logged out by the account server. +
+ Please check your account. +
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
diff --git a/desktop/angular/src/app/shared/spn-login/spn-login.scss b/desktop/angular/src/app/shared/spn-login/spn-login.scss new file mode 100644 index 00000000..232d51ee --- /dev/null +++ b/desktop/angular/src/app/shared/spn-login/spn-login.scss @@ -0,0 +1,53 @@ +:host { + display: block; + width: 100%; +} + +.custom-form-input { + background: none; + @apply border-0 border-b border-buttons-light text-secondary font-medium px-0; + + &:active, + &:focus { + background: none; + } +} + +.logo-image { + @apply w-16 absolute; +} + +svg.logo-image { + animation-timing-function: cubic-bezier(0.445, 0.05, 0.55, 0.95); +} + +.spin { + animation-name: spin; + animation-duration: 3500ms; + animation-iteration-count: infinite; + animation-timing-function: linear; +} + +.reverse { + animation-name: spin-reverse; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes spin-reverse { + 0% { + transform: rotate(360deg); + } + + 100% { + transform: rotate(0deg); + } +} diff --git a/desktop/angular/src/app/shared/spn-login/spn-login.ts b/desktop/angular/src/app/shared/spn-login/spn-login.ts new file mode 100644 index 00000000..a5ae4172 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-login/spn-login.ts @@ -0,0 +1,70 @@ +import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Input, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { SPNService, UserProfile } from "@safing/portmaster-api"; +import { catchError, finalize, of } from "rxjs"; +import { ActionIndicatorService } from "../action-indicator"; + +@Component({ + selector: 'app-spn-login', + templateUrl: './spn-login.html', + styleUrls: ['./spn-login.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SPNLoginComponent implements OnInit { + private destroyRef = inject(DestroyRef); + + /** The current user profile if the user is already logged in */ + profile: UserProfile | null = null; + + /** The value of the username text box */ + username: string = ''; + + /** The value of the password text box */ + password: string = ''; + + @Input() + set forcedLogout(v: any) { + this._forcedLogout = coerceBooleanProperty(v); + } + get forcedLogout() { return this._forcedLogout } + private _forcedLogout = false; + + constructor( + private spnService: SPNService, + private uai: ActionIndicatorService, + private cdr: ChangeDetectorRef + ) { } + + login(): void { + if (!this.username || !this.password) { + return; + } + + this.spnService.login({ + username: this.username, + password: this.password + }) + .pipe(finalize(() => { + this.password = ''; + })) + .subscribe(this.uai.httpObserver('SPN Login', 'SPN Login')) + } + + ngOnInit(): void { + this.spnService.profile$ + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(() => of(null)) + ) + .subscribe(profile => { + this.profile = profile || null; + + if (!!this.profile) { + this.username = this.profile.username; + } + + this.cdr.markForCheck(); + }); + } +} diff --git a/desktop/angular/src/app/shared/spn-network-status/index.ts b/desktop/angular/src/app/shared/spn-network-status/index.ts new file mode 100644 index 00000000..bfa12d5d --- /dev/null +++ b/desktop/angular/src/app/shared/spn-network-status/index.ts @@ -0,0 +1 @@ +export * from './spn-network-status'; diff --git a/desktop/angular/src/app/shared/spn-network-status/spn-network-status.html b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.html new file mode 100644 index 00000000..83196071 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.html @@ -0,0 +1,28 @@ + +
+

Network Status

+ +
+ + Loading Network Status ... +
+
    +
  • +
    + {{ issue.title }} + {{ issue.closed ? 'closed' : 'opened'}} by {{ issue.user }} + {{ + issue.createdAt | timeAgo + }} +
    + +
    + + +
    +
  • +
+
diff --git a/desktop/angular/src/app/shared/spn-network-status/spn-network-status.scss b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.scss new file mode 100644 index 00000000..6c73c8bb --- /dev/null +++ b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.scss @@ -0,0 +1,71 @@ +:host { + @apply block; + min-width: 500px; + width: 50vw; +} + +.issue-list { + width: 100%; + + &, + ul { + overflow-y: auto; + } + + .issue { + position: relative; + display: flex; + flex-direction: column; + cursor: pointer; + @apply mx-2; + + .header { + @apply p-4; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + cursor: pointer; + } + + @apply rounded; + @apply bg-cards-primary; + + .title { + @apply mr-4; + } + + span { + word-break: keep-all; + } + + &:not(:last-child) { + margin-bottom: 0.5rem; + } + + .body { + @apply bg-cards-secondary; + @apply rounded-b; + @apply p-4; + } + + .meta { + @apply text-tertiary; + @apply font-normal; + opacity: .7; + font-size: 95%; + } + + &:hover { + @apply bg-cards-tertiary; + } + + fa-icon { + position: absolute; + right: 1rem; + top: 1rem; + opacity: .8; + cursor: pointer; + } + } +} diff --git a/desktop/angular/src/app/shared/spn-network-status/spn-network-status.ts b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.ts new file mode 100644 index 00000000..131c907f --- /dev/null +++ b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.ts @@ -0,0 +1,65 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, TrackByFunction, inject } from "@angular/core"; +import { map } from "rxjs"; +import { INTEGRATION_SERVICE } from "src/app/integration"; +import { Issue, SupportHubService } from "src/app/services"; + +/** The name of the SPN repository used to filter SPN support hub issues. */ +const SPNRepository = "spn"; + +/** A set of issue labels that are eligible to be displayed */ +const SPNTagSet = new Set(["network status"]) + +interface _Issue extends Issue { + expanded: boolean; +} + +@Component({ + selector: 'app-spn-network-status', + templateUrl: './spn-network-status.html', + styleUrls: ['./spn-network-status.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SPNNetworkStatusComponent implements OnInit { + private readonly integration = inject(INTEGRATION_SERVICE); + private readonly supportHub = inject(SupportHubService); + private readonly cdr = inject(ChangeDetectorRef); + + /** trackIssue is used as a track-by function when rendering SPN issues. */ + trackIssue: TrackByFunction<_Issue> = (_: number, issue: _Issue) => issue.url; + + spnIssues: _Issue[] = []; + + ngOnInit(): void { + this.supportHub.loadIssues() + .pipe( + map(issues => { + return issues + .filter(issue => issue.repository === SPNRepository && issue.labels?.some(l => { + return SPNTagSet.has(l); + })) + .reverse() + }) + ) + .subscribe(issues => { + let spnIssues: _Issue[] = issues + .map(i => { + const existing = this.spnIssues.find(existing => existing.url === i.url); + return { + ...i, + expanded: existing !== undefined ? existing.expanded : false + } + }) + this.spnIssues = spnIssues; + this.cdr.markForCheck(); + }) + } + + /** + * Open a github issue in a new tab/window + * + * @private - template only + */ + openIssue(issue: Issue) { + this.integration.openExternal(issue.url); + } +} diff --git a/desktop/angular/src/app/shared/spn-status/index.ts b/desktop/angular/src/app/shared/spn-status/index.ts new file mode 100644 index 00000000..a996c3cf --- /dev/null +++ b/desktop/angular/src/app/shared/spn-status/index.ts @@ -0,0 +1 @@ +export * from './spn-status'; diff --git a/desktop/angular/src/app/shared/spn-status/spn-status.html b/desktop/angular/src/app/shared/spn-status/spn-status.html new file mode 100644 index 00000000..84006d46 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-status/spn-status.html @@ -0,0 +1,54 @@ +
+ + + +
+

SPN

+ + + + Increase privacy protection + + + Failed to connect + + + Connecting to the SPN ... + + + You're protected + + + + + Home: {{ spnStatus?.ConnectedIP }} via {{ spnStatus?.ConnectedTransport}} + + + + + +
+ +
+
+
+
+ Identities + {{ identities }} +
+
+
diff --git a/desktop/angular/src/app/shared/spn-status/spn-status.ts b/desktop/angular/src/app/shared/spn-status/spn-status.ts new file mode 100644 index 00000000..5cc26478 --- /dev/null +++ b/desktop/angular/src/app/shared/spn-status/spn-status.ts @@ -0,0 +1,128 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, OnInit, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute, Router } from '@angular/router'; +import { BoolSetting, ChartResult, ConfigService, FeatureID, Netquery, SPNService, SPNStatus, UserProfile } from "@safing/portmaster-api"; +import { SfngDialogService } from '@safing/ui'; +import { catchError, forkJoin, interval, of, startWith, switchMap } from "rxjs"; +import { fadeInAnimation, fadeOutAnimation } from "../animations"; +import { SPNAccountDetailsComponent } from '../spn-account-details'; + +@Component({ + selector: 'app-spn-status', + templateUrl: './spn-status.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeInAnimation, + fadeOutAnimation, + ] +}) +export class SPNStatusComponent implements OnInit { + private destroyRef = inject(DestroyRef); + + /** Whether or not the SPN is currently enabled */ + spnEnabled = false; + + /** The chart data for the SPN connection chart */ + spnConnChart: ChartResult[] = []; + + /** The current amount of SPN identities used */ + identities: number = 0; + + /** The current SPN user profile */ + profile: UserProfile | null = null; + + /** The current status of the SPN module */ + spnStatus: SPNStatus | null = null; + + /** Returns whether or not the current package has the SPN feature */ + get packageHasSPN() { + return this.profile?.current_plan?.feature_ids?.includes(FeatureID.SPN) + } + + constructor( + private configService: ConfigService, + private spnService: SPNService, + private netquery: Netquery, + private cdr: ChangeDetectorRef, + private router: Router, + private activeRoute: ActivatedRoute, + private dialog: SfngDialogService + ) { } + + ngOnInit(): void { + this.spnService + .profile$ + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError(() => of(null)) + ) + .subscribe(profile => { + this.profile = profile || null; + + this.cdr.markForCheck(); + }); + + this.spnService.status$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(status => { + this.spnStatus = status; + + this.cdr.markForCheck(); + }) + + this.configService.watch("spn/enable") + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(value => { + this.spnEnabled = value; + + // If the user disabled the SPN clear the connection chart + // as well. + if (!this.spnEnabled) { + this.spnConnChart = []; + } + + this.cdr.markForCheck(); + }); + + interval(5000) + .pipe( + startWith(-1), + takeUntilDestroyed(this.destroyRef), + switchMap(() => forkJoin({ + chart: this.netquery.activeConnectionChart({ tunneled: { $eq: true } }), + identities: this.netquery.query({ + query: { tunneled: { $eq: true }, exit_node: { $ne: "" } }, + groupBy: ['exit_node'], + select: [ + 'exit_node', + { $count: { field: '*', as: 'totalCount' } } + ] + }, 'spn-status-get-connections-count-per-exit-node') + })) + ) + .subscribe(data => { + this.spnConnChart = data.chart; + this.identities = data.identities.length; + + this.cdr.markForCheck(); + }) + } + + openOrLogin() { + if (this.activeRoute.snapshot.firstChild?.url[0]?.path === "spn") { + this.dialog.create(SPNAccountDetailsComponent, { + autoclose: true, + backdrop: 'light' + }) + + return + } + + this.router.navigate(['/spn']) + } + + setSPNEnabled(v: boolean) { + this.configService.save(`spn/enable`, v) + .subscribe(); + } +} diff --git a/desktop/angular/src/app/shared/status-pilot/index.ts b/desktop/angular/src/app/shared/status-pilot/index.ts new file mode 100644 index 00000000..1ec75e5b --- /dev/null +++ b/desktop/angular/src/app/shared/status-pilot/index.ts @@ -0,0 +1 @@ +export { StatusPilotComponent as PilotWidgetComponent } from "./pilot-widget"; diff --git a/desktop/angular/src/app/shared/status-pilot/pilot-widget.html b/desktop/angular/src/app/shared/status-pilot/pilot-widget.html new file mode 100644 index 00000000..52e41fbb --- /dev/null +++ b/desktop/angular/src/app/shared/status-pilot/pilot-widget.html @@ -0,0 +1,57 @@ + + + +
+ {{ activeLevelText }} + + + + + +
+ + +
+
+ + + + + + Auto Detect + + + + + + Manual + + + +
+ + +
+
+ + {{opt.displayText}} + + + {{opt.subText || ''}} + + +
+
+
+
+
diff --git a/desktop/angular/src/app/shared/status-pilot/pilot-widget.scss b/desktop/angular/src/app/shared/status-pilot/pilot-widget.scss new file mode 100644 index 00000000..3f1bcae7 --- /dev/null +++ b/desktop/angular/src/app/shared/status-pilot/pilot-widget.scss @@ -0,0 +1,208 @@ +:host { + overflow: visible; + position: relative; + display: flex; + justify-content: space-between; + background: none; + user-select: none; + align-items: center; + justify-content: space-evenly; + flex-direction: column; + + + @keyframes shield-pulse { + 0% { + transform: scale(.62); + opacity: 1; + } + + 100% { + transform: scale(1.1); + opacity: 0; + } + } + + @keyframes pulse-opacity { + 0% { + opacity: 0.1; + } + + 100% { + opacity: 1; + } + } +} + +.spn-status { + background-color: var(--info-blue); + border-radius: 100%; + display: flex; + align-items: center; + justify-content: center; + opacity: 1 !important; + padding: 0.2rem; + transform: scale(0.8); + position: absolute; + bottom: 42px; + right: 18px; + + &.connected { + background-color: theme('colors.info.blue'); + } + + &.connecting, + &.failed { + background-color: theme('colors.info.gray'); + } + + svg { + stroke: white; + } +} + +::ng-deep { + + .network-rating-level-list { + @apply p-3 rounded; + + flex-grow: 1; + + label { + opacity: 0.6; + font-size: 0.75rem; + font-weight: 500; + } + + div.rate-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 0 0.3rem 0; + margin-right: 0.11rem; + + .auto-detect { + height: 5px; + width: 5px; + margin-right: 10px; + margin-bottom: 1px; + background-color: #4995f3; + border-radius: 50%; + display: inline-block; + } + } + + &:not(.auto-pilot) { + div.level.selected { + div { + background-color: #292929; + } + + &:after { + transition: none; + opacity: 0 !important; + } + } + } + + div.level { + position: relative; + padding: 2px; + margin-top: 0.155rem; + cursor: pointer; + overflow: hidden; + z-index: 1; + + fa-icon[icon*="question-circle"] { + float: right; + } + + &:after { + transition: all cubic-bezier(0.19, 1, 0.82, 1) .2s; + @apply rounded; + content: ""; + filter: saturate(1.3); + background-image: linear-gradient(90deg, #226ab79f 0%, rgba(2, 0, 36, 0) 45%); + transform: translateX(100%); + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: -1; + opacity: 0; + } + + div { + background-color: #202020; + border-radius: 2px; + padding: 9px 17px 10px 18px; + display: block; + opacity: 0.55; + + span { + font-size: 0.725rem; + font-weight: 400; + } + + .situation { + @apply text-tertiary; + @apply ml-2; + font-size: 0.6rem; + font-weight: 600; + } + + svg.help { + width: 0.95rem; + float: right; + padding: 0; + margin: 0; + margin-top: 1.5px; + + .inner { + stroke: var(--text-secondary); + } + + &:hover, + &:active { + .inner { + stroke: var(--text-primary); + } + } + } + } + + &.selected { + div { + background-color: #292929; + opacity: 1; + } + } + + &.selected, + &.suggested { + &:after { + transform: translateX(0%); + opacity: 1; + } + + } + + &.suggested { + &:after { + animation: pulse-opacity 1s ease-in-out infinite alternate; + } + } + + &:hover, + &:active { + div { + opacity: 1; + + span { + opacity: 1; + } + } + } + } + } +} diff --git a/desktop/angular/src/app/shared/status-pilot/pilot-widget.ts b/desktop/angular/src/app/shared/status-pilot/pilot-widget.ts new file mode 100644 index 00000000..4fa01dd6 --- /dev/null +++ b/desktop/angular/src/app/shared/status-pilot/pilot-widget.ts @@ -0,0 +1,115 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { ConfigService, SecurityLevel } from '@safing/portmaster-api'; +import { combineLatest } from 'rxjs'; +import { FailureStatus, StatusService, Subsystem } from 'src/app/services'; + +interface SecurityOption { + level: SecurityLevel; + displayText: string; + class: string; + subText?: string; +} + +@Component({ + selector: 'app-status-pilot', + templateUrl: './pilot-widget.html', + styleUrls: [ + './pilot-widget.scss' + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class StatusPilotComponent implements OnInit { + activeLevel: SecurityLevel = SecurityLevel.Off; + selectedLevel: SecurityLevel = SecurityLevel.Off; + suggestedLevel: SecurityLevel = SecurityLevel.Off; + activeOption: SecurityOption | null = null; + selectedOption: SecurityOption | null = null; + + mode: 'auto' | 'manual' = 'auto'; + + get activeLevelText() { + return this.options.find(opt => opt.level === this.activeLevel)?.displayText || ''; + } + + readonly options: SecurityOption[] = [ + { + level: SecurityLevel.Normal, + displayText: 'Trusted', + class: 'low', + subText: 'Home Network' + }, + { + level: SecurityLevel.High, + displayText: 'Untrusted', + class: 'medium', + subText: 'Public Network' + }, + { + level: SecurityLevel.Extreme, + displayText: 'Danger', + class: 'high', + subText: 'Hacked Network' + }, + ]; + + get networkRatingEnabled$() { return this.configService.networkRatingEnabled$ } + + constructor( + private statusService: StatusService, + private changeDetectorRef: ChangeDetectorRef, + private configService: ConfigService, + ) { } + + ngOnInit() { + + combineLatest([ + this.statusService.status$, + this.statusService.watchSubsystems() + ]) + .subscribe(([status, subsystems]) => { + this.activeLevel = status.ActiveSecurityLevel; + this.selectedLevel = status.SelectedSecurityLevel; + this.suggestedLevel = status.ThreatMitigationLevel; + + if (this.selectedLevel === SecurityLevel.Off) { + this.mode = 'auto'; + } else { + this.mode = 'manual'; + } + + this.selectedOption = this.options.find(opt => opt.level === this.selectedLevel) || null; + this.activeOption = this.options.find(opt => opt.level === this.activeLevel) || null; + + // Find the highest failure-status reported by any module + // of any subsystem. + const failureStatus = subsystems.reduce((value: FailureStatus, system: Subsystem) => { + if (system.FailureStatus != 0) { + console.log(system); + } + return system.FailureStatus > value + ? system.FailureStatus + : value; + }, FailureStatus.Operational) + + this.changeDetectorRef.markForCheck(); + }); + } + + updateMode(mode: 'auto' | 'manual') { + this.mode = mode; + + if (mode === 'auto') { + this.selectLevel(SecurityLevel.Off); + } else { + this.selectLevel(this.activeLevel); + } + } + + selectLevel(level: SecurityLevel) { + if (this.mode === 'auto' && level !== SecurityLevel.Off) { + this.mode = 'manual'; + } + + this.statusService.selectLevel(level).subscribe(); + } +} diff --git a/desktop/angular/src/app/shared/text-placeholder/index.ts b/desktop/angular/src/app/shared/text-placeholder/index.ts new file mode 100644 index 00000000..8d04c94a --- /dev/null +++ b/desktop/angular/src/app/shared/text-placeholder/index.ts @@ -0,0 +1 @@ +export { PlaceholderComponent } from './placeholder'; diff --git a/desktop/angular/src/app/shared/text-placeholder/placeholder.scss b/desktop/angular/src/app/shared/text-placeholder/placeholder.scss new file mode 100644 index 00000000..88140deb --- /dev/null +++ b/desktop/angular/src/app/shared/text-placeholder/placeholder.scss @@ -0,0 +1,32 @@ +.text-placeholder { + display : inline-block; + height : 0.75rem; + position: relative; + + .background { + @apply rounded; + opacity : 0.8; + animation-duration : 6s; + animation-fill-mode : forwards; + animation-iteration-count: infinite; + animation-name : placeHolderShimmer; + animation-timing-function: linear; + background : linear-gradient(to right, #4b4b4b 8%, #5a5a5a 18%, #4b4b4b 33%); + position : absolute; + backface-visibility : hidden; + left : 0; + right : 0; + top : 2px; + bottom : 0; + } +} + +@keyframes placeHolderShimmer { + 0% { + background-position: 0px 0; + } + + 100% { + background-position: 100em 0; + } +} diff --git a/desktop/angular/src/app/shared/text-placeholder/placeholder.ts b/desktop/angular/src/app/shared/text-placeholder/placeholder.ts new file mode 100644 index 00000000..0b9797a3 --- /dev/null +++ b/desktop/angular/src/app/shared/text-placeholder/placeholder.ts @@ -0,0 +1,61 @@ +import { AfterContentChecked, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Input } from '@angular/core'; + +@Component({ + selector: 'app-text-placeholder', + template: ` + +
+
+ + `, + styleUrls: ['./placeholder.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PlaceholderComponent implements AfterContentChecked { + @Input() + set width(v: string | number) { + if (typeof v === 'number') { + this._width = `${v}px`; + return + } + + switch (v) { + case 'small': + this._width = '5rem'; + break; + case 'medium': + this._width = '10rem'; + break; + case 'large': + this._width = '15rem'; + break + default: + this._width = v; + } + } + get width() { return this._width; } + private _width: string = '10rem'; + + @Input() + mode: 'auto' | 'input' = 'auto'; + + @Input() + loading = true; + + constructor( + private elementRef: ElementRef, + private changeDetector: ChangeDetectorRef, + ) { } + + ngAfterContentChecked() { + if (this.mode === 'input') { + return; + } + + const show = this.elementRef.nativeElement.innerText === ''; + if (this.loading != show) { + this.loading = show; + this.changeDetector.detectChanges(); + } + } +} diff --git a/desktop/angular/src/app/shared/utils.ts b/desktop/angular/src/app/shared/utils.ts new file mode 100644 index 00000000..c36caa07 --- /dev/null +++ b/desktop/angular/src/app/shared/utils.ts @@ -0,0 +1,76 @@ +import { parse } from 'psl'; + +export interface ParsedDomain { + domain: string | null; + subdomain: string | null; +} +export function parseDomain(scope: string): ParsedDomain { + // Due to https://github.com/lupomontero/psl/issues/185 + // parse will throw an error for service-discovery lookups + // so make sure we split them apart. + const domainParts = scope.split(".") + const lastUnderscorePart = domainParts.length - [...domainParts].reverse().findIndex(dom => dom.startsWith("_")) + let result: ParsedDomain = { + domain: null, + subdomain: null, + } + + let cleanedDomain = scope; + let removedPrefix = ''; + if (lastUnderscorePart <= domainParts.length) { + removedPrefix = domainParts.slice(0, lastUnderscorePart).join('.') + cleanedDomain = domainParts.slice(lastUnderscorePart).join('.') + } + + const parsed = parse(cleanedDomain); + if ('listed' in parsed) { + result.domain = parsed.domain || scope; + result.subdomain = removedPrefix; + if (!!parsed.subdomain) { + if (removedPrefix != '') { + result.subdomain += '.'; + } + result.subdomain += parsed.subdomain; + } + } + + return result +} + +export function binarySearch(array: T[], what: T, sortFunc: (a: T, b: T) => number): number { + let l = 0; + let h = array.length - 1; + let currentIndex: number = 0; + + while (l <= h) { + currentIndex = (l + h) >>> 1; + const result = sortFunc(what, array[currentIndex]); + if (result < 0) { + l = currentIndex + 1; + } else if (result > 0) { + h = currentIndex - 1; + } else { + return currentIndex; + } + } + return ~currentIndex; +} + +export function binaryInsert(array: T[], what: T, sortFunc: (a: T, b: T) => number, duplicate = false): number { + let idx = binarySearch(array, what, sortFunc); + if (idx >= 0) { + if (!duplicate) { + return idx; + } + } else { + // if `what` is not part of `array` than index is the bitwise complement + // of the expected index in array. + idx = ~idx; + } + array.splice(idx, 0, what) + return idx; +} + +export function objKeys(obj: T): (keyof T)[] { + return Object.keys(obj) as any; +} diff --git a/desktop/angular/src/assets b/desktop/angular/src/assets new file mode 120000 index 00000000..ec2e4be2 --- /dev/null +++ b/desktop/angular/src/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/desktop/angular/src/electron-app.d.ts b/desktop/angular/src/electron-app.d.ts new file mode 100644 index 00000000..febea42b --- /dev/null +++ b/desktop/angular/src/electron-app.d.ts @@ -0,0 +1,41 @@ +declare global { + interface Window { + app: AppAPI; + } +} + +export class AppAPI { + /** Returns the current platform */ + getPlatform(): Promise; + + /** The installation directory of portmaster. */ + getInstallDir(): Promise; + + /** + * Open an URL or path using an external application. + * + * @param pathOrUrl The path or URL to open. + */ + openExternal(pathOrUrl: string): Promise; + + /** + * Creates a new URL with the file:// scheme. Works + * on any platform. + * + * @param path The path for the file URL. + */ + createFileURL(path: string): Promise; + + /** + * Returns a dataURL for the icon that is used to represent + * the path on this platform. + * This method only works on windows for now. On all other + * platforms an empty string is returned. + * + * @param path The path the the binary + */ + getFileIcon(path: string): Promise; + + /** Exit the electron appliction. */ + exitApp(): Promise; +} diff --git a/desktop/angular/src/environments/environment.prod.ts b/desktop/angular/src/environments/environment.prod.ts new file mode 100644 index 00000000..71b53cec --- /dev/null +++ b/desktop/angular/src/environments/environment.prod.ts @@ -0,0 +1,22 @@ +/* +export const environment = new class { + readonly supportHub = "https://support.safing.io" + readonly production = true; + + get httpAPI() { + return `http://${window.location.host}/api` + } + + get portAPI() { + const result = `ws://${window.location.host}/api/database/v1`; + return result; + } +} +*/ + +export const environment = { + production: false, + portAPI: "ws://127.0.0.1:817/api/database/v1", + httpAPI: "http://127.0.0.1:817/api", + supportHub: "https://support.safing.io" +}; diff --git a/desktop/angular/src/environments/environment.ts b/desktop/angular/src/environments/environment.ts new file mode 100644 index 00000000..5ef6df25 --- /dev/null +++ b/desktop/angular/src/environments/environment.ts @@ -0,0 +1,19 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + portAPI: "ws://127.0.0.1:817/api/database/v1", + httpAPI: "http://127.0.0.1:817/api", + supportHub: "https://support.safing.io" +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/desktop/angular/src/i18n/helptexts.yaml b/desktop/angular/src/i18n/helptexts.yaml new file mode 100644 index 00000000..e00b6023 --- /dev/null +++ b/desktop/angular/src/i18n/helptexts.yaml @@ -0,0 +1,370 @@ +########### +### Example + +myKey: + title: Tipup Example + content: | + This is the Markdown formatted content. + + This is a super cool, new feature that you will love! + It even supports markdown features like: + - order lists + - with multiple items + + And :rocket: emojis + + ### :tada: :facepalm: + url: https://docs.safing.io/?source=Portmaster + urlText: Show me! + nextKey: navMonitor + +############## +### Navigation + +introTipup: + title: Hey there! + content: | + Thanks for installing the Portmaster. + +intro: + title: Portmaster Tips + content: | + Open tips to learn how the Portmaster work. + + Tips like this one are found throughout the Portmaster. With some tips you can tour an element or a feature, like this: + nextKey: navShield + +navShield: + title: Status Shield & Dashboard + content: | + The shield gives you a high level overview of Portmaster's status. If turns any other color than green, look for a notification that tells you what is going on. + + __Click the shield in order to open the dashboard.__ + nextKey: navMonitor + +navMonitor: + title: Network Monitor + content: | + Oversee and investigate everything happening on your device. + nextKey: navApps + buttons: + - name: Take the tour + action: + Type: open-page + Payload: monitor + nextKey: networkMonitor + +navApps: + title: Per-App Settings + content: | + Configure per-app settings which override the global default. + nextKey: navMap + buttons: + - name: Take the tour + action: + Type: open-page + Payload: apps + nextKey: appsTitle + +navMap: + title: SPN Map + content: | + View the SPN map and see how your connections are routed. + nextKey: navSettings + +navSettings: + title: Global Settings + content: | + Configure global Portmaster settings. + nextKey: navSupport + buttons: + - name: Take the tour + action: + Type: open-page + Payload: settings + nextKey: globalSettings + +navSupport: + title: Get Help + content: | + Report a bug, contact support or view the extended Portmaster docs. + nextKey: navTools + buttons: + - name: Open Page + action: + Type: open-page + Payload: support + +navTools: + title: Version and Tools + content: | + View the Portmaster's version and use special actions and tools. + nextKey: navPower + +navPower: + title: Shutdown and Restart + content: | + Shutdown or Restart Portmaster. + nextKey: uiMode + +uiMode: + title: UI Mode + content: | + Quickly change the amount of settings and information shown. + + Hidden settings are still in effect. After closing the User Interface it changes back to the default. + buttons: + - name: Change Default UI Mode + action: + Type: "open-setting" + Payload: + Key: "core/expertiseLevel" + +############ +### Sidedash + +pilot-widget: + title: Portmaster Status + content: | + This shield shows you the current state of the Portmaster: + + - 🟢 all is well + - 🟡 something is off, please investigate + - 🔴 dangerous condition, respond immediately + + This color code is also displayed as part of the icon in the system tray. + +pilot-widget-NetworkRating: + title: Network Rating + content: | + Control your privacy even when connecting to new networks. + + In the Portmaster you configure settings to be active in one environment but not in the other, like allowing sensitive connections at home but not at the public library. + + The only thing you have to do is to change the network rating whenever you connect to a different network. + nextKey: pilot-widget-NetworkRating-Trusted + +pilot-widget-NetworkRating-Trusted: + title: "Network Rating: Trusted" + content: | + You trust the current network to be secure and protect you. + + Examples: + - your home network + - network of a trusted friends + nextKey: pilot-widget-NetworkRating-Untrusted + +pilot-widget-NetworkRating-Untrusted: + title: "Network Rating: Untrusted" + content: | + You do not trust the current network and question if it will keep you secure and private. + + Examples: + - public WiFi of a coffeeshop, a library, a train, a hotel, ... + - network of a non-tech-savvy relative + nextKey: pilot-widget-NetworkRating-Danger + +pilot-widget-NetworkRating-Danger: + title: "Network Rating: Danger" + content: | + You think that the current network is hacked or otherwise hostile towards you. + + Examples: + - something suspicious is going on in your home network + + _Note: In the "Danger" rating the Portmaster will become very protective. This might break functionality of apps or render them useless._ + +broadcast-info: + title: Broadcast Notifications + content: | + Broadcast Notifications are public messages downloaded by the Portmaster when checking for updates. + + The Portmaster then locally decides which messages should be displayed. + url: https://github.com/safing/portmaster/issues/703 + urlText: Learn More + +# TODO +# prompt-widget: +# title: Prompts +# content: | +# This is where you can more easily control the +# connections for the specific app for the time being. + +# How to use? In App settings, search for "Default Action" +# and set it to "Prompt". + +# Note: Don't set the "Prompt" setting in your browser, +# you will be spammed. You have been warned. +# nextKey: notification-widget + +# TODO +# notification-widget: +# title: Notifications +# content: | +# This informs you with what's going on with portmaster. +# Ie, Updates, Errors, Warring etc + +############# +### Dashboard + +dashboardIntro: + title: Dashboard + content: | + The Dashboard gives you a first overview of Portmaster's active features and what is happening on your device. + + Unless noted otherwise, all graphs and statistics shown are based on what Portmaster has seen in the last 10 minutes and are refreshed every 10 seconds. + +######################## +### Network Monitor Page + +networkMonitor: + title: Network Activity + content: | + Oversee everything happening on your device. + + Look at all network connections of all applications and processes that were active in the last 10 minutes. Click on any app or process to investigate further. + +# TODO: Wait for overview to be more useful. +# networkMonitor-Overview: +# title: Monitor Overview +# content: | +# This is just a placeholder for the meantime, but this is +# just the Network Monitor with 3 stats on it. + +# TODO: Wait for revamp of status indication. +# networkMonitor-App: +# title: App Activity +# content: | +# There are 3 colours. Ie, Green, Red, Gray. + +# Allowed(Green) +# The colour green shows that all the connections are allowed in +# the app. + +# Blocked(Red) +# The colour red shows that all the connections are blocked in +# the app. + +# Allowed/Blocked(Gray) +# The colour gray shows that some connections are +# allowed and blocked in the app. + +networkMonitor-App-Focus-connection-history: + title: Network Activity + content: | + Monitor connections as they happen. Click on any connection to view details and to take action. +

+ + + 2k+ + + + + + Status Summary +

+ Grouped connections have a colored bar showing the total amount of connections, + as well as the percentage between allowed (green) and blocked/failed connections (grey). +

+ An individual connection has three states:
+ Allowed
+ Blocked
+ Failed
+ + If the circle is full, your _current_ settings allowed or blocked the connection.
+ If the circle is empty, _previous_ settings allowed or blocked the connection. + Your current settings could decide differently. + +######################## +### Global Settings Page + +globalSettings: + title: Global Settings + content: | + Here you can set system-wide preferences and configure default rules for all your apps and connections. + + It is easy to create a stricter global ruleset and then create exceptions in the app settings, which override the global default. + +######################### +### Per-App Settings Page + +appsTitle: + title: Application Overview + content: | + All applications or processes that the Portmaster saw being active on the network are listed and can be configured here. + + Apps are categorized and only appear once: + + - **Active:** apps that are currently active and visible in the Network Monitor + - **Recently Used:** apps that were active some time within the last week + - **Recently Edited:** apps whose settings were edited within the last week + - **Other:** all other apps + +appSettings: + title: App Settings + content: | + Here you can configure app-specific settings which override the global settings. + + It is easy to create a stricter global ruleset and then create exceptions in the app settings, which override the global default. + nextKey: appSettings-Filter + +appSettings-Filter: + title: Display Mode + content: | + Quickly change what settings are displayed: + + **View Active:**
+ Only show app-specific settings which override the global default. + + **View All:**
+ Show all settings. App-specific settings which override the global default are highlighted. + +appSettings-QuickSettings: + title: Quickly Change the Most Important Settings + content: | + __Block Connections__ + + Set the default action for when nothing else allows or blocks an outgoing connection. + + When other settings might overwrite this, a yellow dot next to the toggle will inform you of possible exceptions. + + __SPN__ + + Quickly enable or disable SPN for this app. + + When other settings might overwrite this, a yellow dot next to the toggle will inform you of possible exceptions. + + __Keep History__ + + Save connections in a database (on disk) in order to view and search them later. + + Changes might take a couple minutes to apply to all connections. + +######################### +### Support Page + +support-page-related-issues: + title: Local Issue Search + content: | + Public issues are only searched for locally so no data leaves your device until you decide so. + + The public GitHub issues are downloaded via our support system to prevent exposure to GitHub. + +######################### +### Configuration Options + +spn: + title: Safing Privacy Network + content: | + The Safing Privacy Network (SPN) is a Portmaster Add-On that protects your identity + and Internet traffic from prying eyes. It spreads your connections over multiple server, + letting you access the Internet from many places at once in order to effectively hide + your tracks. + url: https://safing.io/spn/?source=Portmaster + urlText: Learn More + +########################### +# Process Matching and Fingerprints +process-tags: + title: Process Tags + content: Tags holds special metadata of processes and are gathered by Portmaster. You can use these tags in fingerprints to better match processes, which would otherwise be a lot more difficult or impossible to match correctly. diff --git a/desktop/angular/src/i18n/helptexts.yaml.d.ts b/desktop/angular/src/i18n/helptexts.yaml.d.ts new file mode 100644 index 00000000..979498e3 --- /dev/null +++ b/desktop/angular/src/i18n/helptexts.yaml.d.ts @@ -0,0 +1,24 @@ + +declare module 'js-yaml-loader!*' { + import { Action } from "src/app/services/notifications.types"; + export interface Button { + name: string; + action: Action; + nextKey?: string; + } + + export interface TipUp { + title: string; + content: string; + url?: string; + urlText?: string; + buttons?: Button[]; + nextKey?: string; + } + export interface HelpTexts { + [key: string]: TipUp; + } + + const content: HelpTexts; + export default content; +} diff --git a/desktop/angular/src/index.html b/desktop/angular/src/index.html new file mode 100644 index 00000000..8912951b --- /dev/null +++ b/desktop/angular/src/index.html @@ -0,0 +1,34 @@ + + + + + + Portmaster + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/angular/src/main.ts b/desktop/angular/src/main.ts new file mode 100644 index 00000000..2b10a238 --- /dev/null +++ b/desktop/angular/src/main.ts @@ -0,0 +1,94 @@ +import { enableProdMode, importProvidersFrom } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; +import { INTEGRATION_SERVICE, integrationServiceFactory } from './app/integration'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { PromptWidgetComponent } from './app/shared/prompt-list'; +import { PromptEntryPointComponent } from './app/prompt-entrypoint/prompt-entrypoint'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { PortmasterAPIModule } from '@safing/portmaster-api'; +import { NotificationsService } from './app/services'; +import { TauriIntegrationService } from './app/integration/taur-app'; + +if (environment.production) { + enableProdMode(); +} + +if (typeof (CSS as any)['registerProperty'] === 'function') { + (CSS as any).registerProperty({ + name: '--lock-color', + syntax: '*', + inherits: true, + initialValue: '10, 10, 10' + }) +} + +function handleExternalResources(e: Event) { + // get click target + let target: HTMLElement | null = e.target as HTMLElement; + // traverse until we reach an a tag + while (!!target && target.tagName !== "A") { + target = target.parentElement; + } + + if (!!target) { + let href = target.getAttribute("href"); + if (href?.startsWith("blob")) { + return + } + + if (!!href && !href.includes(location.hostname)) { + e.preventDefault(); + + integrationServiceFactory().openExternal(href); + } + } +} + +if (document.addEventListener) { + document.addEventListener("click", handleExternalResources); +} + +// load the font file but make sure to use the slimfix version +// windows. +{ + // we cannot use document.writeXX here as it's not allowed to + // write to Document from an async loaded script. + + let linkTag = document.createElement("link"); + linkTag.rel = "stylesheet"; + linkTag.href = "/assets/vendor/fonts/roboto.css"; + if (navigator.platform.startsWith("Win")) { + linkTag.href = "/assets/vendor/fonts/roboto-slimfix.css" + } + + document.head.appendChild(linkTag); +} + + +if (location.pathname !== "/prompt") { + // bootstrap our normal application + platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); + +} else { + // bootstrap the prompt interface + bootstrapApplication(PromptEntryPointComponent, { + providers: [ + provideHttpClient(), + importProvidersFrom(PortmasterAPIModule.forRoot({ + websocketAPI: "ws://localhost:817/api/database/v1", + httpAPI: "http://localhost:817/api" + })), + NotificationsService, + { + provide: INTEGRATION_SERVICE, + useClass: TauriIntegrationService + } + ], + }) +} + diff --git a/desktop/angular/src/polyfills.ts b/desktop/angular/src/polyfills.ts new file mode 100644 index 00000000..576bf9d7 --- /dev/null +++ b/desktop/angular/src/polyfills.ts @@ -0,0 +1,57 @@ +/*************************************************************************************************** + * Load `$localize` onto the global scope - used if i18n tags appear in Angular templates. + */ +import '@angular/localize/init'; +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/desktop/angular/src/styles.scss b/desktop/angular/src/styles.scss new file mode 100644 index 00000000..52a47745 --- /dev/null +++ b/desktop/angular/src/styles.scss @@ -0,0 +1,120 @@ +// +// Import our complete theme, order is important! +// +@import 'theme/_colors.scss'; +@import 'theme/_tailwind.scss'; +@import '@angular/cdk/overlay-prebuilt'; +@import 'theme/_button.scss'; +@import 'theme/_drag-n-drop.scss'; +@import 'theme/_inputs.scss'; +@import 'theme/_scroll.scss'; +@import 'theme/_search.scss'; +@import 'theme/_trust-level.scss'; +@import 'theme/_verdict.scss'; +@import 'theme/_typography.scss'; +@import 'theme/_markdown.scss'; +@import 'theme/_card.scss'; +@import 'theme/_breadcrumbs.scss'; +@import 'theme/_dialog.scss'; +@import 'theme/_table.scss'; +@import 'theme/_pill.scss'; + +@import 'safing/ui/theming'; + +*[routerlink] { + cursor: pointer; +} + +.form-field { + display: flex; + justify-content: flext-start; + align-items: center; + + *:not(:last-child) { + @apply mr-1; + } +} + +.sidebar { + @apply bg-background; + height: 100vh; + flex-shrink: 0; + flex-grow: 0; + @apply px-2; + display: flex; + flex-direction: column; + + &.no-scroll { + @apply px-0; + } +} + +.main { + .content { + flex-grow: 1; + @apply pl-12; + @apply pr-16; + @apply mr-4; + overflow: auto; + } + + .header { + display: flex; + width: 100%; + @apply pl-12; + @apply pr-5; + @apply mb-2; + align-items: center; + height: 3rem; + flex-shrink: 0; + + &:first-of-type { + @apply mt-2; + } + + >* { + flex-grow: 1; + margin: 0; + } + + >app-expertise { + flex-grow: 0; + flex-shrink: 0; + } + } +} + +.tableFixHead { + overflow-y: auto; +} + +.tableFixHead thead th { + position: sticky; + top: 0; +} + + +fa-icon.tipup, +fa-icon[icon="question-circle"], +fa-icon[icon="question"] { + max-width: 10px; + max-height: 10px; + opacity: 0.8; + display: inline-block; + font-size: 0.75rem; + color: rgb(250 250 250 / 55%); + margin-left: 3px; + + &:hover { + opacity: unset; + } +} + +.tipup-preview { + transition: all .25s ease-in-out !important; + opacity: 0 !important; + + &.visible { + opacity: 1 !important; + } +} diff --git a/desktop/angular/src/test.ts b/desktop/angular/src/test.ts new file mode 100644 index 00000000..06aa8e41 --- /dev/null +++ b/desktop/angular/src/test.ts @@ -0,0 +1,14 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/dist/zone-testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); diff --git a/desktop/angular/src/theme.less b/desktop/angular/src/theme.less new file mode 100644 index 00000000..64154716 --- /dev/null +++ b/desktop/angular/src/theme.less @@ -0,0 +1,4 @@ + +// Custom Theming for NG-ZORRO +// For more information: https://ng.ant.design/docs/customize-theme/en +@import "../node_modules/ng-zorro-antd/ng-zorro-antd.dark.less"; diff --git a/desktop/angular/src/theme/_breadcrumbs.scss b/desktop/angular/src/theme/_breadcrumbs.scss new file mode 100644 index 00000000..42df118b --- /dev/null +++ b/desktop/angular/src/theme/_breadcrumbs.scss @@ -0,0 +1,20 @@ +h4.breadcrumbs { + * { + margin-left : 0.125rem; + margin-right: 0.125rem; + } + + span { + outline: none; + @apply text-secondary; + + &:hover { + @apply text-primary; + text-decoration: underline; + } + + &:last-of-type { + @apply text-primary; + } + } +} diff --git a/desktop/angular/src/theme/_button.scss b/desktop/angular/src/theme/_button.scss new file mode 100644 index 00000000..39012ee7 --- /dev/null +++ b/desktop/angular/src/theme/_button.scss @@ -0,0 +1,58 @@ +@layer components { + button { + @apply text-xs; + @apply bg-buttons-dark; + @apply p-1; + @apply px-4; + @apply capitalize; + @apply rounded-sm; + @apply font-medium; + user-select: none; + outline: none; + cursor: pointer; + font-size: 0.7rem; + + &.btn-outline { + background: transparent; + opacity: 0.6; + } + + &:hover { + &:not(.outline):not(.bg-blue) { + @apply bg-buttons-light; + } + + opacity: 1; + } + + &:disabled { + @apply cursor-default; + opacity: 0.3; + + &:not(.outline):hover { + @apply bg-buttons-dark; + } + } + + &:active { + @apply bg-buttons-dark; + } + + &:hover, + &:focus, + &:active { + outline: none; + } + } + + .info-circle { + width: 18px; + height: 18px; + display: flex; + justify-content: center; + align-items: center; + font-size: 0.8em; + @apply rounded-full; + @apply bg-buttons-dark; + } +} diff --git a/desktop/angular/src/theme/_card.scss b/desktop/angular/src/theme/_card.scss new file mode 100644 index 00000000..284a4bbe --- /dev/null +++ b/desktop/angular/src/theme/_card.scss @@ -0,0 +1,110 @@ +.card-header { + display : flex; + align-items : center; + cursor : pointer; + outline : none; + justify-content: space-between; + @apply text-xs; + @apply font-medium; + margin-top : 5px; + padding-top : 0.65rem; + padding-bottom : 0.65rem; + padding-left : 0.65rem; + padding-right : 0.65rem; + border-top-left-radius : 4px; + border-top-right-radius: 4px; + background-color : #202020e0; + + &:not(.open) { + border-radius: 4px; + } + + &>*:not(:last-child) { + @apply mr-1; + } + + &>app-icon:not(:last-child) { + @apply mr-2; + } + + &:hover { + background-color: #292929b0; + } + + &.active { + background-color: #303030; + + app-count-indicator { + background-color: #474747; + + div.state { + background-color: #5c5c5c; + + } + } + } + + &>app-icon { + --app-icon-size: 22px; + } + + .card-title { + flex-grow : 1; + overflow : hidden; + white-space : nowrap; + text-overflow: ellipsis; + font-size : 0.7rem; + font-weight : 600; + color : #cacaca; + margin-left : 3px; + + .card-sub-title { + display : block; + font-size : 0.8em; + margin-top: -3px; + @apply text-tertiary; + text-overflow: ellipsis; + overflow : hidden; + } + } + + .card-actions { + @apply mr-2; + + span { + display : inline-block; + text-align: center; + min-width : 5rem; + @apply px-2; + @apply rounded; + @apply text-xs; + + padding-top : 0.1rem; + padding-bottom: 0.1rem; + + // TODO(ppacher): this is actually a "toggle-switch" / radio-button + // component. make it one. + &.selected { + @apply bg-buttons-dark; + } + + &:hover { + @apply bg-buttons-light; + } + } + } +} + +.card-content { + @apply bg-cards-secondary; + @apply rounded-b; + + @apply py-2; + @apply px-4; + @apply mb-2; + + display : flex; + flex-direction : column; + flex : 1 0; + justify-content: space-between; +} diff --git a/desktop/angular/src/theme/_colors.scss b/desktop/angular/src/theme/_colors.scss new file mode 100644 index 00000000..65310698 --- /dev/null +++ b/desktop/angular/src/theme/_colors.scss @@ -0,0 +1,46 @@ +/** + * For debugging purposes, we define all our colors as + * CSS3 variables and make tailwind put a reference to those + * variables. This way we will see the variable name in the + * developer-tools instead of the hex/rgba values. + * + * You're welcome 🚀 + */ +:root { + --background: #121213; + + --text-primary : #ffffff; + --text-secondary: #ababab; + --text-tertiary : #888888; + + --cards-primary : #222222; + --cards-secondary : #1b1b1b; + --cards-secondary-rgb: 27, 27, 27; + --cards-tertiary : #2c2c2c; + + --button-icon : #ababab; + --button-dark : #343434; + --button-light: #474747; + + --info-green : #3df57f; + --info-red : #d12e2e; + --info-gray : #ababab; + --info-blue : #4e97fa; + --info-yellow : #e9d31d; + --info-yellow-rgb: 233, 211, 29; + + --protection-ok-primary : rgb(29, 233, 102); + --protection-ok-secondary: rgb(24, 130, 61); + --protection-ok-tertiary : rgb(20, 61, 36); + + --protection-warn-primary : rgb(233, 216, 29); + --protection-warn-secondary: rgb(130, 121, 24); + --protection-warn-tertiary : rgb(61, 58, 20); + + --protection-fail-primary : rgb(224, 29, 29); + --protection-fail-secondary: rgb(129, 24, 24); + --protection-fail-tertiary : rgb(61, 20, 20); + + --portmaster-plus: #2fcfae; + --portmaster-pro: #029ad0; +} diff --git a/desktop/angular/src/theme/_dialog.scss b/desktop/angular/src/theme/_dialog.scss new file mode 100644 index 00000000..c4eaaabe --- /dev/null +++ b/desktop/angular/src/theme/_dialog.scss @@ -0,0 +1,9 @@ +.dialog-screen-backdrop { + backdrop-filter : blur(10px); + background-color: rgba(#000000, 0.7); +} + +.dialog-screen-backdrop-light { + backdrop-filter : blur(3px); + background-color: rgba(#000000, 0.4); +} diff --git a/desktop/angular/src/theme/_drag-n-drop.scss b/desktop/angular/src/theme/_drag-n-drop.scss new file mode 100644 index 00000000..e6c69add --- /dev/null +++ b/desktop/angular/src/theme/_drag-n-drop.scss @@ -0,0 +1,46 @@ +.cdk-drag { + .widget { + user-select: none; + + fa-icon { + opacity: 1; + } + } +} + +.cdk-drag-placeholder { + user-select: none; + position: relative; + opacity: 0.5; + box-sizing: border-box; + cursor: grabbing !important; + @apply border-2; + @apply rounded; + @apply border-dashed; + border-color: #292929; +} + +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); + user-select: none; +} + +.cdk-drag-preview { + user-select: none; + box-sizing: border-box; + cursor: grabbing !important; + + @apply rounded; + @apply border-2; + @apply border-dashed; + border-color: #292929; + @apply text-primary; +} + +.cdk-drag-handle { + cursor: grab !important; +} + +.document-grabbing { + cursor: grabbing !important; +} diff --git a/desktop/angular/src/theme/_inputs.scss b/desktop/angular/src/theme/_inputs.scss new file mode 100644 index 00000000..a7baf154 --- /dev/null +++ b/desktop/angular/src/theme/_inputs.scss @@ -0,0 +1,35 @@ +input:not([type="checkbox"]), +textarea, +select { + @apply outline-none w-full block; + @apply bg-gray-300 rounded; + @apply text-xs text-primary; + @apply border border-gray-300; + @apply rounded-sm font-medium; + @apply p-1.5; + + transition: border cubic-bezier(0.175, 0.885, 0.32, 1.275) .3s; + + &::placeholder { + @apply text-secondary text-xxs; + } + + &:active, + &:focus { + @apply text-primary; + @apply bg-gray-500 border-gray-400 bg-opacity-75 border-opacity-75; + + &::placeholder { + @apply text-tertiary; + } + } +} + + +input, +textarea, +select { + .ng-invalid { + @apply border-red; + } +} diff --git a/desktop/angular/src/theme/_markdown.scss b/desktop/angular/src/theme/_markdown.scss new file mode 100644 index 00000000..4bf2fa09 --- /dev/null +++ b/desktop/angular/src/theme/_markdown.scss @@ -0,0 +1,455 @@ +// Mostly taken from https://github.com/sindresorhus/github-markdown-css/blob/gh-pages/license + +markdown { + width: 100%; + @apply p-2; + color: white !important; + @apply font-normal; + + details { + display: block; + } + + summary { + display: list-item; + } + + a { + background-color: initial; + } + + a:active, + a:hover { + outline-width: 0; + } + + strong { + font-weight: inherit; + font-weight: bolder; + } + + h1 { + font-size: 2rem; + margin: .67rem 0; + } + + img { + border-style: none; + } + + code, + kbd, + pre { + font-family: monospace, monospace; + font-size: 1rem; + } + + hr { + box-sizing: initial; + height: 0; + overflow: visible; + } + + input { + font: inherit; + margin: 0; + } + + input { + overflow: visible; + } + + [type=checkbox] { + box-sizing: border-box; + padding: 0; + } + + * { + box-sizing: border-box; + } + + input { + font-family: inherit; + font-size: inherit; + line-height: inherit; + } + + a { + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + + strong { + font-weight: 600; + } + + hr { + height: 0; + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: 1px solid #dfe2e5; + } + + hr:after, + hr:before { + display: table; + content: ""; + } + + hr:after { + clear: both; + } + + table { + border-spacing: 0; + border-collapse: collapse; + } + + td, + th { + padding: 0; + } + + details summary { + cursor: pointer; + } + + kbd { + display: inline-block; + padding: 3px 5px; + font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + line-height: 10px; + vertical-align: middle; + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #d1d5da; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin-top: 0; + margin-bottom: 0; + } + + h1 { + font-size: 32px; + } + + h1, + h2 { + font-weight: 600; + } + + h2 { + font-size: 24px; + } + + h3 { + font-size: 20px; + } + + h3, + h4 { + font-weight: 600; + } + + h4 { + font-size: 16px; + } + + h5 { + font-size: 14px; + } + + h5, + h6 { + font-weight: 600; + } + + h6 { + font-size: 12px; + } + + p { + margin-top: 0; + margin-bottom: 10px; + } + + blockquote { + margin: 0; + } + + ol, + ul { + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + } + + ol { + list-style-type: decimal; + } + + ul { + list-style-type: circle; + } + + ol ol, + ul ol { + list-style-type: lower-roman; + } + + ol ol ol, + ol ul ol, + ul ol ol, + ul ul ol { + list-style-type: lower-alpha; + } + + dd { + margin-left: 0; + } + + code, + pre { + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + font-size: 12px; + } + + pre { + margin-top: 0; + margin-bottom: 0; + } + + input::-webkit-inner-spin-button, + input::-webkit-outer-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; + } + + a:not([href]) { + color: inherit; + text-decoration: none; + } + + blockquote, + details, + dl, + ol, + p, + pre, + table, + ul { + margin-top: 0; + // be carefully when ever changing this! + margin-bottom: 16px; + } + + hr { + height: .25rem; + padding: 0; + margin: 24px 0; + background-color: #e1e4e8; + border: 0; + } + + blockquote { + padding: 0 1rem; + border-left: .25rem solid #dfe2e5; + } + + blockquote>:first-child { + margin-top: 0; + } + + blockquote>:last-child { + margin-bottom: 0; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; + } + + h1 { + font-size: 2rem; + } + + h1, + h2 { + padding-bottom: .3rem; + border-bottom: 1px solid #eaecef; + } + + h2 { + font-size: 1.5rem; + } + + h3 { + font-size: 1.25rem; + } + + h4 { + font-size: 1rem; + } + + h5 { + font-size: .875rem; + } + + h6 { + font-size: .85rem; + } + + ol, + ul { + padding-left: 2rem; + } + + ol ol, + ol ul, + ul ol, + ul ul { + margin-top: 0; + margin-bottom: 0; + } + + li { + word-wrap: break-all; + } + + li>p { + margin-top: 16px; + } + + li+li { + margin-top: .25rem; + } + + dl { + padding: 0; + } + + dl dt { + padding: 0; + margin-top: 16px; + font-size: 1rem; + font-style: italic; + font-weight: 600; + } + + dl dd { + padding: 0 16px; + margin-bottom: 16px; + } + + table { + display: block; + width: 100%; + overflow: auto; + } + + table th { + font-weight: 600; + } + + table td, + table th { + padding: 6px 13px; + border: 1px solid #dfe2e5; + } + + table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; + } + + table tr:nth-child(2n) { + background-color: #f6f8fa; + } + + img { + max-width: 100%; + box-sizing: initial; + background-color: #fff; + } + + img[align=right] { + padding-left: 20px; + } + + img[align=left] { + padding-right: 20px; + } + + code { + padding: .2rem .4rem; + margin: 0; + font-size: 95%; + background-color: rgba(27, 31, 35, .05); + border-radius: 3px; + } + + pre { + word-wrap: normal; + } + + pre>code { + padding: 0; + margin: 0; + font-size: 100%; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; + } + + .highlight { + margin-bottom: 16px; + } + + .highlight pre { + margin-bottom: 0; + word-break: normal; + } + + .highlight pre, + pre { + padding: 16px; + overflow: auto; + font-size: 90%; + line-height: 1.45; + background-color: #f6f8fa; + border-radius: 3px; + } + + pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: initial; + border: 0; + } +} diff --git a/desktop/angular/src/theme/_pill.scss b/desktop/angular/src/theme/_pill.scss new file mode 100644 index 00000000..e85d9ac3 --- /dev/null +++ b/desktop/angular/src/theme/_pill.scss @@ -0,0 +1,7 @@ +@import 'mixins/_pill.scss'; + +.pill-container { + @include pill-container; + @apply pl-2; + @apply bg-buttons-dark; +} diff --git a/desktop/angular/src/theme/_scroll.scss b/desktop/angular/src/theme/_scroll.scss new file mode 100644 index 00000000..36ea80b0 --- /dev/null +++ b/desktop/angular/src/theme/_scroll.scss @@ -0,0 +1,28 @@ +html, +body { + scroll-behavior: smooth; +} + +::-webkit-scrollbar { + @apply bg-buttons-dark; + width: 4px; +} + +::-webkit-scrollbar-thumb { + @apply bg-buttons-light; + @apply rounded; + cursor: pointer; +} + +.no-scroll { + overflow: hidden; +} + +.scrollable { + width : 100%; + max-height: 100%; + overflow : auto; + overflow-x: hidden; + flex-grow : 1; + @apply px-3; +} diff --git a/desktop/angular/src/theme/_search.scss b/desktop/angular/src/theme/_search.scss new file mode 100644 index 00000000..d950cafd --- /dev/null +++ b/desktop/angular/src/theme/_search.scss @@ -0,0 +1,10 @@ +em.search-result { + @apply text-background; + @apply bg-yellow; + @apply border; + @apply border-yellow; + @apply rounded-sm; + + text-decoration: none; + font-style: inherit; +} diff --git a/desktop/angular/src/theme/_table.scss b/desktop/angular/src/theme/_table.scss new file mode 100644 index 00000000..035b5e17 --- /dev/null +++ b/desktop/angular/src/theme/_table.scss @@ -0,0 +1,41 @@ +table:not(.custom) { + width: 100%; + + th, + tr, + td { + @apply text-xs; + } + + th { + text-align: left; + @apply text-secondary; + z-index: 1; + } + + td, + th { + @apply p-2; + @apply font-medium; + } + + tr:nth-child(even) { + @apply bg-cards-secondary; + --bg-opacity: 0.5; + } + + tr:nth-child(odd) { + @apply bg-cards-tertiary; + --bg-opacity: 0.6; + } + + tr.cdk-header-row th { + @apply bg-cards-tertiary; + --bg-opacity: 1; + + // we cannot use borders directly due to + // the sticky header. Use a box-shadow to + // simulate a border. + box-shadow: 0 2px rgba(0, 0, 0, 0.3); + } +} diff --git a/desktop/angular/src/theme/_tailwind.scss b/desktop/angular/src/theme/_tailwind.scss new file mode 100644 index 00000000..28ccd29f --- /dev/null +++ b/desktop/angular/src/theme/_tailwind.scss @@ -0,0 +1,4 @@ +/** The tailwind post-processor will inject all tailwind styles here **/ +@import 'tailwindcss/base'; +@import 'tailwindcss/components'; +@import 'tailwindcss/utilities'; diff --git a/desktop/angular/src/theme/_trust-level.scss b/desktop/angular/src/theme/_trust-level.scss new file mode 100644 index 00000000..a4b00b51 --- /dev/null +++ b/desktop/angular/src/theme/_trust-level.scss @@ -0,0 +1,73 @@ +span.trust-level { + display : inline-block; + position : relative; + width : 6px; + user-select: none; + overflow : visible; + + &~* { + @apply ml-2; + } + + &:before { + content : ""; + display : block; + position : relative; + height : 6px; + width : 6px; + top : -1px; + left : 0px; + border-radius: 50%; + } + + &.centered:before { + top: 0px; + } + + &:before { + background-color: var(--bg-color); + @apply shadow-inner-xs; + } + + &.pulse:before { + animation : pulsate-trust 1s ease-out infinite; + box-shadow: 0 0 10px var(--glow-color); + } + + &.off { + --bg-color : theme('colors.info.gray'); + --glow-color: theme('colors.info.gray'); + } + + &.auto { + --bg-color : theme('colors.info.blue'); + --glow-color: theme('colors.info.blue'); + } + + &.low { + --bg-color : theme('colors.info.green'); + --glow-color: theme('colors.info.green'); + } + + &.medium { + --bg-color : theme('colors.info.yellow'); + --glow-color: theme('colors.info.yellow'); + } + + &.high { + --bg-color : theme('colors.info.red'); + --glow-color: theme('colors.info.red'); + } +} + +@keyframes pulsate-trust { + 100% { + opacity: 0.8; + } + + 0% { + background: var(--glow-color); + box-shadow: 0 0 0 var(--glow-color); + opacity : 1; + } +} diff --git a/desktop/angular/src/theme/_typography.scss b/desktop/angular/src/theme/_typography.scss new file mode 100644 index 00000000..1c8a6d01 --- /dev/null +++ b/desktop/angular/src/theme/_typography.scss @@ -0,0 +1,61 @@ +html, +body { + font-family: 'Roboto', sans-serif; + @apply text-primary; + @apply font-medium; +} + +body, +.primary-text, +.secondary-text { + @apply text-xs; + @apply font-medium; +} + +label, +.secondary-text { + @apply text-secondary; +} + +.primary-text { + @apply text-primary; +} + +.tertiary-text { + @apply text-tertiary; +} + +h1, +h2, +h3 { + @apply text-primary; +} + +h1 { + display: block; + + @apply mb-1; + @apply text-xl; + @apply font-normal; + @apply mb-2; +} + +h2 { + @apply p-2; + @apply ml-2; + @apply text-lg; + @apply font-medium; + letter-spacing: -0.01rem; +} + +h3 { + @apply mb-1; + @apply text-base; + @apply font-medium; +} + +h4 { + @apply text-xs; + @apply font-medium; + @apply text-tertiary; +} diff --git a/desktop/angular/src/theme/_verdict.scss b/desktop/angular/src/theme/_verdict.scss new file mode 100644 index 00000000..9f4a2730 --- /dev/null +++ b/desktop/angular/src/theme/_verdict.scss @@ -0,0 +1,47 @@ +span.verdict { + display : inline-block; + position : relative; + width : 12px; + height : 9px; + align-self : center; + justify-self: center; + user-select : none; + overflow : visible; + + &:before { + content : ""; + display : block; + position : absolute; + height : 8px; + width : 8px; + top : 0px; + left : 0px; + border-radius : 50%; + background-color: var(--bg-color); + border : 1px solid var(--bg-color); + @apply shadow-inner-xs; + } + + &.failed { + --bg-color: theme('colors.info.yellow'); + } + + &.accept, + &.reroutetons, + &.reroutetotunnel { + --bg-color: theme('colors.info.green'); + } + + &.block, + &.drop { + --bg-color: theme('colors.info.red'); + } + + &.outdated { + &:before { + background-color: transparent; + border-color : var(--bg-color); + opacity : .85; + } + } +} diff --git a/desktop/angular/src/theme/mixins/_pill.scss b/desktop/angular/src/theme/mixins/_pill.scss new file mode 100644 index 00000000..130f5519 --- /dev/null +++ b/desktop/angular/src/theme/mixins/_pill.scss @@ -0,0 +1,42 @@ +@mixin pill-container { + display : flex; + width : auto; + height : 18px; + align-items : center; + justify-content: flex-end; + font-size : 0.6rem; + line-height : 18px; + + border-radius: 0.5rem; + transform : scale(0.95); + + .counter { + flex-grow : 1; + display : inline-block; + text-align : right; + padding-right: 4px; + padding-left : 2px; + color : #999999ee; + font-size : 0.65rem; + font-weight : 800; + width : max-content; + } + + .pill { + display : inline-block; + width : 29px; + height : 5px; + background-color: #686868; + border-radius : 1rem; + overflow : hidden; + margin-left : 0.2rem; + margin-right : 0.6rem; + + .percentage { + display : block; + height : 100%; + width : 75%; + background-color: #21ad58; + } + } +} diff --git a/desktop/angular/tailwind.config.js b/desktop/angular/tailwind.config.js new file mode 100644 index 00000000..ba4e7f11 --- /dev/null +++ b/desktop/angular/tailwind.config.js @@ -0,0 +1,127 @@ +const plugin = require("tailwindcss/plugin"); + +module.exports = { + content: [ + "./src/**/*.{html,scss,css,ts}", + "./projects/**/*.{html,scss,css,ts}", + ], + theme: { + colors: { + transparent: "transparent", + current: "currentColor", + white: "#ffffff", + background: "#121213", + + gray: { + 100: "#131111", + 200: "#1b1b1b", + 300: "#222222", + 400: "#2c2c2c", + 500: "#474747", + 600: "#888888", + 700: "#ababab", + DEFAULT: "#ababab", + }, + + green: { + 100: "#143d24", + 200: "#18823d", + 300: "#1de966", + DEFAULT: "#18823d", + }, + + red: { + 100: "#3d1414", + 200: "#811818", + 300: "#e01d1d", + DEFAULT: "#d12e2e", + }, + + yellow: { + 100: "#3d3a14", + 200: "#827918", + 300: "#e9d81d", + DEFAULT: "#e9d81d", + }, + + cyan: { + 100: "#b2ebf2", + 200: "#80deea", + 300: "#4dd0e1", + 400: "#26c6da", + 500: "#00bcd4", + 600: "#00acc1", + 700: "#0097a7", + 800: "#00838f", + 900: "#006064", + }, + + deepPurple: { + 50: "#ede7f6", + 100: "#d1c4e9", + 200: "#b39ddb", + 300: "#9575cd", + 400: "#7e57c2", + 500: "#673ab7", + 600: "#5e35b1", + 700: "#512da8", + 800: "#4527a0", + 900: "#311b92", + }, + + blue: { + DEFAULT: "#4e97fa", + }, + + // Legacy color definitions + + // The overall application background color + + // Text shades + cards: { + primary: "var(--cards-primary)", + secondary: "var(--cards-secondary)", + tertiary: "var(--cards-tertiary)", + }, + + buttons: { + icon: "var(--button-icon)", + dark: "var(--button-dark)", + light: "var(--button-light)", + }, + + info: { + green: "var(--info-green)", + red: "var(--info-red)", + gray: "var(--info-gray)", + blue: "var(--info-blue)", + yellow: "var(--info-yellow)", + }, + }, + textColor: (theme) => { + return { + primary: theme("colors.white"), + secondary: theme("colors.gray.700"), + tertiary: theme("colors.gray.600"), + + ...theme("colors"), + }; + }, + extend: { + boxShadow: { + xs: "0 0 0 1px rgba(0, 0, 0, 0.05)", + "inner-xs": "inset 0 2px 4px 0 rgba(0, 0, 0, 0.16)", + }, + fontSize: { + xxs: "0.7rem", + }, + }, + }, + plugins: [ + plugin(function ({ addVariant, theme }) { + Object.keys(theme("screens")).forEach((key) => { + addVariant("sfng-" + key, ".min-width-" + theme("screens")[key] + " &"); + }); + }), + ], +}; diff --git a/desktop/angular/tsconfig.app.json b/desktop/angular/tsconfig.app.json new file mode 100644 index 00000000..f67c4660 --- /dev/null +++ b/desktop/angular/tsconfig.app.json @@ -0,0 +1,16 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [ + ] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/desktop/angular/tsconfig.json b/desktop/angular/tsconfig.json new file mode 100644 index 00000000..281e9628 --- /dev/null +++ b/desktop/angular/tsconfig.json @@ -0,0 +1,41 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "paths": { + "@safing/portmaster-api": [ + "dist-lib/safing/portmaster-api" + ], + "@safing/ui": [ + "dist-lib/safing/ui" + ] + }, + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "es2020", + "lib": [ + "es2018", + "dom" + ], + "types": [ + "./src/electron-app.d.ts", + "chrome" + ], + "useDefineForClassFields": false + }, + "angularCompilerOptions": { + "strictInjectionParameters": true, + "strictTemplates": true + } +} diff --git a/desktop/angular/tsconfig.spec.json b/desktop/angular/tsconfig.spec.json new file mode 100644 index 00000000..800c6e2f --- /dev/null +++ b/desktop/angular/tsconfig.spec.json @@ -0,0 +1,19 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts", + "src/app/widgets/status-widget-factory/settings.ts" + ] +} diff --git a/desktop/angular/tslint.json b/desktop/angular/tslint.json new file mode 100644 index 00000000..eba6f798 --- /dev/null +++ b/desktop/angular/tslint.json @@ -0,0 +1,153 @@ +{ + "extends": "tslint:recommended", + "rules": { + "align": { + "options": [ + "parameters", + "statements" + ] + }, + "array-type": false, + "arrow-return-shorthand": true, + "curly": true, + "deprecation": { + "severity": "warning" + }, + "component-class-suffix": true, + "contextual-lifecycle": true, + "directive-class-suffix": true, + "directive-selector": [ + true, + "attribute", + "app", + "camelCase" + ], + "component-selector": [ + true, + "element", + "app", + "kebab-case" + ], + "eofline": true, + "import-blacklist": [ + true, + "rxjs/Rx" + ], + "import-spacing": true, + "indent": { + "options": [ + "spaces" + ] + }, + "max-classes-per-file": false, + "max-line-length": [ + true, + 140 + ], + "member-ordering": [ + true, + { + "order": [ + "static-field", + "instance-field", + "static-method", + "instance-method" + ] + } + ], + "no-any": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-empty": false, + "no-inferrable-types": [ + true, + "ignore-params" + ], + "no-non-null-assertion": true, + "no-redundant-jsdoc": true, + "no-switch-case-fall-through": true, + "no-var-requires": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], + "quotemark": [ + true, + "single" + ], + "semicolon": { + "options": [ + "always" + ] + }, + "space-before-function-paren": { + "options": { + "anonymous": "never", + "asyncArrow": "always", + "constructor": "never", + "method": "never", + "named": "never" + } + }, + "typedef": [ + true, + "call-signature" + ], + "typedef-whitespace": { + "options": [ + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } + ] + }, + "variable-name": { + "options": [ + "ban-keywords", + "check-format", + "allow-pascal-case" + ] + }, + "whitespace": { + "options": [ + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + }, + "no-conflicting-lifecycle": true, + "no-host-metadata-property": true, + "no-input-rename": true, + "no-inputs-metadata-property": true, + "no-output-native": true, + "no-output-on-prefix": true, + "no-output-rename": true, + "no-outputs-metadata-property": true, + "template-banana-in-box": true, + "template-no-negated-async": true, + "use-lifecycle-interface": true, + "use-pipe-transform-interface": true + }, + "rulesDirectory": [ + "codelyzer" + ] +} \ No newline at end of file