├── .gitignore ├── LICENSE ├── LICENSE_CC_BY_NC_ND_4.0 ├── NewFlow.desktop ├── README.md ├── assets ├── banner.png ├── bootstrap-icons │ ├── telegram.svg │ ├── twitter-x.svg │ └── whatsapp.svg ├── breeze-icons │ ├── go-down.svg │ ├── go-up.svg │ ├── window-close.svg │ └── window-restore.svg ├── fluent-icons │ ├── dismiss_16_regular.svg │ ├── line_horizontal_1_16_regular.svg │ ├── maximize_16_regular.svg │ └── square_multiple_16_regular.svg ├── inner │ ├── playlist_liked.png │ └── playlist_watch_later.png ├── material-symbols │ ├── keyboard_arrow_down.png │ ├── keyboard_arrow_left.png │ ├── keyboard_arrow_right.png │ ├── keyboard_arrow_up.png │ ├── pause.png │ ├── play_arrow.png │ ├── skip_next.png │ └── skip_previous.png ├── newflow.ico ├── newflow.png ├── newflow.svg ├── screenshots │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ ├── 9.png │ └── GALLERY.md └── yaru-icons │ ├── window-close-symbolic.svg │ ├── window-maximize-symbolic.svg │ ├── window-minimize-symbolic.svg │ └── window-restore-symbolic.svg ├── css ├── background-material.css ├── layout.css ├── material-symbols.css ├── pip.css └── styles.css ├── fonts └── material-symbols-rounded.woff2 ├── index.html ├── index.js ├── js ├── backtrace.js ├── components.js ├── context_menu.js ├── database.js ├── downloader.js ├── extensions │ └── lyrics │ │ ├── index.js │ │ └── plugin.json ├── image_helper.js ├── imports.js ├── json_database.js ├── keyboard_shortcuts.js ├── language_pack.js ├── pip.js ├── plugins.js ├── render.js └── thirdparty │ ├── hls.light.min.js │ └── vibrant.js ├── languages.json ├── linux-setup ├── node_modules └── @electron │ └── remote │ ├── LICENSE │ ├── README.md │ ├── dist │ └── src │ │ ├── common │ │ ├── get-electron-binding.d.ts │ │ ├── get-electron-binding.js │ │ ├── ipc-messages.d.ts │ │ ├── ipc-messages.js │ │ ├── module-names.d.ts │ │ ├── module-names.js │ │ ├── type-utils.d.ts │ │ └── type-utils.js │ │ ├── main │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── objects-registry.d.ts │ │ ├── objects-registry.js │ │ ├── server.d.ts │ │ └── server.js │ │ └── renderer │ │ ├── callbacks-registry.d.ts │ │ ├── callbacks-registry.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── remote.d.ts │ │ └── remote.js │ ├── index.d.ts │ ├── main │ ├── index.d.ts │ └── index.js │ ├── package.json │ └── renderer │ ├── index.d.ts │ └── index.js ├── package.json ├── pip.html ├── preload.js ├── scripts ├── create_launcher.vbs ├── create_launcher_with_global_electron.vbs ├── install-desktop-file ├── remove_launcher.vbs └── uninstall-desktop-file └── windows-setup ├── .gitignore ├── build_setup.cmd └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | electron-data 2 | yt-extractor 3 | dbs/*.json 4 | js/__.js 5 | window_config.json 6 | \!TODO -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE_CC_BY_NC_ND_4.0: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial-NoDerivatives 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 58 | International Public License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-NonCommercial-NoDerivatives 4.0 International Public 63 | License ("Public License"). To the extent this Public License may be 64 | interpreted as a contract, You are granted the Licensed Rights in 65 | consideration of Your acceptance of these terms and conditions, and the 66 | Licensor grants You such rights in consideration of benefits the 67 | Licensor receives from making the Licensed Material available under 68 | these terms and conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Copyright and Similar Rights means copyright and/or similar rights 84 | closely related to copyright including, without limitation, 85 | performance, broadcast, sound recording, and Sui Generis Database 86 | Rights, without regard to how the rights are labeled or 87 | categorized. For purposes of this Public License, the rights 88 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 89 | Rights. 90 | 91 | c. Effective Technological Measures means those measures that, in the 92 | absence of proper authority, may not be circumvented under laws 93 | fulfilling obligations under Article 11 of the WIPO Copyright 94 | Treaty adopted on December 20, 1996, and/or similar international 95 | agreements. 96 | 97 | d. Exceptions and Limitations means fair use, fair dealing, and/or 98 | any other exception or limitation to Copyright and Similar Rights 99 | that applies to Your use of the Licensed Material. 100 | 101 | e. Licensed Material means the artistic or literary work, database, 102 | or other material to which the Licensor applied this Public 103 | License. 104 | 105 | f. Licensed Rights means the rights granted to You subject to the 106 | terms and conditions of this Public License, which are limited to 107 | all Copyright and Similar Rights that apply to Your use of the 108 | Licensed Material and that the Licensor has authority to license. 109 | 110 | g. Licensor means the individual(s) or entity(ies) granting rights 111 | under this Public License. 112 | 113 | h. NonCommercial means not primarily intended for or directed towards 114 | commercial advantage or monetary compensation. For purposes of 115 | this Public License, the exchange of the Licensed Material for 116 | other material subject to Copyright and Similar Rights by digital 117 | file-sharing or similar means is NonCommercial provided there is 118 | no payment of monetary compensation in connection with the 119 | exchange. 120 | 121 | i. Share means to provide material to the public by any means or 122 | process that requires permission under the Licensed Rights, such 123 | as reproduction, public display, public performance, distribution, 124 | dissemination, communication, or importation, and to make material 125 | available to the public including in ways that members of the 126 | public may access the material from a place and at a time 127 | individually chosen by them. 128 | 129 | j. Sui Generis Database Rights means rights other than copyright 130 | resulting from Directive 96/9/EC of the European Parliament and of 131 | the Council of 11 March 1996 on the legal protection of databases, 132 | as amended and/or succeeded, as well as other essentially 133 | equivalent rights anywhere in the world. 134 | 135 | k. You means the individual or entity exercising the Licensed Rights 136 | under this Public License. Your has a corresponding meaning. 137 | 138 | 139 | Section 2 -- Scope. 140 | 141 | a. License grant. 142 | 143 | 1. Subject to the terms and conditions of this Public License, 144 | the Licensor hereby grants You a worldwide, royalty-free, 145 | non-sublicensable, non-exclusive, irrevocable license to 146 | exercise the Licensed Rights in the Licensed Material to: 147 | 148 | a. reproduce and Share the Licensed Material, in whole or 149 | in part, for NonCommercial purposes only; and 150 | 151 | b. produce and reproduce, but not Share, Adapted Material 152 | for NonCommercial purposes only. 153 | 154 | 2. Exceptions and Limitations. For the avoidance of doubt, where 155 | Exceptions and Limitations apply to Your use, this Public 156 | License does not apply, and You do not need to comply with 157 | its terms and conditions. 158 | 159 | 3. Term. The term of this Public License is specified in Section 160 | 6(a). 161 | 162 | 4. Media and formats; technical modifications allowed. The 163 | Licensor authorizes You to exercise the Licensed Rights in 164 | all media and formats whether now known or hereafter created, 165 | and to make technical modifications necessary to do so. The 166 | Licensor waives and/or agrees not to assert any right or 167 | authority to forbid You from making technical modifications 168 | necessary to exercise the Licensed Rights, including 169 | technical modifications necessary to circumvent Effective 170 | Technological Measures. For purposes of this Public License, 171 | simply making modifications authorized by this Section 2(a) 172 | (4) never produces Adapted Material. 173 | 174 | 5. Downstream recipients. 175 | 176 | a. Offer from the Licensor -- Licensed Material. Every 177 | recipient of the Licensed Material automatically 178 | receives an offer from the Licensor to exercise the 179 | Licensed Rights under the terms and conditions of this 180 | Public License. 181 | 182 | b. No downstream restrictions. You may not offer or impose 183 | any additional or different terms or conditions on, or 184 | apply any Effective Technological Measures to, the 185 | Licensed Material if doing so restricts exercise of the 186 | Licensed Rights by any recipient of the Licensed 187 | Material. 188 | 189 | 6. No endorsement. Nothing in this Public License constitutes or 190 | may be construed as permission to assert or imply that You 191 | are, or that Your use of the Licensed Material is, connected 192 | with, or sponsored, endorsed, or granted official status by, 193 | the Licensor or others designated to receive attribution as 194 | provided in Section 3(a)(1)(A)(i). 195 | 196 | b. Other rights. 197 | 198 | 1. Moral rights, such as the right of integrity, are not 199 | licensed under this Public License, nor are publicity, 200 | privacy, and/or other similar personality rights; however, to 201 | the extent possible, the Licensor waives and/or agrees not to 202 | assert any such rights held by the Licensor to the limited 203 | extent necessary to allow You to exercise the Licensed 204 | Rights, but not otherwise. 205 | 206 | 2. Patent and trademark rights are not licensed under this 207 | Public License. 208 | 209 | 3. To the extent possible, the Licensor waives any right to 210 | collect royalties from You for the exercise of the Licensed 211 | Rights, whether directly or through a collecting society 212 | under any voluntary or waivable statutory or compulsory 213 | licensing scheme. In all other cases the Licensor expressly 214 | reserves any right to collect such royalties, including when 215 | the Licensed Material is used other than for NonCommercial 216 | purposes. 217 | 218 | 219 | Section 3 -- License Conditions. 220 | 221 | Your exercise of the Licensed Rights is expressly made subject to the 222 | following conditions. 223 | 224 | a. Attribution. 225 | 226 | 1. If You Share the Licensed Material, You must: 227 | 228 | a. retain the following if it is supplied by the Licensor 229 | with the Licensed Material: 230 | 231 | i. identification of the creator(s) of the Licensed 232 | Material and any others designated to receive 233 | attribution, in any reasonable manner requested by 234 | the Licensor (including by pseudonym if 235 | designated); 236 | 237 | ii. a copyright notice; 238 | 239 | iii. a notice that refers to this Public License; 240 | 241 | iv. a notice that refers to the disclaimer of 242 | warranties; 243 | 244 | v. a URI or hyperlink to the Licensed Material to the 245 | extent reasonably practicable; 246 | 247 | b. indicate if You modified the Licensed Material and 248 | retain an indication of any previous modifications; and 249 | 250 | c. indicate the Licensed Material is licensed under this 251 | Public License, and include the text of, or the URI or 252 | hyperlink to, this Public License. 253 | 254 | For the avoidance of doubt, You do not have permission under 255 | this Public License to Share Adapted Material. 256 | 257 | 2. You may satisfy the conditions in Section 3(a)(1) in any 258 | reasonable manner based on the medium, means, and context in 259 | which You Share the Licensed Material. For example, it may be 260 | reasonable to satisfy the conditions by providing a URI or 261 | hyperlink to a resource that includes the required 262 | information. 263 | 264 | 3. If requested by the Licensor, You must remove any of the 265 | information required by Section 3(a)(1)(A) to the extent 266 | reasonably practicable. 267 | 268 | 269 | Section 4 -- Sui Generis Database Rights. 270 | 271 | Where the Licensed Rights include Sui Generis Database Rights that 272 | apply to Your use of the Licensed Material: 273 | 274 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 275 | to extract, reuse, reproduce, and Share all or a substantial 276 | portion of the contents of the database for NonCommercial purposes 277 | only and provided You do not Share Adapted Material; 278 | 279 | b. if You include all or a substantial portion of the database 280 | contents in a database in which You have Sui Generis Database 281 | Rights, then the database in which You have Sui Generis Database 282 | Rights (but not its individual contents) is Adapted Material; and 283 | 284 | c. You must comply with the conditions in Section 3(a) if You Share 285 | all or a substantial portion of the contents of the database. 286 | 287 | For the avoidance of doubt, this Section 4 supplements and does not 288 | replace Your obligations under this Public License where the Licensed 289 | Rights include other Copyright and Similar Rights. 290 | 291 | 292 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 293 | 294 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 295 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 296 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 297 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 298 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 299 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 300 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 301 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 302 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 303 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 304 | 305 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 306 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 307 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 308 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 309 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 310 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 311 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 312 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 313 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 314 | 315 | c. The disclaimer of warranties and limitation of liability provided 316 | above shall be interpreted in a manner that, to the extent 317 | possible, most closely approximates an absolute disclaimer and 318 | waiver of all liability. 319 | 320 | 321 | Section 6 -- Term and Termination. 322 | 323 | a. This Public License applies for the term of the Copyright and 324 | Similar Rights licensed here. However, if You fail to comply with 325 | this Public License, then Your rights under this Public License 326 | terminate automatically. 327 | 328 | b. Where Your right to use the Licensed Material has terminated under 329 | Section 6(a), it reinstates: 330 | 331 | 1. automatically as of the date the violation is cured, provided 332 | it is cured within 30 days of Your discovery of the 333 | violation; or 334 | 335 | 2. upon express reinstatement by the Licensor. 336 | 337 | For the avoidance of doubt, this Section 6(b) does not affect any 338 | right the Licensor may have to seek remedies for Your violations 339 | of this Public License. 340 | 341 | c. For the avoidance of doubt, the Licensor may also offer the 342 | Licensed Material under separate terms or conditions or stop 343 | distributing the Licensed Material at any time; however, doing so 344 | will not terminate this Public License. 345 | 346 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 347 | License. 348 | 349 | 350 | Section 7 -- Other Terms and Conditions. 351 | 352 | a. The Licensor shall not be bound by any additional or different 353 | terms or conditions communicated by You unless expressly agreed. 354 | 355 | b. Any arrangements, understandings, or agreements regarding the 356 | Licensed Material not stated herein are separate from and 357 | independent of the terms and conditions of this Public License. 358 | 359 | 360 | Section 8 -- Interpretation. 361 | 362 | a. For the avoidance of doubt, this Public License does not, and 363 | shall not be interpreted to, reduce, limit, restrict, or impose 364 | conditions on any use of the Licensed Material that could lawfully 365 | be made without permission under this Public License. 366 | 367 | b. To the extent possible, if any provision of this Public License is 368 | deemed unenforceable, it shall be automatically reformed to the 369 | minimum extent necessary to make it enforceable. If the provision 370 | cannot be reformed, it shall be severed from this Public License 371 | without affecting the enforceability of the remaining terms and 372 | conditions. 373 | 374 | c. No term or condition of this Public License will be waived and no 375 | failure to comply consented to unless expressly agreed to by the 376 | Licensor. 377 | 378 | d. Nothing in this Public License constitutes or may be interpreted 379 | as a limitation upon, or waiver of, any privileges and immunities 380 | that apply to the Licensor or You, including from the legal 381 | processes of any jurisdiction or authority. 382 | 383 | ======================================================================= 384 | 385 | Creative Commons is not a party to its public 386 | licenses. Notwithstanding, Creative Commons may elect to apply one of 387 | its public licenses to material it publishes and in those instances 388 | will be considered the “Licensor.” The text of the Creative Commons 389 | public licenses is dedicated to the public domain under the CC0 Public 390 | Domain Dedication. Except for the limited purpose of indicating that 391 | material is shared under a Creative Commons public license or as 392 | otherwise permitted by the Creative Commons policies published at 393 | creativecommons.org/policies, Creative Commons does not authorize the 394 | use of the trademark "Creative Commons" or any other trademark or logo 395 | of Creative Commons without its prior written consent including, 396 | without limitation, in connection with any unauthorized modifications 397 | to any of its public licenses or any other arrangements, 398 | understandings, or agreements concerning use of licensed material. For 399 | the avoidance of doubt, this paragraph does not form part of the 400 | public licenses. 401 | 402 | Creative Commons may be contacted at creativecommons.org. 403 | 404 | -------------------------------------------------------------------------------- /NewFlow.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=NewFlow 3 | Comment=Your free video player client for YouTube™ 4 | Type=Application 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NewFlow ![Download Count](https://img.shields.io/github/downloads/malisipi/NewFlow/total?style=plastic&link=https%3A%2F%2Fgithub.com%2Fmalisipi%2FNewFlow) 2 | 3 | !["NewFlow Screenshot"](./assets/screenshots/4.png) 4 | 5 | Your free video player client for YouTube™. 6 | 7 | > You can see more screenshots from [Gallery](./assets/screenshots/GALLERY.md) 8 | 9 | > [!WARNING] 10 | > This application is unstable, there are still a lot of missing or partially implementated features however most of core features should work. 11 | 12 | ## Installation 13 | 14 | > [!IMPORTANT] 15 | > Installers is designed to help end users while installing NewFlow however the installers can have some bugs. If you encounter if any bug, please report it. 16 | 17 | ### For Windows 10 and above 18 | 19 | > You can use the installer that placed in releases section. 20 | 21 | ### For Linux 22 | 23 | > You can run the command that placed below to install NewFlow. 24 | 25 | > [!IMPORTANT] 26 | > `git` and `electron` commands must be installed to use the script. 27 | 28 | ```curl https://raw.githubusercontent.com/malisipi/NewFlow/main/linux-setup | bash``` 29 | 30 | ## Licenses 31 | 32 | > [!WARNING] 33 | >

Disclaimer

34 | > 38 | 39 | > [!IMPORTANT] 40 | > The application is licensed by [Apache 2.0 License](./LICENSE). 41 | > 42 | > The icons and banners of application is licensed by [Attribution-NonCommercial-NoDerivatives 4.0 International License](./LICENSE_CC_BY_NC_ND_4.0). So `./assets/banner.png`, `./assets/newflow.png`, `./assets/newflow.ico` and `./assets/newflow.svg` is licensed by [Attribution-NonCommercial-NoDerivatives 4.0 International License](./LICENSE_CC_BY_NC_ND_4.0) 43 | 44 | - [Electron.JS](https://github.com/electron/electron) is licensed by [MIT License](https://github.com/electron/electron/blob/main/LICENSE). 45 | - [electron/remote](https://github.com/electron/remote) is licensed by [MIT License](https://github.com/electron/remote/blob/main/LICENSE). 46 | - Module placed in `./node_modules/@electron/remote`. 47 | - [Yaru Icons](https://github.com/ubuntu/yaru) is licensed by [CC-BY-SA 4.0 License](https://github.com/ubuntu/yaru#copying-or-reusing). 48 | - Icons placed in `./assets/yaru-icons/`. 49 | - [Breeze Icons](https://github.com/KDE/breeze-icons) is licensed by [LGPL 2.1 License](https://github.com/KDE/breeze-icons/blob/master/COPYING.LIB). 50 | - Icons placed in `./assets/breeze-icons/`. 51 | - [Fluent Icons](https://github.com/microsoft/fluentui-system-icons) is licensed by [MIT License](https://github.com/microsoft/fluentui-system-icons/blob/main/LICENSE). 52 | - Icons placed in `./assets/fluent-icons/`. 53 | - [Bootstrap Icons](https://github.com/twbs/icons) is licensed by [MIT License](https://github.com/twbs/icons/blob/main/LICENSE). 54 | - Icons placed in `./assets/bootstrap-icons/`. 55 | - [Material Symbols](https://github.com/google/material-design-icons) is licensed by [Apache 2.0 License](https://github.com/google/material-design-icons/blob/master/LICENSE). 56 | - Icons placed in `./assets/material-symbols/`, `./fonts/material-symbols-rounded.woff2` and `./css/material-symbols.css` (Edited). 57 | - [yt-extractor.js](https://github.com/malisipi/yt-extractor.js) is licensed by [Apache 2.0 License](https://github.com/malisipi/yt-extractor.js/blob/main/LICENSE). 58 | - Sub dependencies: 59 | - [fast-xml-parse](https://www.npmjs.com/package/fast-xml-parser) is licensed by [MIT License](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/LICENSE). 60 | - [node-vibrant](https://github.com/Vibrant-Colors/node-vibrant) is licensed by [MIT License](https://github.com/Vibrant-Colors/node-vibrant/blob/master/LICENSE.md). 61 | - Script file placed in `./js/thirdparty/vibrant.js`. 62 | - [hls.js](https://github.com/video-dev/hls.js) is licensed by [Apache 2.0 License](https://github.com/video-dev/hls.js/blob/master/LICENSE). 63 | - Script file placed in `./js/thirdparty/hls.light.min.js`. 64 | 65 | ## FAQ 66 | 67 | ### Picture-in-Picture gets under windows on Linux/Wayland 68 | 69 | Since Wayland protocol doesn't support to set windows always on top, there're no possible way to do it without a Window-Manager trick. 70 | 71 | * If you're using KWin Windows Manager (Default Windows Manager of KDE Desktop Environment), you can set a new window rule to keep on top. 72 | 73 | * If you're using Ubuntu or based distro, you can look up [this extension](https://github.com/Rafostar/gnome-shell-extension-pip-on-top) to get working. 74 | 75 | ### Creating Desktop Shortcut on Linux with Wayland Support 76 | 77 | ``` 78 | NEWFLOW_FLAGS="--enable-features=UseOzonePlatform --ozone-platform=wayland" ./scripts/install-desktop-file 79 | ``` 80 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/banner.png -------------------------------------------------------------------------------- /assets/bootstrap-icons/telegram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/bootstrap-icons/twitter-x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/bootstrap-icons/whatsapp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/breeze-icons/go-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/breeze-icons/go-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/breeze-icons/window-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /assets/breeze-icons/window-restore.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/fluent-icons/dismiss_16_regular.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/fluent-icons/line_horizontal_1_16_regular.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/fluent-icons/maximize_16_regular.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/fluent-icons/square_multiple_16_regular.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/inner/playlist_liked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/inner/playlist_liked.png -------------------------------------------------------------------------------- /assets/inner/playlist_watch_later.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/inner/playlist_watch_later.png -------------------------------------------------------------------------------- /assets/material-symbols/keyboard_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/keyboard_arrow_down.png -------------------------------------------------------------------------------- /assets/material-symbols/keyboard_arrow_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/keyboard_arrow_left.png -------------------------------------------------------------------------------- /assets/material-symbols/keyboard_arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/keyboard_arrow_right.png -------------------------------------------------------------------------------- /assets/material-symbols/keyboard_arrow_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/keyboard_arrow_up.png -------------------------------------------------------------------------------- /assets/material-symbols/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/pause.png -------------------------------------------------------------------------------- /assets/material-symbols/play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/play_arrow.png -------------------------------------------------------------------------------- /assets/material-symbols/skip_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/skip_next.png -------------------------------------------------------------------------------- /assets/material-symbols/skip_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/material-symbols/skip_previous.png -------------------------------------------------------------------------------- /assets/newflow.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/newflow.ico -------------------------------------------------------------------------------- /assets/newflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/newflow.png -------------------------------------------------------------------------------- /assets/newflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 17 | 20 | 23 | 24 | 25 | 34 | 39 | 41 | 44 | 47 | 51 | 52 | 55 | 59 | 60 | 63 | 67 | 68 | 71 | 75 | 76 | 79 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /assets/screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/1.png -------------------------------------------------------------------------------- /assets/screenshots/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/10.png -------------------------------------------------------------------------------- /assets/screenshots/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/11.png -------------------------------------------------------------------------------- /assets/screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/2.png -------------------------------------------------------------------------------- /assets/screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/3.png -------------------------------------------------------------------------------- /assets/screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/4.png -------------------------------------------------------------------------------- /assets/screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/5.png -------------------------------------------------------------------------------- /assets/screenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/6.png -------------------------------------------------------------------------------- /assets/screenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/7.png -------------------------------------------------------------------------------- /assets/screenshots/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/8.png -------------------------------------------------------------------------------- /assets/screenshots/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/assets/screenshots/9.png -------------------------------------------------------------------------------- /assets/screenshots/GALLERY.md: -------------------------------------------------------------------------------- 1 | # NewFlow Gallery 2 | 3 | ## Main Page 4 | ![](./1.png) 5 | 6 | ## Search 7 | ![](./2.png) 8 | ![](./3.png) 9 | 10 | ## Player 11 | ![](./4.png) 12 | 13 | ## Side Navigation 14 | 15 | ![](./5.png) 16 | 17 | ## Queue view 18 | 19 | ![](./6.png) 20 | 21 | ## Comments 22 | 23 | ![](./7.png) 24 | 25 | ## Following Page 26 | 27 | ![](./8.png) 28 | 29 | ## Feeds 30 | 31 | ![](./9.png) 32 | 33 | ## Pop-Up Video Player 34 | 35 | ![](./10.png) 36 | 37 | ## Artist View 38 | 39 | ![](./11.png) -------------------------------------------------------------------------------- /assets/yaru-icons/window-close-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/yaru-icons/window-maximize-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/yaru-icons/window-minimize-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/yaru-icons/window-restore-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /css/background-material.css: -------------------------------------------------------------------------------- 1 | body[material] { 2 | &, & video { 3 | background:transparent !important; 4 | } 5 | 6 | & .sidenav[open] { 7 | background: var(--background-color, black); 8 | border-radius: 5px; 9 | } 10 | } 11 | 12 | body[material="noise"] { 13 | & :fullscreen::after, 14 | &::after { 15 | content: ""; 16 | z-index: -2; 17 | opacity: 1; 18 | position: fixed; 19 | top: 0; 20 | left: 0; 21 | width: 100%; 22 | height: 100%; 23 | border-radius: 5px; 24 | background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 250 250' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='6' numOctaves='1' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E"); 25 | } 26 | 27 | & :fullscreen::before, 28 | &::before { 29 | content: ""; 30 | z-index: -1; 31 | background: var(--background-color, black); 32 | opacity: 0.5; 33 | position: fixed; 34 | top: 0; 35 | left: 0; 36 | width: 100%; 37 | height: 100%; 38 | border-radius: 5px; 39 | } 40 | } -------------------------------------------------------------------------------- /css/layout.css: -------------------------------------------------------------------------------- 1 | html { 2 | --titlebar-height: 30px; 3 | --titlebar-button-width: 50px; 4 | --sidebar-width: 50px; 5 | --playbar-height: 50px; 6 | --playbar-spacing: 10px; 7 | } 8 | 9 | body[os="linux"] { 10 | --titlebar-button-width: 30px; 11 | } 12 | 13 | * { /* Clear CSS */ 14 | box-sizing: border-box; 15 | margin: 0; 16 | padding: 0; 17 | user-select: none; 18 | } 19 | 20 | div.titlebar { 21 | display: flex; 22 | left: 0; 23 | top: 0; 24 | width: 100%; 25 | height: var(--titlebar-height); 26 | -webkit-app-region: drag; 27 | 28 | & .button-layout { 29 | display: flex; 30 | 31 | &[custom] { 32 | & button:not([show]) { 33 | display: none; 34 | } 35 | } 36 | 37 | :is(body[os="linux"]) &[variant="kde"] { 38 | & img:not(.kde) { 39 | display: none; 40 | } 41 | } 42 | 43 | :is(body[os="linux"]) &:not([variant]) { 44 | & img.kde { 45 | display: none; 46 | } 47 | } 48 | } 49 | 50 | & .title { 51 | padding-left: 10px; 52 | flex-grow: 1; 53 | align-self: center; 54 | } 55 | 56 | & input.search { 57 | position: fixed; 58 | left: 50%; 59 | top: 2.5px; 60 | height: calc(var(--titlebar-height) - 5px); 61 | width: 300px; 62 | transform: translate(-50%, 0); 63 | padding: 0 5px; 64 | -webkit-app-region: none; 65 | } 66 | 67 | & button { 68 | width: var(--titlebar-button-width); 69 | -webkit-app-region: none; 70 | 71 | &.menu { 72 | width: var(--sidebar-width); 73 | } 74 | 75 | &[state="maximize"] img.restore { 76 | display: none; 77 | } 78 | 79 | &[state="restore"] img.maximize { 80 | display: none; 81 | } 82 | 83 | & img { 84 | width: 16px; 85 | filter: brightness(0); 86 | :is(body):is([theme="dark"], [theme="black"]) & { 87 | filter: brightness(0) invert(1); 88 | } 89 | 90 | :is(body)[os="linux"] &.other { 91 | display: none; 92 | } 93 | 94 | :is(body)[os="other"] &.linux { 95 | display: none; 96 | } 97 | } 98 | } 99 | } 100 | 101 | @media (max-width: 700px) { 102 | .titlebar input.search { 103 | &:not(:focus) { 104 | pointer-events: none; 105 | opacity: 0; 106 | -webkit-app-region: drag; 107 | max-width: 0; 108 | } 109 | 110 | &:focus { 111 | width: 90%; 112 | left: 5%; 113 | transform: unset; 114 | } 115 | 116 | .titlebar:has(&:focus) *:not(&) { 117 | pointer-events: none; 118 | opacity: 0; 119 | -webkit-app-region: drag; 120 | max-width: 0; 121 | } 122 | } 123 | } 124 | 125 | div.sidenav { 126 | position: fixed; 127 | left: 0; 128 | top: var(--titlebar-height); 129 | height: calc(100% - var(--titlebar-height)); 130 | width: var(--sidebar-width); 131 | display: flex; 132 | flex-direction: column; 133 | background: inherit; 134 | z-index: 8; 135 | 136 | & > * { 137 | display: flex; 138 | align-items: center; 139 | gap: 5px; 140 | 141 | & > * { 142 | pointer-events: none; 143 | } 144 | } 145 | 146 | & material-symbol { 147 | width: var(--sidebar-width); 148 | height: var(--sidebar-width); 149 | } 150 | 151 | & :is(.open-only, .description) { 152 | display: none; 153 | } 154 | 155 | &[open] { 156 | width: 200px; 157 | 158 | & :is(.open-only, .description) { 159 | display: flex; 160 | } 161 | } 162 | 163 | & img { 164 | width: 100%; 165 | } 166 | 167 | @media (max-width: 500px) { 168 | &:not([open]) { 169 | pointer-events: none; 170 | opacity: 0; 171 | visibility: hidden; 172 | } 173 | } 174 | } 175 | 176 | button.global-action.search { 177 | position: fixed; 178 | bottom: 24px; 179 | right: 24px; 180 | width: var(--sidebar-width); 181 | height: var(--sidebar-width); 182 | display: none; 183 | z-index: 99999999; 184 | 185 | @media (max-width: 700px) { 186 | display: unset; 187 | } 188 | } 189 | 190 | div.tabs { 191 | & div.view { 192 | position: fixed; 193 | left: var(--sidebar-width); 194 | top: var(--titlebar-height); 195 | height: calc(100% - var(--titlebar-height)); 196 | width: calc(100% - var(--sidebar-width)); 197 | display: block; 198 | overflow: auto; 199 | padding: 10px; 200 | 201 | @media (max-width: 500px) { 202 | left: 0; 203 | width: 100%; 204 | } 205 | 206 | .tabs:not([active="trends"]) &:is(#trends), 207 | .tabs:not([active="search"]) &:is(#search), 208 | .tabs:not([active="watch"]) &:is(#watch), 209 | .tabs:not([active="feed"]) &:is(#feed), 210 | .tabs:not([active="following"]) &:is(#following), 211 | .tabs:not([active="owner"]) &:is(#owner), 212 | .tabs:not([active="playlists"]) &:is(#playlists), 213 | .tabs:not([active="playlist"]) &:is(#playlist), 214 | .tabs:not([active="history"]) &:is(#history), 215 | .tabs:not([active="downloads"]) &:is(#downloads), 216 | .tabs:not([active="settings"]) &:is(#settings), 217 | .tabs:not([active="about"]) &:is(#about), 218 | .tabs:not([active="offline"]) &:is(#offline), 219 | .tabs:not([active="queue"]) &:is(#queue) { 220 | display: none; 221 | } 222 | } 223 | } 224 | 225 | div.playbar { 226 | position: fixed; 227 | bottom: var(--playbar-spacing); 228 | left: 10%; 229 | width: 80%; 230 | height: var(--playbar-height); 231 | display: none; 232 | padding: 0 20px; 233 | grid-template-areas: 234 | "thumbnail title controls" 235 | "thumbnail owner controls"; 236 | grid-auto-columns: min-content auto min-content; 237 | align-items: center; 238 | gap: 0 10px; 239 | z-index: 7; 240 | 241 | & .thumbnail { 242 | grid-area: thumbnail; 243 | height: var(--playbar-height); 244 | } 245 | 246 | & .title { 247 | grid-area: title; 248 | text-overflow: ellipsis; 249 | overflow: hidden; 250 | text-wrap: nowrap; 251 | transform: translate(0, 2px); 252 | } 253 | 254 | & .owner { 255 | grid-area: owner; 256 | text-overflow: ellipsis; 257 | overflow: hidden; 258 | text-wrap: nowrap; 259 | transform: translate(0, -2px); 260 | } 261 | 262 | & .controls { 263 | display: flex; 264 | grid-area: controls; 265 | flex-wrap: nowrap; 266 | } 267 | 268 | @media (max-width: 600px) { 269 | left: 0; 270 | width: 100%; 271 | bottom: 0; 272 | } 273 | 274 | @media (max-width: 450px) { 275 | padding: 0 10px 0 0; 276 | } 277 | 278 | @media (max-width: 360px) { 279 | padding: 0 10px; 280 | 281 | & .thumbnail { 282 | display: none; 283 | } 284 | } 285 | } 286 | 287 | dialog#share[open] { 288 | display: flex; 289 | position: fixed; 290 | top: 50%; 291 | left: 50%; 292 | transform: translate(-50%, -50%); 293 | z-index: 999; 294 | gap: 10px; 295 | padding: 10px; 296 | flex-direction: column; 297 | align-items: center; 298 | width: min(80%, 400px); 299 | 300 | & .close { 301 | position: absolute; 302 | right: 10px; 303 | } 304 | 305 | & .actions { 306 | display: flex; 307 | gap: 5px; 308 | 309 | & div.masked-symbol { 310 | width: 32px; 311 | height: 32px; 312 | background: var(--text-color); 313 | mask-size: 32px !important; 314 | } 315 | 316 | & material-symbol { 317 | --size: 32px; 318 | } 319 | } 320 | 321 | *:is(body):has(&) > *:not(&, .titlebar), *:is(body):has(&) .titlebar > :not(.button-layout) { 322 | pointer-events: none; 323 | opacity: 0.3; 324 | } 325 | } 326 | 327 | :is(body):has(div.tabs:not([active="watch"]) #watch video[src*="://"]), 328 | :is(body):has(div.tabs:not([active="watch"])):not(:has(div.tabs #watch video)) { 329 | & div.tabs { 330 | & div.view { 331 | height: calc(100% - calc(var(--titlebar-height) + calc(var(--playbar-height) + calc(var(--playbar-spacing) * 1.5)))); 332 | } 333 | } 334 | 335 | & div.playbar { 336 | display: grid; 337 | } 338 | } -------------------------------------------------------------------------------- /css/material-symbols.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Symbols Rounded'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(../fonts/material-symbols-rounded.woff2); 6 | } 7 | 8 | material-symbol, .material-symbol { 9 | --size: 24px; 10 | font-family: 'Material Symbols Rounded'; 11 | font-weight: normal; 12 | font-style: normal; 13 | font-size: var(--size); 14 | line-height: 1; 15 | letter-spacing: normal; 16 | text-transform: none; 17 | display: inline-block; 18 | white-space: nowrap; 19 | word-wrap: normal; 20 | direction: ltr; 21 | -webkit-font-feature-settings: 'liga'; 22 | font-feature-settings: "liga"; 23 | -webkit-font-smoothing: antialiased; 24 | } 25 | 26 | material-symbol { 27 | height: var(--size); 28 | width: var(--size); 29 | } 30 | -------------------------------------------------------------------------------- /css/pip.css: -------------------------------------------------------------------------------- 1 | @import url(./background-material.css); 2 | 3 | body { 4 | background: #000000; 5 | } 6 | 7 | * { 8 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 9 | user-select: none; 10 | } 11 | 12 | .control { 13 | -webkit-app-region: none; 14 | opacity: 0.05; 15 | color: #222222; 16 | background: #DDDDDD; 17 | z-index: 1; 18 | --size: 24px; 19 | padding: 4px; 20 | border-radius: 8px; 21 | border: none; 22 | cursor: pointer; 23 | transition-duration: 200ms; 24 | 25 | &:hover { 26 | opacity: 1; 27 | } 28 | 29 | &.close { 30 | position: fixed; 31 | top: 20px; 32 | right: 20px; 33 | } 34 | 35 | &.sound { 36 | position: fixed; 37 | bottom: 20px; 38 | left: 20px; 39 | } 40 | 41 | &.fullscreen { 42 | position: fixed; 43 | bottom: 20px; 44 | right: 20px; 45 | 46 | @media (max-width: 110px) { 47 | display: none; 48 | } 49 | } 50 | 51 | @media (max-width: 350px) { 52 | &.backward { 53 | display: none; 54 | } 55 | 56 | &.forward { 57 | display: none; 58 | } 59 | } 60 | 61 | @media (max-width: 250px) { 62 | &.previous { 63 | display: none; 64 | } 65 | 66 | &.next { 67 | display: none; 68 | } 69 | } 70 | 71 | @media (max-width: 150px) { 72 | &.sound { 73 | display: none; 74 | } 75 | &.fullscreen { 76 | position: unset; 77 | } 78 | } 79 | } 80 | 81 | .main-controls { 82 | position: fixed; 83 | bottom: 20px; 84 | left: 20px; 85 | right: 20px; 86 | display: flex; 87 | align-items: center; 88 | justify-content: center; 89 | gap: 5px; 90 | 91 | &:hover .control:not(:hover) { 92 | opacity: 0.2; 93 | transition-duration: 50ms; 94 | } 95 | 96 | @media (max-height: 110px) { 97 | display: none; 98 | } 99 | } 100 | 101 | .time-slider { 102 | -webkit-app-region: none; 103 | box-sizing: border-box; 104 | accent-color: white; 105 | opacity: 0.05; 106 | position: fixed; 107 | bottom: 60px; 108 | right: 20px; 109 | left: 20px; 110 | transition-duration: 200ms; 111 | 112 | &:hover { 113 | opacity: 1; 114 | } 115 | 116 | @media (max-height: 170px) { 117 | display: none; 118 | } 119 | } 120 | 121 | .time-info { 122 | position: fixed; 123 | color: #ffffff; 124 | bottom: 90px; 125 | left: 40px; 126 | opacity: 0; 127 | -webkit-app-region: drag; 128 | transition-duration: 200ms; 129 | 130 | :is(body):has(.time-slider:hover) & { 131 | opacity: 1; 132 | } 133 | 134 | @media (max-height: 200px) { 135 | display: none; 136 | } 137 | 138 | @media (max-width: 300px) { 139 | display: none; 140 | } 141 | 142 | } 143 | 144 | .video-container { 145 | z-index: -1; 146 | 147 | & video { 148 | -webkit-app-region: drag; 149 | position: fixed; 150 | top: 0; 151 | left: 0; 152 | width: 100%; 153 | height: 100%; 154 | 155 | &::cue { 156 | background: color-mix(in srgb, #222222 75%, transparent); 157 | color: #DDDDDD; 158 | } 159 | } 160 | } 161 | 162 | input[type="range"] { 163 | height: 12px; 164 | appearance: none; 165 | background: transparent; 166 | border-radius: 5px; 167 | clip-path: inset(0 round 10px); 168 | } 169 | 170 | input[type="range"]::-webkit-slider-runnable-track { 171 | width: 100%; 172 | background: #222222; 173 | height: 12px; 174 | border-radius: 5px; 175 | } 176 | 177 | input[type="range"]::-webkit-slider-thumb { 178 | appearance: none; 179 | height: 12px; 180 | width: 12px; 181 | background: #cccccc; 182 | box-shadow: -100vw 0 0 calc(100vw - 8px) #888888; 183 | } -------------------------------------------------------------------------------- /fonts/material-symbols-rounded.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/fonts/material-symbols-rounded.woff2 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NewFlow 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 34 | 35 | 36 | 37 |
38 | 39 |
NewFlow
40 | 41 |
42 | 47 | 55 | 60 |
61 |
62 |
63 | 64 |
65 | local_fire_department 66 |
67 |
68 |
69 | rss_feed 70 |
71 |
72 |
73 | tv 74 |
75 |
76 |
77 | bookmarks 78 |
79 |
80 |
81 | history 82 |
83 |
84 |
85 | download 86 |
87 |
88 |
89 | settings 90 |
91 |
92 |
93 | info 94 |
95 |
96 | 100 |
101 | 102 |
103 | 104 | 105 |
106 |
107 |
108 | 109 | 110 | 111 | 126 | 127 | 133 | 145 | 146 | 147 | 148 | 162 |
163 | 164 |
165 |
166 |
167 | skip_previous 168 | fast_rewind 169 | play_arrow 170 | fast_forward 171 | skip_next 172 |
173 | volume_up 174 | 175 |
176 |
0:00/0:00
177 |
178 |
179 | movie 180 | menu_book 181 | slow_motion_video 182 | closed_caption 183 | settings 184 | fullscreen 185 |
186 |
187 |
188 | 189 |
Stream will be started at
190 |
191 |
192 |
193 |
194 | 195 |
196 |
197 | 198 |
199 |
200 |
201 | 202 | 203 | 204 | 205 | 206 | 207 |
208 |
209 |
210 |
Comments is not implemented
211 |
212 |
213 |
214 |
215 | 216 | 217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
Playlist
227 |
228 |
229 |
230 |
231 | 246 |
247 |
248 |
249 |
250 | 251 |
    252 |
  • 253 | Active Theme 254 | 255 |
  • 256 |
  • 257 | Background Material 258 | 259 |
  • 260 |
261 |
262 |
263 | 264 | Settings about media 265 |
266 |
267 | 268 | Settings about plugins 269 |
270 |
271 |
272 | NewFlow
273 | Developed by malisipi 274 |
275 |
276 |
277 |
278 | 279 |
280 |
281 |
282 |
283 | 284 |
285 |
286 |
287 | 288 | 289 | 290 |
291 |
292 | 293 | close 294 |
295 |
296 | 297 |
298 |
299 |
link
300 |
globe
301 |
302 |
303 | 304 | 305 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | 3 | process.chdir(electron.app.getAppPath()); 4 | 5 | const remote = require('@electron/remote/main'); 6 | const path = require("node:path"); 7 | const fs = require("node:fs"); 8 | 9 | remote.initialize(); 10 | 11 | electron.app.on("ready", async _ => { 12 | let window_config = {}; 13 | try { 14 | window_config = JSON.parse(await fs.readFileSync("./window_config.json")); 15 | } catch {}; 16 | window_config.material = {"mica":"mica","acrylic":"acrylic","noise":"noise"}[window_config.material] ?? "default"; 17 | window_config.transparent = window_config.material != "default"; 18 | 19 | window_config.args = [`--newflow-material=${window_config.material}`]; 20 | 21 | if(!window_config.transparent) { 22 | window_config.titlebar_style = ["default", "hidden"][Number(window_config.system_titlebar)]; 23 | window_config.titlebar_overlay = [false, {color: "#00000000", symbolColor: "#FFFFFF", height: 30}][Number(window_config.system_titlebar)]; 24 | if(window_config.system_titlebar){ 25 | window_config.args = [...window_config.args, "--newflow-system-titlebar"]; 26 | }; 27 | } else { 28 | window_config.titlebar_style = "default"; 29 | window_config.titlebar_overlay = false; 30 | } 31 | 32 | newflow = new electron.BrowserWindow({ 33 | width: 800, 34 | height: 600, 35 | frame: false, 36 | transparent: window_config.transparent, 37 | titleBarStyle: window_config.titlebar_style, 38 | titleBarOverlay: window_config.titlebar_overlay, 39 | webPreferences: { 40 | nodeIntegration: true, 41 | contextIsolation: false, 42 | nodeIntegrationInWorker: true, 43 | backgroundThrottling: false, 44 | preload: path.join(__dirname, 'preload.js'), 45 | additionalArguments: window_config.args 46 | } 47 | }); 48 | newflow.loadFile("index.html"); 49 | remote.enable(newflow.webContents); 50 | 51 | newflow.on("closed", _ => { 52 | electron.app.quit(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /js/backtrace.js: -------------------------------------------------------------------------------- 1 | window.onerror = (message, source, lineno, colno, err) => { 2 | alert(`@${lineno}/${colno}: ${message} 3 | Source: ${source}, 4 | Full Error: ${err}`); 5 | console.warn(`@${lineno}/${colno}: ${message} 6 | Source: ${source}, 7 | Full Error: ${err}`); 8 | return true; 9 | }; 10 | 11 | window.addEventListener("DOMContentLoaded", () => { 12 | components.tabs.watch.video.$video.addEventListener("error", async event => { 13 | alert(`${event.target.error.code} / ${event.target.error.message}`); 14 | console.error(event.target.error); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /js/context_menu.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malisipi/NewFlow/42d7b80e57654f042def2dce89f7a6e60ee84993/js/context_menu.js -------------------------------------------------------------------------------- /js/database.js: -------------------------------------------------------------------------------- 1 | var database = { 2 | following: new JsonDatabase("./dbs/following.json", {defaultStructure: []}), 3 | feed: new JsonDatabase("./dbs/feed.json"), 4 | playlists: new JsonDatabase("./dbs/playlists.json", { 5 | defaultStructure: { 6 | $liked_videos: { 7 | title: "Liked Videos", 8 | thumbnail: "./assets/inner/playlist_liked.png", 9 | list: [] 10 | }, 11 | $watch_later: { 12 | title: "Watch Later", 13 | thumbnail: "./assets/inner/playlist_watch_later.png", 14 | list: [] 15 | } 16 | }, 17 | fill_missing: true 18 | }), 19 | history: new JsonDatabase("./dbs/history.json", {defaultStructure: []}), 20 | settings: new JsonDatabase("./dbs/settings.json", {defaultStructureFrom: "./dbs/settings.template.json", fill_missing: true}) 21 | }; 22 | 23 | database.following.init(); 24 | database.following.add = (owner) => { 25 | /* 26 | * owner.id 27 | * owner.thumbnail (data-uri) 28 | * owner.name 29 | * owner.followers 30 | * owner.verified 31 | * owner.follow_time (ISODate) 32 | */ 33 | if(owner.id == null) return false; 34 | database.following.content.push({ 35 | id: owner.id, 36 | thumbnail: owner.thumbnail ?? null, 37 | name: owner.name ?? "Unknown Channel", 38 | followers: owner.followers ?? 0, 39 | verified: owner.verified ?? false, 40 | follow_time: (new Date).toISOString() 41 | }); 42 | database.following.update_file(); 43 | }; 44 | database.following.is_following = (owner_id) => { 45 | return database.following.content.filter(owner => owner.id == owner_id).length >= 1; 46 | }; 47 | database.following.remove = (owner_id) => { 48 | database.following.content = database.following.content.filter(owner => owner.id != owner_id); 49 | database.following.update_file(); 50 | }; 51 | database.feed.init(); 52 | database.feed.fetch = async () => { 53 | database.feed.content.fetch_time = (new Date).toISOString(); 54 | database.feed.content.feed = []; 55 | let owners = database.following.content.map(owner => owner.id); 56 | for(let owner_index=0; owner_index < owners.length; owner_index++){ 57 | let id = owners[owner_index]; 58 | database.feed.content.feed = [...database.feed.content.feed, ...(await yt_extractor.owner.get_owner_videos(id)).entry]; 59 | }; 60 | database.feed.content.feed.sort((a, b) => { 61 | if(new Date(a.published) < new Date(b.published)){ 62 | return 1; 63 | } else { 64 | return -1; 65 | } 66 | }); 67 | database.feed.update_file(); 68 | }; 69 | database.playlists.init(); 70 | database.history.init().then(() => { 71 | if (database.history.content.length > 2500) { 72 | console.warn("Huge watch history, Maybe you should clear or reduce the history?"); 73 | } 74 | }); 75 | database.history.add = (video) => { 76 | /* 77 | * video.id 78 | * video.owner_name 79 | * video.thumbnail (data-uri) 80 | * video.length 81 | * video.title 82 | * video.view_count (Counts of this user view count) 83 | * video.last_watch_time (ISODate) 84 | */ 85 | if(video.id == null) return false; 86 | let previous_record = database.history.content.filter($video => $video.id == video.id)?.[0]; 87 | let view_count = (previous_record?.view_count ?? 0) + 1; 88 | database.history.content = database.history.content.filter($video => $video.id != video.id); 89 | database.history.content.push({ 90 | id: video.id, 91 | owner_name: video.owner_name ?? "Unknown Owner", 92 | length: video.length ?? 0, 93 | thumbnail: video.thumbnail ?? null, 94 | title: video.title ?? "Unknown Title", 95 | view_count: view_count ?? 1, 96 | last_watch_time: (new Date).toISOString() 97 | }); 98 | database.history.update_file(); 99 | }; 100 | database.history.reduce = () => { 101 | let total_watch_count = database.history.content.reduce((total, video) => total + video.view_count, 0); 102 | let average_watch_count = total_watch_count / database.history.content.length; 103 | database.history.content = database.history.content.filter(video => video.view_count > average_watch_count); 104 | database.history.update_file(); 105 | }; 106 | database.settings.init(); 107 | -------------------------------------------------------------------------------- /js/downloader.js: -------------------------------------------------------------------------------- 1 | var downloader = { 2 | download: (uri, href) => { 3 | let $on = document.createElement("div"); 4 | $on.contentSize = 0; 5 | $on.uri = uri; 6 | $on.href = href; 7 | $on.state = "downloading"; 8 | let file = fs_legacy.createWriteStream(href); 9 | http_ = (new URL(uri).protocol == "http:") ? http : https; 10 | let request = http_.get(uri, response => { 11 | response.pipe(file); 12 | response.on("data", (e) => { 13 | $on.contentLength = Number(response.rawHeaders?.[(response.rawHeaders?.indexOf("Content-Length") ?? -1) + 1] ?? -1); 14 | $on.contentSize += e.length; 15 | $on.dispatchEvent(new CustomEvent("state-update")); 16 | }); 17 | 18 | file.on("finish", () => { 19 | file.close(); 20 | $on.state = "finish"; 21 | $on.dispatchEvent(new CustomEvent("downloaded")); 22 | }); 23 | }); 24 | return $on; 25 | } 26 | }; -------------------------------------------------------------------------------- /js/extensions/lyrics/index.js: -------------------------------------------------------------------------------- 1 | var lyrics = { 2 | components: { 3 | $: null, 4 | title: null, 5 | lyrics: null, 6 | get: null, 7 | info: null 8 | }, 9 | parser: new DOMParser(), 10 | get_lyrics: async (title) => { 11 | try { 12 | title = title.replace(/[\[\(\{].*[\]\)\}]/g,"").replace(/\|.*/g,"").trim(); 13 | let search_page = await fetch("https://genius.com/api/search/multi?per_page=5&q=" + encodeURIComponent(title)); 14 | let search_data = await search_page.json() 15 | let lyric_page = await fetch(search_data?.response?.sections?.[0]?.hits?.[0]?.result?.relationships_index_url) 16 | let lyric_page_data = await lyric_page.text() 17 | let lyric_page_document = lyrics.parser.parseFromString(lyric_page_data, "text/html"); 18 | return lyric_page_document.querySelector(`[data-lyrics-container="true"]`).innerHTML.replace(/\<[\\]*br\>/g,"\n").replace(/<[\\]*[^\>]+\>/g,""); 19 | } catch { 20 | return "Unable to extract lyrics"; 21 | } 22 | }, 23 | __load__: () => { 24 | lyrics.components.$ = document.createElement("div"); 25 | lyrics.components.$.style = "display: flex; flex-direction: column; gap: 5px;"; 26 | components.tabs.watch.panels.$.append(lyrics.components.$); 27 | lyrics.components.title = document.createElement("h1"); 28 | lyrics.components.title.innerText = "Lyrics"; 29 | lyrics.components.$.append(lyrics.components.title); 30 | lyrics.components.get = document.createElement("button"); 31 | lyrics.components.get.innerText = "Get Lyrics"; 32 | lyrics.components.get.addEventListener("click", async () => { 33 | let the_lyrics = await lyrics.get_lyrics(components.tabs.watch.$$response.title); 34 | lyrics.components.lyrics.innerText = the_lyrics; 35 | }); 36 | lyrics.components.get.style = "width: 100%; padding: 5px; background: var(--seconder-background-color); border: none; border-radius: var(--border-radius-size);"; 37 | lyrics.components.$.append(lyrics.components.get); 38 | lyrics.components.lyrics = document.createElement("div"); 39 | lyrics.components.lyrics.style = "user-select:text;"; 40 | lyrics.components.$.append(lyrics.components.lyrics); 41 | lyrics.components.info = document.createElement("div"); 42 | lyrics.components.info.innerText = "Lyric data is taken from Genius.\nThis plugin is not supported by Genius."; 43 | lyrics.components.$.append(lyrics.components.info); 44 | }, 45 | __unload__: () => { 46 | lyrics.components.$.remove(); 47 | lyrics = undefined; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /js/extensions/lyrics/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lyrics for NewFlow", 3 | "description": "Find lyrics of your music videos", 4 | "developer": "malisipi", 5 | "version": "1.0.0" 6 | } -------------------------------------------------------------------------------- /js/image_helper.js: -------------------------------------------------------------------------------- 1 | var image_helper = { 2 | crop: { 3 | as_square: (src, size, orig_width, orig_height) => { 4 | return new Promise(resolve => { 5 | let image = document.createElement("img"); 6 | image.addEventListener("load", () => { 7 | let canvas = document.createElement("canvas"); 8 | canvas.width = size; 9 | canvas.height = size; 10 | let ctx = canvas.getContext("2d"); 11 | 12 | square_size = Math.min(orig_width,orig_height); 13 | multiplier = 256/square_size; 14 | max_size = Math.max(orig_width,orig_height); 15 | crop_size = (max_size-square_size)/2; 16 | is_from_left = square_size == orig_height; 17 | if(is_from_left) { 18 | ctx.drawImage(image, -crop_size*multiplier, 0, max_size*multiplier, 256); 19 | } else { 20 | ctx.drawImage(image, 0, -crop_size*multiplier, 256, max_size*multiplier); 21 | } 22 | resolve(canvas.toDataURL()); 23 | }); 24 | image.src = src; 25 | }); 26 | } 27 | }, 28 | data_uri: { 29 | from_image_uri: async (uri, type="jpg") => { 30 | let image_content = await fetch(uri); 31 | let image_content_as_array_buffer = await image_content.arrayBuffer(); 32 | return "data:image/"+ type + ";base64," + Buffer.from(image_content_as_array_buffer).toString("base64") 33 | } 34 | }, 35 | dominant_color: (uri) => { 36 | // Vibrant Library 37 | if(Vibrant != null) { 38 | return new Promise(async (res) => { 39 | try { 40 | let vib = new Vibrant(uri); 41 | let palette = await vib.getPalette(); 42 | res((palette.Vibrant ?? 43 | palette.Muted ?? 44 | palette.DarkVibrant ?? 45 | palette.LightVibrant ?? 46 | palette.DarkMuted ?? 47 | palette.LightMuted)?.getHex() ?? "#777777"); 48 | } catch { 49 | res("#777777"); 50 | } 51 | }); 52 | }; 53 | // Browser implementation 54 | return new Promise((res) => { 55 | let the_image = document.createElement("img"); 56 | the_image.addEventListener("load", () => { 57 | let canvas = document.createElement("canvas"); 58 | canvas.width = 1; 59 | canvas.height = 1; 60 | let ctx = canvas.getContext("2d"); 61 | ctx.drawImage(the_image, 0,0,1,1); 62 | let dominant_pixel = ctx.getImageData(0,0,1,1); 63 | let dominant_color = "#"+Array.from(dominant_pixel.data).map(data=>data.toString(16).padStart(2, "0")).join(""); 64 | res(dominant_color.slice(0,7)); // remove alpha from color 65 | }); 66 | the_image.src = uri; 67 | }); 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /js/imports.js: -------------------------------------------------------------------------------- 1 | var yt_extractor = require("./yt-extractor"); 2 | var fs = require("node:fs/promises"); 3 | var path = require('node:path'); 4 | window.addEventListener("DocumentReady", () => { 5 | window.http = require("node:http"); 6 | window.https = require("node:https"); 7 | window.fs_legacy = require("node:fs"); 8 | window.child_process = require("node:child_process"); 9 | }) 10 | 11 | fs.exists = async (file_name) => { 12 | try { 13 | await fs.realpath(file_name); 14 | return true; 15 | } catch { 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /js/json_database.js: -------------------------------------------------------------------------------- 1 | class JsonDatabase { 2 | #config; 3 | /* 4 | * defaultStructure(null) 5 | * defaultStructureFrom(null) 6 | * fill_missing(false) 7 | */ 8 | file_name; 9 | content; 10 | 11 | async init (){ 12 | this.autosave = this.#config.disable_autosave != true; 13 | 14 | let content = this.#config.defaultStructure ?? "{}"; 15 | try { 16 | content = String(await fs.readFile(this.file_name)); 17 | } catch {}; 18 | if(typeof(content) == "object") { 19 | this.content = content; 20 | } else { 21 | this.content = JSON.parse(content); 22 | }; 23 | await this.#fill_missing(); 24 | } 25 | 26 | async #fill_missing (){ 27 | //this.#config.defaultStructure 28 | } 29 | 30 | async update_file (){ 31 | console.warn("Updated: " + this.file_name); 32 | await fs.writeFile(this.file_name, JSON.stringify(this.content)) 33 | } 34 | 35 | constructor(file_name, config = {}) { 36 | this.file_name = file_name; 37 | this.#config = config; 38 | return this; 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /js/keyboard_shortcuts.js: -------------------------------------------------------------------------------- 1 | document.body.addEventListener("keydown", event => { 2 | if(document.activeElement.tagName.toLowerCase() === "input") return; 3 | switch(components.tabs.$active()){ 4 | case "watch": { 5 | switch(event.code){ 6 | case "KeyK": 7 | case "Space": { 8 | components.tabs.watch.video.$_play_pause(); 9 | break; 10 | }; 11 | case "KeyJ": 12 | case "ArrowLeft": { 13 | components.tabs.watch.video.$_seekby(-10); 14 | break; 15 | }; 16 | case "KeyL": 17 | case "ArrowRight": { 18 | components.tabs.watch.video.$_seekby(10); 19 | break; 20 | }; 21 | case "KeyP": { 22 | components.tabs.watch.video.$_previous(); 23 | break; 24 | }; 25 | case "KeyN": { 26 | components.tabs.watch.video.$_next(); 27 | break; 28 | }; 29 | case "KeyT": { 30 | components.tabs.watch.video.$_theatre(); 31 | break; 32 | }; 33 | case "KeyF": { 34 | components.tabs.watch.video.$_fullscreen(); 35 | break; 36 | }; 37 | case "ArrowUp": { 38 | components.tabs.watch.video.$_volume(Math.min(components.tabs.watch.video.$volume()+0.05,1)); 39 | break; 40 | }; 41 | case "ArrowDown": { 42 | components.tabs.watch.video.$_volume(Math.max(components.tabs.watch.video.$volume()-0.05,0)); 43 | break; 44 | }; 45 | default: { 46 | return; 47 | } 48 | } 49 | break; 50 | } 51 | default: { 52 | return; 53 | } 54 | }; 55 | event.preventDefault(); 56 | }); -------------------------------------------------------------------------------- /js/language_pack.js: -------------------------------------------------------------------------------- 1 | var i18n = { 2 | language_package: {}, 3 | get: async () => { 4 | let languages = await fetch("./languages.json"); 5 | languages = await languages.json(); 6 | let active_language = navigator.language; 7 | i18n.language_package = Object.keys(languages).map( 8 | key => ({ 9 | key:key, value:languages[key][active_language] ?? languages[key]["en"] ?? key 10 | }) 11 | ).reduce((pack, word) => { 12 | pack[word.key] = word.value; 13 | return pack; 14 | }, {}); 15 | }, 16 | text: (key) => { 17 | return i18n.language_package[key] ?? key; 18 | }, 19 | ready: null 20 | }; 21 | 22 | i18n.ready = window.i18n.get(); 23 | 24 | window.addEventListener("DOMContentLoaded", async () => { 25 | await i18n.ready; 26 | document.querySelectorAll("language-pack").forEach(text => text.replaceWith(i18n.text(text.getAttribute("key")))); 27 | }) -------------------------------------------------------------------------------- /js/pip.js: -------------------------------------------------------------------------------- 1 | var components = { 2 | $_human_readable_time: time => `${(time>=3600)?Math.floor(time/3600)+":":""}${(String(Math.floor(time/60)%60).length==1 && time>=3600)?"0":""}${Math.floor(time/60)%60}:${String(Math.floor(time%60)).padStart(2,"0")}`, 3 | $_window: null, 4 | controls: { 5 | close: document.querySelector(".close"), 6 | time_slider: document.querySelector(".time-slider"), 7 | sound: document.querySelector(".sound"), 8 | previous: document.querySelector(".previous"), 9 | backward: document.querySelector(".backward"), 10 | play_pause: document.querySelector(".play-pause"), 11 | forward: document.querySelector(".forward"), 12 | next: document.querySelector(".next"), 13 | fullscreen: document.querySelector(".fullscreen"), 14 | time_info: { 15 | current_time: document.querySelector(".current-time"), 16 | duration: document.querySelector(".duration") 17 | } 18 | }, 19 | video: null, 20 | __interval: setInterval(() => { 21 | let video_component = document.querySelector("video"); 22 | if(!!video_component) { 23 | console.log("Video conntected to PiP"); 24 | components.video = video_component; 25 | clearInterval(components.__interval); 26 | components.$_add_temp_listeners(); 27 | }; 28 | }, 100), 29 | __abort_controller: new AbortController(), 30 | $_add_temp_listeners: () => { 31 | components.video.addEventListener("pause", () => { 32 | components.controls.play_pause.innerText = "play_arrow"; 33 | }, {signal: components.__abort_controller.signal}); 34 | components.video.addEventListener("play", () => { 35 | components.controls.play_pause.innerText = "pause"; 36 | }, {signal: components.__abort_controller.signal}); 37 | components.controls.play_pause.innerText = ["pause","play_arrow"][Number(components.video.paused)]; 38 | 39 | components.video.addEventListener("durationchange", () => { 40 | time = components.video.duration; 41 | components.controls.time_slider.max = time; 42 | components.controls.time_info.duration.innerText = components.$_human_readable_time(time); 43 | }, {signal: components.__abort_controller.signal}); 44 | components.video.dispatchEvent(new Event("durationchange")); 45 | 46 | components.video.addEventListener("timeupdate", () => { 47 | let time = components.video.currentTime; 48 | components.controls.time_slider.value = time; 49 | components.controls.time_info.current_time.innerText = components.$_human_readable_time(time); 50 | }, {signal: components.__abort_controller.signal}); 51 | 52 | components.video.$$$video.$audio.addEventListener("volumechange", () => { 53 | components.controls.sound.innerText = ["volume_up", "volume_off"][Number(components.video.$$$video.$muted())]; 54 | }, {signal: components.__abort_controller.signal}); 55 | components.video.$$$video.$audio.dispatchEvent(new Event("volumechange")); 56 | } 57 | }; 58 | 59 | components.controls.close.addEventListener("click", () => { 60 | window.close(); 61 | }); 62 | 63 | components.controls.sound.addEventListener("click", () => { 64 | components.video.$$$video.$_mute(); 65 | }); 66 | components.controls.previous.addEventListener("click", () => { 67 | components.video.$$$video.$_previous(); 68 | }); 69 | components.controls.backward.addEventListener("click", () => { 70 | components.video.$$$video.$_seekby(-5); 71 | }); 72 | components.controls.play_pause.addEventListener("click", () => { 73 | components.video.$$$video.$_play_pause(); 74 | }); 75 | components.controls.forward.addEventListener("click", () => { 76 | components.video.$$$video.$_seekby(5); 77 | }); 78 | components.controls.next.addEventListener("click", () => { 79 | components.video.$$$video.$_next(); 80 | }); 81 | components.controls.time_slider.addEventListener("input", () => { 82 | components.video.currentTime = components.controls.time_slider.value; 83 | }); 84 | 85 | electron_loaded = () => { 86 | components.$_window = electron.BrowserWindow.getAllWindows().filter(_window => _window.getTitle() == document.title)[0]; 87 | components.controls.fullscreen.addEventListener("click", () => { 88 | if(components.$_window.isFullScreen()) { 89 | components.$_window.setFullScreen(false); 90 | components.controls.fullscreen.innerText = "fullscreen"; 91 | } else { 92 | components.$_window.setFullScreen(true); 93 | components.controls.fullscreen.innerText = "fullscreen_exit"; 94 | } 95 | }); 96 | 97 | // Set always on top (not works @ Linux/Wayland) 98 | components.$_window.setAlwaysOnTop(true); 99 | } 100 | 101 | // Add event listeners to abort all event listeners 102 | 103 | window.onunload = () => { 104 | components.__abort_controller.abort(); 105 | } 106 | -------------------------------------------------------------------------------- /js/plugins.js: -------------------------------------------------------------------------------- 1 | var plugins = { 2 | list: () => {}, 3 | load: (plugin_id) => { 4 | console.info(`Plugin: ${plugin_id} is loaded`); 5 | let plugin_script = document.createElement("script"); 6 | document.head.append(plugin_script); 7 | plugin_script.setAttribute("plugin", plugin_id); 8 | plugin_script.addEventListener("load", (event) => { 9 | window[plugin_id].__load__(); 10 | }); 11 | plugin_script.src = `./js/extensions/${plugin_id}/index.js`; 12 | }, 13 | unload: (plugin_id) => { 14 | console.info(`Plugin: ${plugin_id} is unloaded`); 15 | Array.from(document.head.querySelectorAll("script[plugin]")).filter(a=>a.getAttribute("plugin") == plugin_id)?.[0]?.remove(); 16 | window[plugin_id].__unload__(); 17 | } 18 | }; -------------------------------------------------------------------------------- /languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "trends": { 3 | "en": "Trends", 4 | "tr": "Popüler" 5 | }, 6 | "feed": { 7 | "en": "Feed", 8 | "tr": "Akış" 9 | }, 10 | "following": { 11 | "en": "Following", 12 | "tr": "Takip Edilenler" 13 | }, 14 | "playlists": { 15 | "en": "Playlists", 16 | "tr": "Oynatma Listeleri" 17 | }, 18 | "history": { 19 | "en": "History", 20 | "tr": "Geçmiş" 21 | }, 22 | "downloads": { 23 | "en": "Downloads", 24 | "tr": "İndirilenler" 25 | }, 26 | "settings": { 27 | "en": "Settings", 28 | "tr": "Ayarlar" 29 | }, 30 | "about_n_faq": { 31 | "en": "About & FAQ", 32 | "tr": "Hakkında & SSS" 33 | }, 34 | "devtools": { 35 | "en": "Devtools", 36 | "tr": "Geliştirici Araçları" 37 | }, 38 | "playback_speed": { 39 | "en": "Playback Speed", 40 | "tr": "Oynatma Hızı" 41 | }, 42 | "normal": { 43 | "en": "Normal", 44 | "tr": "Normal" 45 | }, 46 | "loading": { 47 | "en": "Loading..", 48 | "tr": "Hazırlanıyor.." 49 | }, 50 | "warning_not_family_safe_video": { 51 | "en": "It is not a family safe video. Do you want to watch it?", 52 | "tr": "Bu video aile ortamına uygun değil. İzlemek istediğinize emin misin?" 53 | }, 54 | "followers": { 55 | "en": "Followers", 56 | "tr": "Takipçi" 57 | }, 58 | "auto_add_to_queue": { 59 | "en": "Automatically add to queue", 60 | "tr": "Kendiliğinden sıraya ekle" 61 | }, 62 | "video_quality": { 63 | "en": "Video Quality", 64 | "tr": "Video Kalitesi" 65 | }, 66 | "audio_quality": { 67 | "en": "Audio Quality", 68 | "tr": "Ses Kalitesi" 69 | }, 70 | "audio_pitch": { 71 | "en": "Audio Patch", 72 | "tr": "Ses Perdesi" 73 | }, 74 | "captions": { 75 | "en": "Captions", 76 | "tr": "Alt Yazılar" 77 | }, 78 | "filters": { 79 | "en": "Filters", 80 | "tr": "Filtreler" 81 | }, 82 | "disable": { 83 | "en": "Disable", 84 | "tr": "Devre Dışı" 85 | }, 86 | "disabled_while_debug_mode": { 87 | "en": "Disabled while debug mode", 88 | "tr": "Hata ayıklama modunda devre dışı bırakıldı" 89 | }, 90 | "share": { 91 | "en": "Share", 92 | "tr": "Paylaş" 93 | }, 94 | "timelines": { 95 | "en": "Timelines", 96 | "tr": "Zaman Damgaları" 97 | }, 98 | "kaomoji_dog": { 99 | "en": "∪・ェ・∪" 100 | }, 101 | "kaomoji_bird": { 102 | "en": "(。・ө・。)" 103 | }, 104 | "there_are_nothing_more_dog": { 105 | "en": "There're nothing more than a dog that wants play with you", 106 | "tr": "Seninle oyun oynamak isteyen bir köpek dışında bir şey yok" 107 | }, 108 | "no_network_bird": { 109 | "en": "There're a bird who waits network to fly", 110 | "tr": "Burada uçabilmek için interneti bekleyen bir kuş var" 111 | }, 112 | "brightness": { 113 | "en": "Brightness", 114 | "tr": "Parlaklık" 115 | }, 116 | "contrast": { 117 | "en": "Contrast", 118 | "tr": "Karşıtlık" 119 | }, 120 | "blur": { 121 | "en": "Blur", 122 | "tr": "Bulanıklaştırma" 123 | }, 124 | "grayscale": { 125 | "en": "Grayscale", 126 | "tr": "Gri Tonlama" 127 | }, 128 | "invert": { 129 | "en": "Invert", 130 | "tr": "Ters Çevirme" 131 | }, 132 | "hue_rotate": { 133 | "en": "Hue Rotate", 134 | "tr": "Ton Değiştirme" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /linux-setup: -------------------------------------------------------------------------------- 1 | mkdir "$HOME/opt/" 2 | cd "$HOME/opt/" 3 | git clone https://github.com/malisipi/NewFlow 4 | cd NewFlow 5 | git clone https://github.com/malisipi/yt-extractor.js 6 | mv "yt-extractor.js" "yt-extractor" 7 | ./scripts/install-desktop-file -------------------------------------------------------------------------------- /node_modules/@electron/remote/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2022 Electron contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/README.md: -------------------------------------------------------------------------------- 1 | # @electron/remote 2 | 3 | [![CircleCI build status](https://circleci.com/gh/electron/remote/tree/main.svg?style=shield)](https://circleci.com/gh/electron/remote/tree/main) 4 | [![npm version](http://img.shields.io/npm/v/@electron/remote.svg)](https://npmjs.org/package/@electron/remote) 5 | 6 | `@electron/remote` is an [Electron](https://electronjs.org) module that bridges 7 | JavaScript objects from the main process to the renderer process. This lets you 8 | access main-process-only objects as if they were available in the renderer 9 | process. 10 | 11 | > ⚠️ **Warning!** This module has [many subtle 12 | > pitfalls][remote-considered-harmful]. There is almost always a better way to 13 | > accomplish your task than using this module. For example, [`ipcRenderer.invoke`](https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args) can serve many common use cases. 14 | 15 | `@electron/remote` is a replacement for the built-in `remote` module in 16 | Electron, which is deprecated and will eventually be removed. 17 | 18 | ## Migrating from `remote` 19 | 20 | > **NOTE:** `@electron/remote` requires Electron 10 or higher. 21 | 22 | There are three things you need to do to migrate from the built-in `remote` 23 | module to `@electron/remote`. 24 | 25 | First, you need to install it from NPM: 26 | 27 | ```shell 28 | $ npm install --save @electron/remote 29 | ``` 30 | 31 | Second, `@electron/remote/main` must be initialized in the main 32 | process before it can be used from the renderer: 33 | 34 | ```javascript 35 | // in the main process: 36 | require('@electron/remote/main').initialize() 37 | ``` 38 | 39 | Third, `require('electron').remote` in the renderer process must be 40 | replaced with `require('@electron/remote')`. 41 | 42 | ```javascript 43 | // in the renderer process: 44 | 45 | // Before 46 | const { BrowserWindow } = require('electron').remote 47 | 48 | // After 49 | const { BrowserWindow } = require('@electron/remote') 50 | ``` 51 | 52 | **Note:** Since this is requiring a module through npm rather than a built-in 53 | module, if you're using `remote` from a sandboxed process, you'll need to 54 | configure your bundler appropriately to package the code of `@electron/remote` 55 | in the preload script. Of course, [using `@electron/remote` makes the sandbox 56 | much less effective][remote-considered-harmful]. 57 | 58 | **Note:** In `electron >= 14.0.0`, you must use the new `enable` API to enable the remote module for each desired `WebContents` separately: `require("@electron/remote/main").enable(webContents)`. 59 | 60 | In `electron < 14.0.0`, `@electron/remote` respects the `enableRemoteModule` WebPreferences 61 | value. You must pass `{ webPreferences: { enableRemoteModule: true } }` to 62 | the constructor of `BrowserWindow`s that should be granted permission to use 63 | `@electron/remote`. 64 | 65 | # API Reference 66 | 67 | The `remote` module provides a simple way to do inter-process communication 68 | (IPC) between the renderer process (web page) and the main process. 69 | 70 | In Electron, GUI-related modules (such as `dialog`, `menu` etc.) are only 71 | available in the main process, not in the renderer process. In order to use them 72 | from the renderer process, the `ipc` module is necessary to send inter-process 73 | messages to the main process. With the `remote` module, you can invoke methods 74 | of the main process object without explicitly sending inter-process messages, 75 | similar to Java's [RMI][rmi]. An example of creating a browser window from a 76 | renderer process: 77 | 78 | ```javascript 79 | const { BrowserWindow } = require('@electron/remote') 80 | let win = new BrowserWindow({ width: 800, height: 600 }) 81 | win.loadURL('https://github.com') 82 | ``` 83 | 84 | In order for this to work, you first need to initialize the main-process side 85 | of the remote module: 86 | 87 | ```javascript 88 | // in the main process: 89 | require('@electron/remote/main').initialize() 90 | ``` 91 | 92 | **Note:** In `electron >= 14.0.0` the remote module is disabled by default for any `WebContents` instance and is only enabled for specified `WebContents` after explicitly calling `require("@electron/remote/main").enable(webContents)`. 93 | 94 | In `electron < 14.0.0` the remote module can be disabled for security reasons in the following contexts: 95 | - [`BrowserWindow`](https://www.electronjs.org/docs/latest/api/browser-window) - by setting the `enableRemoteModule` option to `false`. 96 | - [``](https://www.electronjs.org/docs/latest/api/webview-tag) - by setting the `enableremotemodule` attribute to `false`. 97 | 98 | ## Remote Objects 99 | 100 | Each object (including functions) returned by the `remote` module represents an 101 | object in the main process (we call it a remote object or remote function). 102 | When you invoke methods of a remote object, call a remote function, or create 103 | a new object with the remote constructor (function), you are actually sending 104 | synchronous inter-process messages. 105 | 106 | In the example above, both `BrowserWindow` and `win` were remote objects and 107 | `new BrowserWindow` didn't create a `BrowserWindow` object in the renderer 108 | process. Instead, it created a `BrowserWindow` object in the main process and 109 | returned the corresponding remote object in the renderer process, namely the 110 | `win` object. 111 | 112 | **Note:** Only [enumerable properties][enumerable-properties] which are present 113 | when the remote object is first referenced are accessible via remote. 114 | 115 | **Note:** Arrays and Buffers are copied over IPC when accessed via the `remote` 116 | module. Modifying them in the renderer process does not modify them in the main 117 | process and vice versa. 118 | 119 | ## Lifetime of Remote Objects 120 | 121 | Electron makes sure that as long as the remote object in the renderer process 122 | lives (in other words, has not been garbage collected), the corresponding object 123 | in the main process will not be released. When the remote object has been 124 | garbage collected, the corresponding object in the main process will be 125 | dereferenced. 126 | 127 | If the remote object is leaked in the renderer process (e.g. stored in a map but 128 | never freed), the corresponding object in the main process will also be leaked, 129 | so you should be very careful not to leak remote objects. 130 | 131 | Primary value types like strings and numbers, however, are sent by copy. 132 | 133 | ## Passing callbacks to the main process 134 | 135 | Code in the main process can accept callbacks from the renderer - for instance 136 | the `remote` module - but you should be extremely careful when using this 137 | feature. 138 | 139 | First, in order to avoid deadlocks, the callbacks passed to the main process 140 | are called asynchronously. You should not expect the main process to 141 | get the return value of the passed callbacks. 142 | 143 | For instance you can't use a function from the renderer process in an 144 | `Array.map` called in the main process: 145 | 146 | ```javascript 147 | // main process mapNumbers.js 148 | exports.withRendererCallback = (mapper) => { 149 | return [1, 2, 3].map(mapper) 150 | } 151 | 152 | exports.withLocalCallback = () => { 153 | return [1, 2, 3].map(x => x + 1) 154 | } 155 | ``` 156 | 157 | ```javascript 158 | // renderer process 159 | const mapNumbers = require('@electron/remote').require('./mapNumbers') 160 | const withRendererCb = mapNumbers.withRendererCallback(x => x + 1) 161 | const withLocalCb = mapNumbers.withLocalCallback() 162 | 163 | console.log(withRendererCb, withLocalCb) 164 | // [undefined, undefined, undefined], [2, 3, 4] 165 | ``` 166 | 167 | As you can see, the renderer callback's synchronous return value was not as 168 | expected, and didn't match the return value of an identical callback that lives 169 | in the main process. 170 | 171 | Second, the callbacks passed to the main process will persist until the 172 | main process garbage-collects them. 173 | 174 | For example, the following code seems innocent at first glance. It installs a 175 | callback for the `close` event on a remote object: 176 | 177 | ```javascript 178 | require('@electron/remote').getCurrentWindow().on('close', () => { 179 | // window was closed... 180 | }) 181 | ``` 182 | 183 | But remember the callback is referenced by the main process until you 184 | explicitly uninstall it. If you do not, each time you reload your window the 185 | callback will be installed again, leaking one callback for each restart. 186 | 187 | To make things worse, since the context of previously installed callbacks has 188 | been released, exceptions will be raised in the main process when the `close` 189 | event is emitted. 190 | 191 | To avoid this problem, ensure you clean up any references to renderer callbacks 192 | passed to the main process. This involves cleaning up event handlers, or 193 | ensuring the main process is explicitly told to dereference callbacks that came 194 | from a renderer process that is exiting. 195 | 196 | ## Accessing built-in modules in the main process 197 | 198 | The built-in modules in the main process are added as getters in the `remote` 199 | module, so you can use them directly like the `electron` module. 200 | 201 | ```javascript 202 | const app = require('@electron/remote').app 203 | console.log(app) 204 | ``` 205 | 206 | ## Methods 207 | 208 | The `remote` module has the following methods: 209 | 210 | ### `remote.require(module)` 211 | 212 | * `module` String 213 | 214 | Returns `any` - The object returned by `require(module)` in the main process. 215 | Modules specified by their relative path will resolve relative to the entrypoint 216 | of the main process. 217 | 218 | e.g. 219 | 220 | ```sh 221 | project/ 222 | ├── main 223 | │ ├── foo.js 224 | │ └── index.js 225 | ├── package.json 226 | └── renderer 227 | └── index.js 228 | ``` 229 | 230 | ```js 231 | // main process: main/index.js 232 | const { app } = require('@electron/remote') 233 | app.whenReady().then(() => { /* ... */ }) 234 | ``` 235 | 236 | ```js 237 | // some relative module: main/foo.js 238 | module.exports = 'bar' 239 | ``` 240 | 241 | ```js 242 | // renderer process: renderer/index.js 243 | const foo = require('@electron/remote').require('./foo') // bar 244 | ``` 245 | 246 | ### `remote.getCurrentWindow()` 247 | 248 | Returns `BrowserWindow` - The window to which this web page belongs. 249 | 250 | **Note:** Do not use `removeAllListeners` on `BrowserWindow`. Use of this can 251 | remove all [`blur`](https://developer.mozilla.org/en-US/docs/Web/Events/blur) 252 | listeners, disable click events on touch bar buttons, and other unintended 253 | consequences. 254 | 255 | ### `remote.getCurrentWebContents()` 256 | 257 | Returns `WebContents` - The web contents of this web page. 258 | 259 | ### `remote.getGlobal(name)` 260 | 261 | * `name` String 262 | 263 | Returns `any` - The global variable of `name` (e.g. `global[name]`) in the main 264 | process. 265 | 266 | ## Properties 267 | 268 | ### `remote.process` _Readonly_ 269 | 270 | A `NodeJS.Process` object. The `process` object in the main process. This is the same as 271 | `remote.getGlobal('process')` but is cached. 272 | 273 | # Overriding exposed objects 274 | 275 | Without filtering, `@electron/remote` will provide access to any JavaScript 276 | object that any renderer requests. In order to control what can be accessed, 277 | `@electron/remote` provides an opportunity to the app to return a custom result 278 | for any of `getGlobal`, `require`, `getCurrentWindow`, `getCurrentWebContents`, 279 | or any of the builtin module properties. 280 | 281 | The following events will be emitted first on the `app` Electron module, and 282 | then on the specific `WebContents` which requested the object. When emitted on 283 | the `app` module, the first parameter after the `Event` object will be the 284 | `WebContents` which originated the request. If any handler calls 285 | `preventDefault`, the request will be denied. If a `returnValue` parameter is 286 | set on the result, then that value will be returned to the renderer instead of 287 | the default. 288 | 289 | ## Events 290 | 291 | ### Event: 'remote-require' 292 | 293 | Returns: 294 | 295 | * `event` Event 296 | * `moduleName` String 297 | 298 | Emitted when `remote.require()` is called in the renderer process of `webContents`. 299 | Calling `event.preventDefault()` will prevent the module from being returned. 300 | Custom value can be returned by setting `event.returnValue`. 301 | 302 | ### Event: 'remote-get-global' 303 | 304 | Returns: 305 | 306 | * `event` Event 307 | * `globalName` String 308 | 309 | Emitted when `remote.getGlobal()` is called in the renderer process of `webContents`. 310 | Calling `event.preventDefault()` will prevent the global from being returned. 311 | Custom value can be returned by setting `event.returnValue`. 312 | 313 | ### Event: 'remote-get-builtin' 314 | 315 | Returns: 316 | 317 | * `event` Event 318 | * `moduleName` String 319 | 320 | Emitted when `remote.getBuiltin()` is called in the renderer process of 321 | `webContents`, including when a builtin module is accessed as a property (e.g. 322 | `require("@electron/remote").BrowserWindow`). 323 | Calling `event.preventDefault()` will prevent the module from being returned. 324 | Custom value can be returned by setting `event.returnValue`. 325 | 326 | ### Event: 'remote-get-current-window' 327 | 328 | Returns: 329 | 330 | * `event` Event 331 | 332 | Emitted when `remote.getCurrentWindow()` is called in the renderer process of `webContents`. 333 | Calling `event.preventDefault()` will prevent the object from being returned. 334 | Custom value can be returned by setting `event.returnValue`. 335 | 336 | ### Event: 'remote-get-current-web-contents' 337 | 338 | Returns: 339 | 340 | * `event` Event 341 | 342 | Emitted when `remote.getCurrentWebContents()` is called in the renderer process of `webContents`. 343 | Calling `event.preventDefault()` will prevent the object from being returned. 344 | Custom value can be returned by setting `event.returnValue`. 345 | 346 | [rmi]: https://en.wikipedia.org/wiki/Java_remote_method_invocation 347 | [enumerable-properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties 348 | [remote-considered-harmful]: https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31 349 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/get-electron-binding.d.ts: -------------------------------------------------------------------------------- 1 | export declare const getElectronBinding: typeof process.electronBinding; 2 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/get-electron-binding.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.getElectronBinding = void 0; 4 | const getElectronBinding = (name) => { 5 | if (process._linkedBinding) { 6 | return process._linkedBinding('electron_common_' + name); 7 | } 8 | else if (process.electronBinding) { 9 | return process.electronBinding(name); 10 | } 11 | else { 12 | return null; 13 | } 14 | }; 15 | exports.getElectronBinding = getElectronBinding; 16 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/ipc-messages.d.ts: -------------------------------------------------------------------------------- 1 | export declare const enum IPC_MESSAGES { 2 | BROWSER_REQUIRE = "REMOTE_BROWSER_REQUIRE", 3 | BROWSER_GET_BUILTIN = "REMOTE_BROWSER_GET_BUILTIN", 4 | BROWSER_GET_GLOBAL = "REMOTE_BROWSER_GET_GLOBAL", 5 | BROWSER_GET_CURRENT_WINDOW = "REMOTE_BROWSER_GET_CURRENT_WINDOW", 6 | BROWSER_GET_CURRENT_WEB_CONTENTS = "REMOTE_BROWSER_GET_CURRENT_WEB_CONTENTS", 7 | BROWSER_CONSTRUCTOR = "REMOTE_BROWSER_CONSTRUCTOR", 8 | BROWSER_FUNCTION_CALL = "REMOTE_BROWSER_FUNCTION_CALL", 9 | BROWSER_MEMBER_CONSTRUCTOR = "REMOTE_BROWSER_MEMBER_CONSTRUCTOR", 10 | BROWSER_MEMBER_CALL = "REMOTE_BROWSER_MEMBER_CALL", 11 | BROWSER_MEMBER_GET = "REMOTE_BROWSER_MEMBER_GET", 12 | BROWSER_MEMBER_SET = "REMOTE_BROWSER_MEMBER_SET", 13 | BROWSER_DEREFERENCE = "REMOTE_BROWSER_DEREFERENCE", 14 | BROWSER_CONTEXT_RELEASE = "REMOTE_BROWSER_CONTEXT_RELEASE", 15 | BROWSER_WRONG_CONTEXT_ERROR = "REMOTE_BROWSER_WRONG_CONTEXT_ERROR", 16 | RENDERER_CALLBACK = "REMOTE_RENDERER_CALLBACK", 17 | RENDERER_RELEASE_CALLBACK = "REMOTE_RENDERER_RELEASE_CALLBACK" 18 | } 19 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/ipc-messages.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/module-names.d.ts: -------------------------------------------------------------------------------- 1 | export declare const commonModuleNames: string[]; 2 | export declare const browserModuleNames: string[]; 3 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/module-names.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var _a, _b; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | exports.browserModuleNames = exports.commonModuleNames = void 0; 5 | const get_electron_binding_1 = require("./get-electron-binding"); 6 | exports.commonModuleNames = [ 7 | 'clipboard', 8 | 'nativeImage', 9 | 'shell', 10 | ]; 11 | exports.browserModuleNames = [ 12 | 'app', 13 | 'autoUpdater', 14 | 'BaseWindow', 15 | 'BrowserView', 16 | 'BrowserWindow', 17 | 'contentTracing', 18 | 'crashReporter', 19 | 'dialog', 20 | 'globalShortcut', 21 | 'ipcMain', 22 | 'inAppPurchase', 23 | 'Menu', 24 | 'MenuItem', 25 | 'nativeTheme', 26 | 'net', 27 | 'netLog', 28 | 'MessageChannelMain', 29 | 'Notification', 30 | 'powerMonitor', 31 | 'powerSaveBlocker', 32 | 'protocol', 33 | 'pushNotifications', 34 | 'safeStorage', 35 | 'screen', 36 | 'session', 37 | 'ShareMenu', 38 | 'systemPreferences', 39 | 'TopLevelWindow', 40 | 'TouchBar', 41 | 'Tray', 42 | 'utilityProcess', 43 | 'View', 44 | 'webContents', 45 | 'WebContentsView', 46 | 'webFrameMain', 47 | ].concat(exports.commonModuleNames); 48 | const features = get_electron_binding_1.getElectronBinding('features'); 49 | if (((_a = features === null || features === void 0 ? void 0 : features.isDesktopCapturerEnabled) === null || _a === void 0 ? void 0 : _a.call(features)) !== false) { 50 | exports.browserModuleNames.push('desktopCapturer'); 51 | } 52 | if (((_b = features === null || features === void 0 ? void 0 : features.isViewApiEnabled) === null || _b === void 0 ? void 0 : _b.call(features)) !== false) { 53 | exports.browserModuleNames.push('ImageView'); 54 | } 55 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/type-utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare function isPromise(val: any): any; 2 | export declare function isSerializableObject(value: any): boolean; 3 | export declare function serialize(value: any): any; 4 | export declare function deserialize(value: any): any; 5 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/common/type-utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.deserialize = exports.serialize = exports.isSerializableObject = exports.isPromise = void 0; 4 | const electron_1 = require("electron"); 5 | function isPromise(val) { 6 | return (val && 7 | val.then && 8 | val.then instanceof Function && 9 | val.constructor && 10 | val.constructor.reject && 11 | val.constructor.reject instanceof Function && 12 | val.constructor.resolve && 13 | val.constructor.resolve instanceof Function); 14 | } 15 | exports.isPromise = isPromise; 16 | const serializableTypes = [ 17 | Boolean, 18 | Number, 19 | String, 20 | Date, 21 | Error, 22 | RegExp, 23 | ArrayBuffer 24 | ]; 25 | // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#Supported_types 26 | function isSerializableObject(value) { 27 | return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type); 28 | } 29 | exports.isSerializableObject = isSerializableObject; 30 | const objectMap = function (source, mapper) { 31 | const sourceEntries = Object.entries(source); 32 | const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]); 33 | return Object.fromEntries(targetEntries); 34 | }; 35 | function serializeNativeImage(image) { 36 | const representations = []; 37 | const scaleFactors = image.getScaleFactors(); 38 | // Use Buffer when there's only one representation for better perf. 39 | // This avoids compressing to/from PNG where it's not necessary to 40 | // ensure uniqueness of dataURLs (since there's only one). 41 | if (scaleFactors.length === 1) { 42 | const scaleFactor = scaleFactors[0]; 43 | const size = image.getSize(scaleFactor); 44 | const buffer = image.toBitmap({ scaleFactor }); 45 | representations.push({ scaleFactor, size, buffer }); 46 | } 47 | else { 48 | // Construct from dataURLs to ensure that they are not lost in creation. 49 | for (const scaleFactor of scaleFactors) { 50 | const size = image.getSize(scaleFactor); 51 | const dataURL = image.toDataURL({ scaleFactor }); 52 | representations.push({ scaleFactor, size, dataURL }); 53 | } 54 | } 55 | return { __ELECTRON_SERIALIZED_NativeImage__: true, representations }; 56 | } 57 | function deserializeNativeImage(value) { 58 | const image = electron_1.nativeImage.createEmpty(); 59 | // Use Buffer when there's only one representation for better perf. 60 | // This avoids compressing to/from PNG where it's not necessary to 61 | // ensure uniqueness of dataURLs (since there's only one). 62 | if (value.representations.length === 1) { 63 | const { buffer, size, scaleFactor } = value.representations[0]; 64 | const { width, height } = size; 65 | image.addRepresentation({ buffer, scaleFactor, width, height }); 66 | } 67 | else { 68 | // Construct from dataURLs to ensure that they are not lost in creation. 69 | for (const rep of value.representations) { 70 | const { dataURL, size, scaleFactor } = rep; 71 | const { width, height } = size; 72 | image.addRepresentation({ dataURL, scaleFactor, width, height }); 73 | } 74 | } 75 | return image; 76 | } 77 | function serialize(value) { 78 | if (value && value.constructor && value.constructor.name === 'NativeImage') { 79 | return serializeNativeImage(value); 80 | } 81 | if (Array.isArray(value)) { 82 | return value.map(serialize); 83 | } 84 | else if (isSerializableObject(value)) { 85 | return value; 86 | } 87 | else if (value instanceof Object) { 88 | return objectMap(value, serialize); 89 | } 90 | else { 91 | return value; 92 | } 93 | } 94 | exports.serialize = serialize; 95 | function deserialize(value) { 96 | if (value && value.__ELECTRON_SERIALIZED_NativeImage__) { 97 | return deserializeNativeImage(value); 98 | } 99 | else if (Array.isArray(value)) { 100 | return value.map(deserialize); 101 | } 102 | else if (isSerializableObject(value)) { 103 | return value; 104 | } 105 | else if (value instanceof Object) { 106 | return objectMap(value, deserialize); 107 | } 108 | else { 109 | return value; 110 | } 111 | } 112 | exports.deserialize = deserialize; 113 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/main/index.d.ts: -------------------------------------------------------------------------------- 1 | export { initialize, isInitialized, enable } from "./server"; 2 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/main/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.enable = exports.isInitialized = exports.initialize = void 0; 4 | var server_1 = require("./server"); 5 | Object.defineProperty(exports, "initialize", { enumerable: true, get: function () { return server_1.initialize; } }); 6 | Object.defineProperty(exports, "isInitialized", { enumerable: true, get: function () { return server_1.isInitialized; } }); 7 | Object.defineProperty(exports, "enable", { enumerable: true, get: function () { return server_1.enable; } }); 8 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/main/objects-registry.d.ts: -------------------------------------------------------------------------------- 1 | import { WebContents } from 'electron'; 2 | declare class ObjectsRegistry { 3 | private nextId; 4 | private storage; 5 | private owners; 6 | private electronIds; 7 | add(webContents: WebContents, contextId: string, obj: any): number; 8 | get(id: number): any; 9 | remove(webContents: WebContents, contextId: string, id: number): void; 10 | clear(webContents: WebContents, contextId: string): void; 11 | private saveToStorage; 12 | private dereference; 13 | private registerDeleteListener; 14 | } 15 | declare const _default: ObjectsRegistry; 16 | export default _default; 17 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/main/objects-registry.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const getOwnerKey = (webContents, contextId) => { 4 | return `${webContents.id}-${contextId}`; 5 | }; 6 | class ObjectsRegistry { 7 | constructor() { 8 | this.nextId = 0; 9 | // Stores all objects by ref-counting. 10 | // (id) => {object, count} 11 | this.storage = {}; 12 | // Stores the IDs + refCounts of objects referenced by WebContents. 13 | // (ownerKey) => { id: refCount } 14 | this.owners = {}; 15 | this.electronIds = new WeakMap(); 16 | } 17 | // Register a new object and return its assigned ID. If the object is already 18 | // registered then the already assigned ID would be returned. 19 | add(webContents, contextId, obj) { 20 | // Get or assign an ID to the object. 21 | const id = this.saveToStorage(obj); 22 | // Add object to the set of referenced objects. 23 | const ownerKey = getOwnerKey(webContents, contextId); 24 | let owner = this.owners[ownerKey]; 25 | if (!owner) { 26 | owner = this.owners[ownerKey] = new Map(); 27 | this.registerDeleteListener(webContents, contextId); 28 | } 29 | if (!owner.has(id)) { 30 | owner.set(id, 0); 31 | // Increase reference count if not referenced before. 32 | this.storage[id].count++; 33 | } 34 | owner.set(id, owner.get(id) + 1); 35 | return id; 36 | } 37 | // Get an object according to its ID. 38 | get(id) { 39 | const pointer = this.storage[id]; 40 | if (pointer != null) 41 | return pointer.object; 42 | } 43 | // Dereference an object according to its ID. 44 | // Note that an object may be double-freed (cleared when page is reloaded, and 45 | // then garbage collected in old page). 46 | remove(webContents, contextId, id) { 47 | const ownerKey = getOwnerKey(webContents, contextId); 48 | const owner = this.owners[ownerKey]; 49 | if (owner && owner.has(id)) { 50 | const newRefCount = owner.get(id) - 1; 51 | // Only completely remove if the number of references GCed in the 52 | // renderer is the same as the number of references we sent them 53 | if (newRefCount <= 0) { 54 | // Remove the reference in owner. 55 | owner.delete(id); 56 | // Dereference from the storage. 57 | this.dereference(id); 58 | } 59 | else { 60 | owner.set(id, newRefCount); 61 | } 62 | } 63 | } 64 | // Clear all references to objects refrenced by the WebContents. 65 | clear(webContents, contextId) { 66 | const ownerKey = getOwnerKey(webContents, contextId); 67 | const owner = this.owners[ownerKey]; 68 | if (!owner) 69 | return; 70 | for (const id of owner.keys()) 71 | this.dereference(id); 72 | delete this.owners[ownerKey]; 73 | } 74 | // Saves the object into storage and assigns an ID for it. 75 | saveToStorage(object) { 76 | let id = this.electronIds.get(object); 77 | if (!id) { 78 | id = ++this.nextId; 79 | this.storage[id] = { 80 | count: 0, 81 | object: object 82 | }; 83 | this.electronIds.set(object, id); 84 | } 85 | return id; 86 | } 87 | // Dereference the object from store. 88 | dereference(id) { 89 | const pointer = this.storage[id]; 90 | if (pointer == null) { 91 | return; 92 | } 93 | pointer.count -= 1; 94 | if (pointer.count === 0) { 95 | this.electronIds.delete(pointer.object); 96 | delete this.storage[id]; 97 | } 98 | } 99 | // Clear the storage when renderer process is destroyed. 100 | registerDeleteListener(webContents, contextId) { 101 | // contextId => ${processHostId}-${contextCount} 102 | const processHostId = contextId.split('-')[0]; 103 | const listener = (_, deletedProcessHostId) => { 104 | if (deletedProcessHostId && 105 | deletedProcessHostId.toString() === processHostId) { 106 | webContents.removeListener('render-view-deleted', listener); 107 | this.clear(webContents, contextId); 108 | } 109 | }; 110 | // Note that the "render-view-deleted" event may not be emitted on time when 111 | // the renderer process get destroyed because of navigation, we rely on the 112 | // renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to 113 | // guard this situation. 114 | webContents.on('render-view-deleted', listener); 115 | } 116 | } 117 | exports.default = new ObjectsRegistry(); 118 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/main/server.d.ts: -------------------------------------------------------------------------------- 1 | import { WebContents } from 'electron'; 2 | export declare const isRemoteModuleEnabled: (contents: WebContents) => boolean | undefined; 3 | export declare function enable(contents: WebContents): void; 4 | export declare function isInitialized(): boolean; 5 | export declare function initialize(): void; 6 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/main/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.initialize = exports.isInitialized = exports.enable = exports.isRemoteModuleEnabled = void 0; 7 | const events_1 = require("events"); 8 | const objects_registry_1 = __importDefault(require("./objects-registry")); 9 | const type_utils_1 = require("../common/type-utils"); 10 | const electron_1 = require("electron"); 11 | const get_electron_binding_1 = require("../common/get-electron-binding"); 12 | const { Promise } = global; 13 | const v8Util = get_electron_binding_1.getElectronBinding('v8_util'); 14 | const hasWebPrefsRemoteModuleAPI = (() => { 15 | var _a, _b; 16 | const electronVersion = Number((_b = (_a = process.versions.electron) === null || _a === void 0 ? void 0 : _a.split(".")) === null || _b === void 0 ? void 0 : _b[0]); 17 | return Number.isNaN(electronVersion) || electronVersion < 14; 18 | })(); 19 | // The internal properties of Function. 20 | const FUNCTION_PROPERTIES = [ 21 | 'length', 'name', 'arguments', 'caller', 'prototype' 22 | ]; 23 | // The remote functions in renderer processes. 24 | const rendererFunctionCache = new Map(); 25 | // eslint-disable-next-line no-undef 26 | const finalizationRegistry = new FinalizationRegistry((fi) => { 27 | const mapKey = fi.id[0] + '~' + fi.id[1]; 28 | const ref = rendererFunctionCache.get(mapKey); 29 | if (ref !== undefined && ref.deref() === undefined) { 30 | rendererFunctionCache.delete(mapKey); 31 | if (!fi.webContents.isDestroyed()) { 32 | try { 33 | fi.webContents.sendToFrame(fi.frameId, "REMOTE_RENDERER_RELEASE_CALLBACK" /* RENDERER_RELEASE_CALLBACK */, fi.id[0], fi.id[1]); 34 | } 35 | catch (error) { 36 | console.warn(`sendToFrame() failed: ${error}`); 37 | } 38 | } 39 | } 40 | }); 41 | function getCachedRendererFunction(id) { 42 | const mapKey = id[0] + '~' + id[1]; 43 | const ref = rendererFunctionCache.get(mapKey); 44 | if (ref !== undefined) { 45 | const deref = ref.deref(); 46 | if (deref !== undefined) 47 | return deref; 48 | } 49 | } 50 | function setCachedRendererFunction(id, wc, frameId, value) { 51 | // eslint-disable-next-line no-undef 52 | const wr = new WeakRef(value); 53 | const mapKey = id[0] + '~' + id[1]; 54 | rendererFunctionCache.set(mapKey, wr); 55 | finalizationRegistry.register(value, { 56 | id, 57 | webContents: wc, 58 | frameId 59 | }); 60 | return value; 61 | } 62 | const locationInfo = new WeakMap(); 63 | // Return the description of object's members: 64 | const getObjectMembers = function (object) { 65 | let names = Object.getOwnPropertyNames(object); 66 | // For Function, we should not override following properties even though they 67 | // are "own" properties. 68 | if (typeof object === 'function') { 69 | names = names.filter((name) => { 70 | return !FUNCTION_PROPERTIES.includes(name); 71 | }); 72 | } 73 | // Map properties to descriptors. 74 | return names.map((name) => { 75 | const descriptor = Object.getOwnPropertyDescriptor(object, name); 76 | let type; 77 | let writable = false; 78 | if (descriptor.get === undefined && typeof object[name] === 'function') { 79 | type = 'method'; 80 | } 81 | else { 82 | if (descriptor.set || descriptor.writable) 83 | writable = true; 84 | type = 'get'; 85 | } 86 | return { name, enumerable: descriptor.enumerable, writable, type }; 87 | }); 88 | }; 89 | // Return the description of object's prototype. 90 | const getObjectPrototype = function (object) { 91 | const proto = Object.getPrototypeOf(object); 92 | if (proto === null || proto === Object.prototype) 93 | return null; 94 | return { 95 | members: getObjectMembers(proto), 96 | proto: getObjectPrototype(proto) 97 | }; 98 | }; 99 | // Convert a real value into meta data. 100 | const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) { 101 | // Determine the type of value. 102 | let type; 103 | switch (typeof value) { 104 | case 'object': 105 | // Recognize certain types of objects. 106 | if (value instanceof Buffer) { 107 | type = 'buffer'; 108 | } 109 | else if (value && value.constructor && value.constructor.name === 'NativeImage') { 110 | type = 'nativeimage'; 111 | } 112 | else if (Array.isArray(value)) { 113 | type = 'array'; 114 | } 115 | else if (value instanceof Error) { 116 | type = 'error'; 117 | } 118 | else if (type_utils_1.isSerializableObject(value)) { 119 | type = 'value'; 120 | } 121 | else if (type_utils_1.isPromise(value)) { 122 | type = 'promise'; 123 | } 124 | else if (Object.prototype.hasOwnProperty.call(value, 'callee') && value.length != null) { 125 | // Treat the arguments object as array. 126 | type = 'array'; 127 | } 128 | else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) { 129 | // Treat simple objects as value. 130 | type = 'value'; 131 | } 132 | else { 133 | type = 'object'; 134 | } 135 | break; 136 | case 'function': 137 | type = 'function'; 138 | break; 139 | default: 140 | type = 'value'; 141 | break; 142 | } 143 | // Fill the meta object according to value's type. 144 | if (type === 'array') { 145 | return { 146 | type, 147 | members: value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject)) 148 | }; 149 | } 150 | else if (type === 'nativeimage') { 151 | return { type, value: type_utils_1.serialize(value) }; 152 | } 153 | else if (type === 'object' || type === 'function') { 154 | return { 155 | type, 156 | name: value.constructor ? value.constructor.name : '', 157 | // Reference the original value if it's an object, because when it's 158 | // passed to renderer we would assume the renderer keeps a reference of 159 | // it. 160 | id: objects_registry_1.default.add(sender, contextId, value), 161 | members: getObjectMembers(value), 162 | proto: getObjectPrototype(value) 163 | }; 164 | } 165 | else if (type === 'buffer') { 166 | return { type, value }; 167 | } 168 | else if (type === 'promise') { 169 | // Add default handler to prevent unhandled rejections in main process 170 | // Instead they should appear in the renderer process 171 | value.then(function () { }, function () { }); 172 | return { 173 | type, 174 | then: valueToMeta(sender, contextId, function (onFulfilled, onRejected) { 175 | value.then(onFulfilled, onRejected); 176 | }) 177 | }; 178 | } 179 | else if (type === 'error') { 180 | return { 181 | type, 182 | value, 183 | members: Object.keys(value).map(name => ({ 184 | name, 185 | value: valueToMeta(sender, contextId, value[name]) 186 | })) 187 | }; 188 | } 189 | else { 190 | return { 191 | type: 'value', 192 | value 193 | }; 194 | } 195 | }; 196 | const throwRPCError = function (message) { 197 | const error = new Error(message); 198 | error.code = 'EBADRPC'; 199 | error.errno = -72; 200 | throw error; 201 | }; 202 | const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => { 203 | const location = locationInfo.get(callIntoRenderer); 204 | let message = 'Attempting to call a function in a renderer window that has been closed or released.' + 205 | `\nFunction provided here: ${location}`; 206 | if (sender instanceof events_1.EventEmitter) { 207 | const remoteEvents = sender.eventNames().filter((eventName) => { 208 | return sender.listeners(eventName).includes(callIntoRenderer); 209 | }); 210 | if (remoteEvents.length > 0) { 211 | message += `\nRemote event names: ${remoteEvents.join(', ')}`; 212 | remoteEvents.forEach((eventName) => { 213 | sender.removeListener(eventName, callIntoRenderer); 214 | }); 215 | } 216 | } 217 | console.warn(message); 218 | }; 219 | const fakeConstructor = (constructor, name) => new Proxy(Object, { 220 | get(target, prop, receiver) { 221 | if (prop === 'name') { 222 | return name; 223 | } 224 | else { 225 | return Reflect.get(target, prop, receiver); 226 | } 227 | } 228 | }); 229 | // Convert array of meta data from renderer into array of real values. 230 | const unwrapArgs = function (sender, frameId, contextId, args) { 231 | const metaToValue = function (meta) { 232 | switch (meta.type) { 233 | case 'nativeimage': 234 | return type_utils_1.deserialize(meta.value); 235 | case 'value': 236 | return meta.value; 237 | case 'remote-object': 238 | return objects_registry_1.default.get(meta.id); 239 | case 'array': 240 | return unwrapArgs(sender, frameId, contextId, meta.value); 241 | case 'buffer': 242 | return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength); 243 | case 'promise': 244 | return Promise.resolve({ 245 | then: metaToValue(meta.then) 246 | }); 247 | case 'object': { 248 | const ret = meta.name !== 'Object' ? Object.create({ 249 | constructor: fakeConstructor(Object, meta.name) 250 | }) : {}; 251 | for (const { name, value } of meta.members) { 252 | ret[name] = metaToValue(value); 253 | } 254 | return ret; 255 | } 256 | case 'function-with-return-value': { 257 | const returnValue = metaToValue(meta.value); 258 | return function () { 259 | return returnValue; 260 | }; 261 | } 262 | case 'function': { 263 | // Merge contextId and meta.id, since meta.id can be the same in 264 | // different webContents. 265 | const objectId = [contextId, meta.id]; 266 | // Cache the callbacks in renderer. 267 | const cachedFunction = getCachedRendererFunction(objectId); 268 | if (cachedFunction !== undefined) { 269 | return cachedFunction; 270 | } 271 | const callIntoRenderer = function (...args) { 272 | let succeed = false; 273 | if (!sender.isDestroyed()) { 274 | try { 275 | succeed = sender.sendToFrame(frameId, "REMOTE_RENDERER_CALLBACK" /* RENDERER_CALLBACK */, contextId, meta.id, valueToMeta(sender, contextId, args)) !== false; 276 | } 277 | catch (error) { 278 | console.warn(`sendToFrame() failed: ${error}`); 279 | } 280 | } 281 | if (!succeed) { 282 | removeRemoteListenersAndLogWarning(this, callIntoRenderer); 283 | } 284 | }; 285 | locationInfo.set(callIntoRenderer, meta.location); 286 | Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }); 287 | setCachedRendererFunction(objectId, sender, frameId, callIntoRenderer); 288 | return callIntoRenderer; 289 | } 290 | default: 291 | throw new TypeError(`Unknown type: ${meta.type}`); 292 | } 293 | }; 294 | return args.map(metaToValue); 295 | }; 296 | const isRemoteModuleEnabledImpl = function (contents) { 297 | const webPreferences = contents.getLastWebPreferences() || {}; 298 | return webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : false; 299 | }; 300 | const isRemoteModuleEnabledCache = new WeakMap(); 301 | const isRemoteModuleEnabled = function (contents) { 302 | if (hasWebPrefsRemoteModuleAPI && !isRemoteModuleEnabledCache.has(contents)) { 303 | isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents)); 304 | } 305 | return isRemoteModuleEnabledCache.get(contents); 306 | }; 307 | exports.isRemoteModuleEnabled = isRemoteModuleEnabled; 308 | function enable(contents) { 309 | isRemoteModuleEnabledCache.set(contents, true); 310 | } 311 | exports.enable = enable; 312 | const handleRemoteCommand = function (channel, handler) { 313 | electron_1.ipcMain.on(channel, (event, contextId, ...args) => { 314 | let returnValue; 315 | if (!exports.isRemoteModuleEnabled(event.sender)) { 316 | event.returnValue = { 317 | type: 'exception', 318 | value: valueToMeta(event.sender, contextId, new Error('@electron/remote is disabled for this WebContents. Call require("@electron/remote/main").enable(webContents) to enable it.')) 319 | }; 320 | return; 321 | } 322 | try { 323 | returnValue = handler(event, contextId, ...args); 324 | } 325 | catch (error) { 326 | returnValue = { 327 | type: 'exception', 328 | value: valueToMeta(event.sender, contextId, error), 329 | }; 330 | } 331 | if (returnValue !== undefined) { 332 | event.returnValue = returnValue; 333 | } 334 | }); 335 | }; 336 | const emitCustomEvent = function (contents, eventName, ...args) { 337 | const event = { sender: contents, returnValue: undefined, defaultPrevented: false }; 338 | electron_1.app.emit(eventName, event, contents, ...args); 339 | contents.emit(eventName, event, ...args); 340 | return event; 341 | }; 342 | const logStack = function (contents, code, stack) { 343 | if (stack) { 344 | console.warn(`WebContents (${contents.id}): ${code}`, stack); 345 | } 346 | }; 347 | let initialized = false; 348 | function isInitialized() { 349 | return initialized; 350 | } 351 | exports.isInitialized = isInitialized; 352 | function initialize() { 353 | if (initialized) 354 | throw new Error('@electron/remote has already been initialized'); 355 | initialized = true; 356 | handleRemoteCommand("REMOTE_BROWSER_WRONG_CONTEXT_ERROR" /* BROWSER_WRONG_CONTEXT_ERROR */, function (event, contextId, passedContextId, id) { 357 | const objectId = [passedContextId, id]; 358 | const cachedFunction = getCachedRendererFunction(objectId); 359 | if (cachedFunction === undefined) { 360 | // Do nothing if the error has already been reported before. 361 | return; 362 | } 363 | removeRemoteListenersAndLogWarning(event.sender, cachedFunction); 364 | }); 365 | handleRemoteCommand("REMOTE_BROWSER_REQUIRE" /* BROWSER_REQUIRE */, function (event, contextId, moduleName, stack) { 366 | logStack(event.sender, `remote.require('${moduleName}')`, stack); 367 | const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName); 368 | if (customEvent.returnValue === undefined) { 369 | if (customEvent.defaultPrevented) { 370 | throw new Error(`Blocked remote.require('${moduleName}')`); 371 | } 372 | else { 373 | // electron < 28 374 | if (process.mainModule) { 375 | customEvent.returnValue = process.mainModule.require(moduleName); 376 | } 377 | else { 378 | // electron >= 28 379 | let mainModule = module; 380 | while (mainModule.parent) { 381 | mainModule = mainModule.parent; 382 | } 383 | customEvent.returnValue = mainModule.require(moduleName); 384 | } 385 | } 386 | } 387 | return valueToMeta(event.sender, contextId, customEvent.returnValue); 388 | }); 389 | handleRemoteCommand("REMOTE_BROWSER_GET_BUILTIN" /* BROWSER_GET_BUILTIN */, function (event, contextId, moduleName, stack) { 390 | logStack(event.sender, `remote.getBuiltin('${moduleName}')`, stack); 391 | const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName); 392 | if (customEvent.returnValue === undefined) { 393 | if (customEvent.defaultPrevented) { 394 | throw new Error(`Blocked remote.getBuiltin('${moduleName}')`); 395 | } 396 | else { 397 | customEvent.returnValue = require('electron')[moduleName]; 398 | } 399 | } 400 | return valueToMeta(event.sender, contextId, customEvent.returnValue); 401 | }); 402 | handleRemoteCommand("REMOTE_BROWSER_GET_GLOBAL" /* BROWSER_GET_GLOBAL */, function (event, contextId, globalName, stack) { 403 | logStack(event.sender, `remote.getGlobal('${globalName}')`, stack); 404 | const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName); 405 | if (customEvent.returnValue === undefined) { 406 | if (customEvent.defaultPrevented) { 407 | throw new Error(`Blocked remote.getGlobal('${globalName}')`); 408 | } 409 | else { 410 | customEvent.returnValue = global[globalName]; 411 | } 412 | } 413 | return valueToMeta(event.sender, contextId, customEvent.returnValue); 414 | }); 415 | handleRemoteCommand("REMOTE_BROWSER_GET_CURRENT_WINDOW" /* BROWSER_GET_CURRENT_WINDOW */, function (event, contextId, stack) { 416 | logStack(event.sender, 'remote.getCurrentWindow()', stack); 417 | const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window'); 418 | if (customEvent.returnValue === undefined) { 419 | if (customEvent.defaultPrevented) { 420 | throw new Error('Blocked remote.getCurrentWindow()'); 421 | } 422 | else { 423 | customEvent.returnValue = event.sender.getOwnerBrowserWindow(); 424 | } 425 | } 426 | return valueToMeta(event.sender, contextId, customEvent.returnValue); 427 | }); 428 | handleRemoteCommand("REMOTE_BROWSER_GET_CURRENT_WEB_CONTENTS" /* BROWSER_GET_CURRENT_WEB_CONTENTS */, function (event, contextId, stack) { 429 | logStack(event.sender, 'remote.getCurrentWebContents()', stack); 430 | const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents'); 431 | if (customEvent.returnValue === undefined) { 432 | if (customEvent.defaultPrevented) { 433 | throw new Error('Blocked remote.getCurrentWebContents()'); 434 | } 435 | else { 436 | customEvent.returnValue = event.sender; 437 | } 438 | } 439 | return valueToMeta(event.sender, contextId, customEvent.returnValue); 440 | }); 441 | handleRemoteCommand("REMOTE_BROWSER_CONSTRUCTOR" /* BROWSER_CONSTRUCTOR */, function (event, contextId, id, args) { 442 | args = unwrapArgs(event.sender, event.frameId, contextId, args); 443 | const constructor = objects_registry_1.default.get(id); 444 | if (constructor == null) { 445 | throwRPCError(`Cannot call constructor on missing remote object ${id}`); 446 | } 447 | return valueToMeta(event.sender, contextId, new constructor(...args)); 448 | }); 449 | handleRemoteCommand("REMOTE_BROWSER_FUNCTION_CALL" /* BROWSER_FUNCTION_CALL */, function (event, contextId, id, args) { 450 | args = unwrapArgs(event.sender, event.frameId, contextId, args); 451 | const func = objects_registry_1.default.get(id); 452 | if (func == null) { 453 | throwRPCError(`Cannot call function on missing remote object ${id}`); 454 | } 455 | try { 456 | return valueToMeta(event.sender, contextId, func(...args), true); 457 | } 458 | catch (error) { 459 | const err = new Error(`Could not call remote function '${func.name || "anonymous"}'. Check that the function signature is correct. Underlying error: ${error}\n` + 460 | (error instanceof Error ? `Underlying stack: ${error.stack}\n` : "")); 461 | err.cause = error; 462 | throw err; 463 | } 464 | }); 465 | handleRemoteCommand("REMOTE_BROWSER_MEMBER_CONSTRUCTOR" /* BROWSER_MEMBER_CONSTRUCTOR */, function (event, contextId, id, method, args) { 466 | args = unwrapArgs(event.sender, event.frameId, contextId, args); 467 | const object = objects_registry_1.default.get(id); 468 | if (object == null) { 469 | throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`); 470 | } 471 | return valueToMeta(event.sender, contextId, new object[method](...args)); 472 | }); 473 | handleRemoteCommand("REMOTE_BROWSER_MEMBER_CALL" /* BROWSER_MEMBER_CALL */, function (event, contextId, id, method, args) { 474 | args = unwrapArgs(event.sender, event.frameId, contextId, args); 475 | const object = objects_registry_1.default.get(id); 476 | if (object == null) { 477 | throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`); 478 | } 479 | try { 480 | return valueToMeta(event.sender, contextId, object[method](...args), true); 481 | } 482 | catch (error) { 483 | const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error}` + 484 | (error instanceof Error ? `Underlying stack: ${error.stack}\n` : "")); 485 | err.cause = error; 486 | throw err; 487 | } 488 | }); 489 | handleRemoteCommand("REMOTE_BROWSER_MEMBER_SET" /* BROWSER_MEMBER_SET */, function (event, contextId, id, name, args) { 490 | args = unwrapArgs(event.sender, event.frameId, contextId, args); 491 | const obj = objects_registry_1.default.get(id); 492 | if (obj == null) { 493 | throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`); 494 | } 495 | obj[name] = args[0]; 496 | return null; 497 | }); 498 | handleRemoteCommand("REMOTE_BROWSER_MEMBER_GET" /* BROWSER_MEMBER_GET */, function (event, contextId, id, name) { 499 | const obj = objects_registry_1.default.get(id); 500 | if (obj == null) { 501 | throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`); 502 | } 503 | return valueToMeta(event.sender, contextId, obj[name]); 504 | }); 505 | handleRemoteCommand("REMOTE_BROWSER_DEREFERENCE" /* BROWSER_DEREFERENCE */, function (event, contextId, id) { 506 | objects_registry_1.default.remove(event.sender, contextId, id); 507 | }); 508 | handleRemoteCommand("REMOTE_BROWSER_CONTEXT_RELEASE" /* BROWSER_CONTEXT_RELEASE */, (event, contextId) => { 509 | objects_registry_1.default.clear(event.sender, contextId); 510 | return null; 511 | }); 512 | } 513 | exports.initialize = initialize; 514 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/renderer/callbacks-registry.d.ts: -------------------------------------------------------------------------------- 1 | export declare class CallbacksRegistry { 2 | private nextId; 3 | private callbacks; 4 | private callbackIds; 5 | private locationInfo; 6 | add(callback: Function): number; 7 | get(id: number): Function; 8 | getLocation(callback: Function): string | undefined; 9 | apply(id: number, ...args: any[]): any; 10 | remove(id: number): void; 11 | } 12 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/renderer/callbacks-registry.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.CallbacksRegistry = void 0; 4 | class CallbacksRegistry { 5 | constructor() { 6 | this.nextId = 0; 7 | this.callbacks = {}; 8 | this.callbackIds = new WeakMap(); 9 | this.locationInfo = new WeakMap(); 10 | } 11 | add(callback) { 12 | // The callback is already added. 13 | let id = this.callbackIds.get(callback); 14 | if (id != null) 15 | return id; 16 | id = this.nextId += 1; 17 | this.callbacks[id] = callback; 18 | this.callbackIds.set(callback, id); 19 | // Capture the location of the function and put it in the ID string, 20 | // so that release errors can be tracked down easily. 21 | const regexp = /at (.*)/gi; 22 | const stackString = (new Error()).stack; 23 | if (!stackString) 24 | return id; 25 | let filenameAndLine; 26 | let match; 27 | while ((match = regexp.exec(stackString)) !== null) { 28 | const location = match[1]; 29 | if (location.includes('(native)')) 30 | continue; 31 | if (location.includes('()')) 32 | continue; 33 | if (location.includes('callbacks-registry.js')) 34 | continue; 35 | if (location.includes('remote.js')) 36 | continue; 37 | if (location.includes('@electron/remote/dist')) 38 | continue; 39 | const ref = /([^/^)]*)\)?$/gi.exec(location); 40 | if (ref) 41 | filenameAndLine = ref[1]; 42 | break; 43 | } 44 | this.locationInfo.set(callback, filenameAndLine); 45 | return id; 46 | } 47 | get(id) { 48 | return this.callbacks[id] || function () { }; 49 | } 50 | getLocation(callback) { 51 | return this.locationInfo.get(callback); 52 | } 53 | apply(id, ...args) { 54 | return this.get(id).apply(global, ...args); 55 | } 56 | remove(id) { 57 | const callback = this.callbacks[id]; 58 | if (callback) { 59 | this.callbackIds.delete(callback); 60 | delete this.callbacks[id]; 61 | } 62 | } 63 | } 64 | exports.CallbacksRegistry = CallbacksRegistry; 65 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/renderer/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './remote'; 2 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/renderer/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 10 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | if (process.type === 'browser') 14 | throw new Error(`"@electron/remote" cannot be required in the browser process. Instead require("@electron/remote/main").`); 15 | __exportStar(require("./remote"), exports); 16 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/renderer/remote.d.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, WebContents } from 'electron'; 2 | export declare function getBuiltin(module: string): any; 3 | export declare function getCurrentWindow(): BrowserWindow; 4 | export declare function getCurrentWebContents(): WebContents; 5 | export declare function getGlobal(name: string): T; 6 | export declare function createFunctionWithReturnValue(returnValue: T): () => T; 7 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/dist/src/renderer/remote.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.createFunctionWithReturnValue = exports.getGlobal = exports.getCurrentWebContents = exports.getCurrentWindow = exports.getBuiltin = void 0; 4 | const callbacks_registry_1 = require("./callbacks-registry"); 5 | const type_utils_1 = require("../common/type-utils"); 6 | const electron_1 = require("electron"); 7 | const module_names_1 = require("../common/module-names"); 8 | const get_electron_binding_1 = require("../common/get-electron-binding"); 9 | const { Promise } = global; 10 | const callbacksRegistry = new callbacks_registry_1.CallbacksRegistry(); 11 | const remoteObjectCache = new Map(); 12 | const finalizationRegistry = new FinalizationRegistry((id) => { 13 | const ref = remoteObjectCache.get(id); 14 | if (ref !== undefined && ref.deref() === undefined) { 15 | remoteObjectCache.delete(id); 16 | electron_1.ipcRenderer.send("REMOTE_BROWSER_DEREFERENCE" /* BROWSER_DEREFERENCE */, contextId, id, 0); 17 | } 18 | }); 19 | const electronIds = new WeakMap(); 20 | const isReturnValue = new WeakSet(); 21 | function getCachedRemoteObject(id) { 22 | const ref = remoteObjectCache.get(id); 23 | if (ref !== undefined) { 24 | const deref = ref.deref(); 25 | if (deref !== undefined) 26 | return deref; 27 | } 28 | } 29 | function setCachedRemoteObject(id, value) { 30 | const wr = new WeakRef(value); 31 | remoteObjectCache.set(id, wr); 32 | finalizationRegistry.register(value, id); 33 | return value; 34 | } 35 | function getContextId() { 36 | const v8Util = get_electron_binding_1.getElectronBinding('v8_util'); 37 | if (v8Util) { 38 | return v8Util.getHiddenValue(global, 'contextId'); 39 | } 40 | else { 41 | throw new Error('Electron >=v13.0.0-beta.6 required to support sandboxed renderers'); 42 | } 43 | } 44 | // An unique ID that can represent current context. 45 | const contextId = process.contextId || getContextId(); 46 | // Notify the main process when current context is going to be released. 47 | // Note that when the renderer process is destroyed, the message may not be 48 | // sent, we also listen to the "render-view-deleted" event in the main process 49 | // to guard that situation. 50 | process.on('exit', () => { 51 | const command = "REMOTE_BROWSER_CONTEXT_RELEASE" /* BROWSER_CONTEXT_RELEASE */; 52 | electron_1.ipcRenderer.send(command, contextId); 53 | }); 54 | const IS_REMOTE_PROXY = Symbol('is-remote-proxy'); 55 | // Convert the arguments object into an array of meta data. 56 | function wrapArgs(args, visited = new Set()) { 57 | const valueToMeta = (value) => { 58 | // Check for circular reference. 59 | if (visited.has(value)) { 60 | return { 61 | type: 'value', 62 | value: null 63 | }; 64 | } 65 | if (value && value.constructor && value.constructor.name === 'NativeImage') { 66 | return { type: 'nativeimage', value: type_utils_1.serialize(value) }; 67 | } 68 | else if (Array.isArray(value)) { 69 | visited.add(value); 70 | const meta = { 71 | type: 'array', 72 | value: wrapArgs(value, visited) 73 | }; 74 | visited.delete(value); 75 | return meta; 76 | } 77 | else if (value instanceof Buffer) { 78 | return { 79 | type: 'buffer', 80 | value 81 | }; 82 | } 83 | else if (type_utils_1.isSerializableObject(value)) { 84 | return { 85 | type: 'value', 86 | value 87 | }; 88 | } 89 | else if (typeof value === 'object') { 90 | if (type_utils_1.isPromise(value)) { 91 | return { 92 | type: 'promise', 93 | then: valueToMeta(function (onFulfilled, onRejected) { 94 | value.then(onFulfilled, onRejected); 95 | }) 96 | }; 97 | } 98 | else if (electronIds.has(value)) { 99 | return { 100 | type: 'remote-object', 101 | id: electronIds.get(value) 102 | }; 103 | } 104 | const meta = { 105 | type: 'object', 106 | name: value.constructor ? value.constructor.name : '', 107 | members: [] 108 | }; 109 | visited.add(value); 110 | for (const prop in value) { // eslint-disable-line guard-for-in 111 | meta.members.push({ 112 | name: prop, 113 | value: valueToMeta(value[prop]) 114 | }); 115 | } 116 | visited.delete(value); 117 | return meta; 118 | } 119 | else if (typeof value === 'function' && isReturnValue.has(value)) { 120 | return { 121 | type: 'function-with-return-value', 122 | value: valueToMeta(value()) 123 | }; 124 | } 125 | else if (typeof value === 'function') { 126 | return { 127 | type: 'function', 128 | id: callbacksRegistry.add(value), 129 | location: callbacksRegistry.getLocation(value), 130 | length: value.length 131 | }; 132 | } 133 | else { 134 | return { 135 | type: 'value', 136 | value 137 | }; 138 | } 139 | }; 140 | return args.map(valueToMeta); 141 | } 142 | // Populate object's members from descriptors. 143 | // The |ref| will be kept referenced by |members|. 144 | // This matches |getObjectMemebers| in rpc-server. 145 | function setObjectMembers(ref, object, metaId, members) { 146 | if (!Array.isArray(members)) 147 | return; 148 | for (const member of members) { 149 | if (Object.prototype.hasOwnProperty.call(object, member.name)) 150 | continue; 151 | const descriptor = { enumerable: member.enumerable }; 152 | if (member.type === 'method') { 153 | const remoteMemberFunction = function (...args) { 154 | let command; 155 | if (this && this.constructor === remoteMemberFunction) { 156 | command = "REMOTE_BROWSER_MEMBER_CONSTRUCTOR" /* BROWSER_MEMBER_CONSTRUCTOR */; 157 | } 158 | else { 159 | command = "REMOTE_BROWSER_MEMBER_CALL" /* BROWSER_MEMBER_CALL */; 160 | } 161 | const ret = electron_1.ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args)); 162 | return metaToValue(ret); 163 | }; 164 | let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name); 165 | descriptor.get = () => { 166 | descriptorFunction.ref = ref; // The member should reference its object. 167 | return descriptorFunction; 168 | }; 169 | // Enable monkey-patch the method 170 | descriptor.set = (value) => { 171 | descriptorFunction = value; 172 | return value; 173 | }; 174 | descriptor.configurable = true; 175 | } 176 | else if (member.type === 'get') { 177 | descriptor.get = () => { 178 | const command = "REMOTE_BROWSER_MEMBER_GET" /* BROWSER_MEMBER_GET */; 179 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, metaId, member.name); 180 | return metaToValue(meta); 181 | }; 182 | if (member.writable) { 183 | descriptor.set = (value) => { 184 | const args = wrapArgs([value]); 185 | const command = "REMOTE_BROWSER_MEMBER_SET" /* BROWSER_MEMBER_SET */; 186 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, metaId, member.name, args); 187 | if (meta != null) 188 | metaToValue(meta); 189 | return value; 190 | }; 191 | } 192 | } 193 | Object.defineProperty(object, member.name, descriptor); 194 | } 195 | } 196 | // Populate object's prototype from descriptor. 197 | // This matches |getObjectPrototype| in rpc-server. 198 | function setObjectPrototype(ref, object, metaId, descriptor) { 199 | if (descriptor === null) 200 | return; 201 | const proto = {}; 202 | setObjectMembers(ref, proto, metaId, descriptor.members); 203 | setObjectPrototype(ref, proto, metaId, descriptor.proto); 204 | Object.setPrototypeOf(object, proto); 205 | } 206 | // Wrap function in Proxy for accessing remote properties 207 | function proxyFunctionProperties(remoteMemberFunction, metaId, name) { 208 | let loaded = false; 209 | // Lazily load function properties 210 | const loadRemoteProperties = () => { 211 | if (loaded) 212 | return; 213 | loaded = true; 214 | const command = "REMOTE_BROWSER_MEMBER_GET" /* BROWSER_MEMBER_GET */; 215 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, metaId, name); 216 | setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members); 217 | }; 218 | return new Proxy(remoteMemberFunction, { 219 | set: (target, property, value) => { 220 | if (property !== 'ref') 221 | loadRemoteProperties(); 222 | target[property] = value; 223 | return true; 224 | }, 225 | get: (target, property) => { 226 | if (property === IS_REMOTE_PROXY) 227 | return true; 228 | if (!Object.prototype.hasOwnProperty.call(target, property)) 229 | loadRemoteProperties(); 230 | const value = target[property]; 231 | if (property === 'toString' && typeof value === 'function') { 232 | return value.bind(target); 233 | } 234 | return value; 235 | }, 236 | ownKeys: (target) => { 237 | loadRemoteProperties(); 238 | return Object.getOwnPropertyNames(target); 239 | }, 240 | getOwnPropertyDescriptor: (target, property) => { 241 | const descriptor = Object.getOwnPropertyDescriptor(target, property); 242 | if (descriptor) 243 | return descriptor; 244 | loadRemoteProperties(); 245 | return Object.getOwnPropertyDescriptor(target, property); 246 | } 247 | }); 248 | } 249 | // Convert meta data from browser into real value. 250 | function metaToValue(meta) { 251 | if (!meta) 252 | return {}; 253 | if (meta.type === 'value') { 254 | return meta.value; 255 | } 256 | else if (meta.type === 'array') { 257 | return meta.members.map((member) => metaToValue(member)); 258 | } 259 | else if (meta.type === 'nativeimage') { 260 | return type_utils_1.deserialize(meta.value); 261 | } 262 | else if (meta.type === 'buffer') { 263 | return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength); 264 | } 265 | else if (meta.type === 'promise') { 266 | return Promise.resolve({ then: metaToValue(meta.then) }); 267 | } 268 | else if (meta.type === 'error') { 269 | return metaToError(meta); 270 | } 271 | else if (meta.type === 'exception') { 272 | if (meta.value.type === 'error') { 273 | throw metaToError(meta.value); 274 | } 275 | else { 276 | throw new Error(`Unexpected value type in exception: ${meta.value.type}`); 277 | } 278 | } 279 | else { 280 | let ret; 281 | if ('id' in meta) { 282 | const cached = getCachedRemoteObject(meta.id); 283 | if (cached !== undefined) { 284 | return cached; 285 | } 286 | } 287 | // A shadow class to represent the remote function object. 288 | if (meta.type === 'function') { 289 | const remoteFunction = function (...args) { 290 | let command; 291 | if (this && this.constructor === remoteFunction) { 292 | command = "REMOTE_BROWSER_CONSTRUCTOR" /* BROWSER_CONSTRUCTOR */; 293 | } 294 | else { 295 | command = "REMOTE_BROWSER_FUNCTION_CALL" /* BROWSER_FUNCTION_CALL */; 296 | } 297 | const obj = electron_1.ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args)); 298 | return metaToValue(obj); 299 | }; 300 | ret = remoteFunction; 301 | } 302 | else { 303 | ret = {}; 304 | } 305 | setObjectMembers(ret, ret, meta.id, meta.members); 306 | setObjectPrototype(ret, ret, meta.id, meta.proto); 307 | if (ret.constructor && ret.constructor[IS_REMOTE_PROXY]) { 308 | Object.defineProperty(ret.constructor, 'name', { value: meta.name }); 309 | } 310 | // Track delegate obj's lifetime & tell browser to clean up when object is GCed. 311 | electronIds.set(ret, meta.id); 312 | setCachedRemoteObject(meta.id, ret); 313 | return ret; 314 | } 315 | } 316 | function metaToError(meta) { 317 | const obj = meta.value; 318 | for (const { name, value } of meta.members) { 319 | obj[name] = metaToValue(value); 320 | } 321 | return obj; 322 | } 323 | function hasSenderId(input) { 324 | return typeof input.senderId === "number"; 325 | } 326 | function handleMessage(channel, handler) { 327 | electron_1.ipcRenderer.on(channel, (event, passedContextId, id, ...args) => { 328 | if (hasSenderId(event)) { 329 | if (event.senderId !== 0 && event.senderId !== undefined) { 330 | console.error(`Message ${channel} sent by unexpected WebContents (${event.senderId})`); 331 | return; 332 | } 333 | } 334 | if (passedContextId === contextId) { 335 | handler(id, ...args); 336 | } 337 | else { 338 | // Message sent to an un-exist context, notify the error to main process. 339 | electron_1.ipcRenderer.send("REMOTE_BROWSER_WRONG_CONTEXT_ERROR" /* BROWSER_WRONG_CONTEXT_ERROR */, contextId, passedContextId, id); 340 | } 341 | }); 342 | } 343 | const enableStacks = process.argv.includes('--enable-api-filtering-logging'); 344 | function getCurrentStack() { 345 | const target = { stack: undefined }; 346 | if (enableStacks) { 347 | Error.captureStackTrace(target, getCurrentStack); 348 | } 349 | return target.stack; 350 | } 351 | // Browser calls a callback in renderer. 352 | handleMessage("REMOTE_RENDERER_CALLBACK" /* RENDERER_CALLBACK */, (id, args) => { 353 | callbacksRegistry.apply(id, metaToValue(args)); 354 | }); 355 | // A callback in browser is released. 356 | handleMessage("REMOTE_RENDERER_RELEASE_CALLBACK" /* RENDERER_RELEASE_CALLBACK */, (id) => { 357 | callbacksRegistry.remove(id); 358 | }); 359 | exports.require = (module) => { 360 | const command = "REMOTE_BROWSER_REQUIRE" /* BROWSER_REQUIRE */; 361 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, module, getCurrentStack()); 362 | return metaToValue(meta); 363 | }; 364 | // Alias to remote.require('electron').xxx. 365 | function getBuiltin(module) { 366 | const command = "REMOTE_BROWSER_GET_BUILTIN" /* BROWSER_GET_BUILTIN */; 367 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, module, getCurrentStack()); 368 | return metaToValue(meta); 369 | } 370 | exports.getBuiltin = getBuiltin; 371 | function getCurrentWindow() { 372 | const command = "REMOTE_BROWSER_GET_CURRENT_WINDOW" /* BROWSER_GET_CURRENT_WINDOW */; 373 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, getCurrentStack()); 374 | return metaToValue(meta); 375 | } 376 | exports.getCurrentWindow = getCurrentWindow; 377 | // Get current WebContents object. 378 | function getCurrentWebContents() { 379 | const command = "REMOTE_BROWSER_GET_CURRENT_WEB_CONTENTS" /* BROWSER_GET_CURRENT_WEB_CONTENTS */; 380 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, getCurrentStack()); 381 | return metaToValue(meta); 382 | } 383 | exports.getCurrentWebContents = getCurrentWebContents; 384 | // Get a global object in browser. 385 | function getGlobal(name) { 386 | const command = "REMOTE_BROWSER_GET_GLOBAL" /* BROWSER_GET_GLOBAL */; 387 | const meta = electron_1.ipcRenderer.sendSync(command, contextId, name, getCurrentStack()); 388 | return metaToValue(meta); 389 | } 390 | exports.getGlobal = getGlobal; 391 | // Get the process object in browser. 392 | Object.defineProperty(exports, 'process', { 393 | enumerable: true, 394 | get: () => exports.getGlobal('process') 395 | }); 396 | // Create a function that will return the specified value when called in browser. 397 | function createFunctionWithReturnValue(returnValue) { 398 | const func = () => returnValue; 399 | isReturnValue.add(func); 400 | return func; 401 | } 402 | exports.createFunctionWithReturnValue = createFunctionWithReturnValue; 403 | const addBuiltinProperty = (name) => { 404 | Object.defineProperty(exports, name, { 405 | enumerable: true, 406 | get: () => exports.getBuiltin(name) 407 | }); 408 | }; 409 | module_names_1.browserModuleNames 410 | .forEach(addBuiltinProperty); 411 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as Electron from 'electron'; 2 | 3 | export { 4 | ClientRequest, 5 | CommandLine, 6 | Cookies, 7 | Debugger, 8 | Dock, 9 | DownloadItem, 10 | IncomingMessage, 11 | MessagePortMain, 12 | ServiceWorkers, 13 | TouchBarButton, 14 | TouchBarColorPicker, 15 | TouchBarGroup, 16 | TouchBarLabel, 17 | TouchBarOtherItemsProxy, 18 | TouchBarPopover, 19 | TouchBarScrubber, 20 | TouchBarSegmentedControl, 21 | TouchBarSlider, 22 | TouchBarSpacer, 23 | WebRequest, 24 | } from 'electron/main'; 25 | 26 | // Taken from `RemoteMainInterface` 27 | export var app: Electron.App; 28 | export var autoUpdater: Electron.AutoUpdater; 29 | export var BrowserView: typeof Electron.BrowserView; 30 | export var BrowserWindow: typeof Electron.BrowserWindow; 31 | export var clipboard: Electron.Clipboard; 32 | export var contentTracing: Electron.ContentTracing; 33 | export var crashReporter: Electron.CrashReporter; 34 | export var desktopCapturer: Electron.DesktopCapturer; 35 | export var dialog: Electron.Dialog; 36 | export var globalShortcut: Electron.GlobalShortcut; 37 | export var inAppPurchase: Electron.InAppPurchase; 38 | export var ipcMain: Electron.IpcMain; 39 | export var Menu: typeof Electron.Menu; 40 | export var MenuItem: typeof Electron.MenuItem; 41 | export var MessageChannelMain: typeof Electron.MessageChannelMain; 42 | export var nativeImage: typeof Electron.nativeImage; 43 | export var nativeTheme: Electron.NativeTheme; 44 | export var net: Electron.Net; 45 | export var netLog: Electron.NetLog; 46 | export var Notification: typeof Electron.Notification; 47 | export var powerMonitor: Electron.PowerMonitor; 48 | export var powerSaveBlocker: Electron.PowerSaveBlocker; 49 | export var protocol: Electron.Protocol; 50 | export var screen: Electron.Screen; 51 | export var session: typeof Electron.session; 52 | export var ShareMenu: typeof Electron.ShareMenu; 53 | export var shell: Electron.Shell; 54 | export var systemPreferences: Electron.SystemPreferences; 55 | export var TouchBar: typeof Electron.TouchBar; 56 | export var Tray: typeof Electron.Tray; 57 | export var webContents: typeof Electron.webContents; 58 | export var webFrameMain: typeof Electron.webFrameMain; 59 | 60 | // Taken from `Remote` 61 | export function getCurrentWebContents(): Electron.WebContents; 62 | export function getCurrentWindow(): Electron.BrowserWindow; 63 | export function getGlobal(name: string): any; 64 | export var process: NodeJS.Process; 65 | export var require: NodeJS.Require; 66 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/main/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/src/main'; 2 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/main/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/src/main') 2 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@electron/remote", 3 | "version": "2.1.2", 4 | "main": "renderer/index.js", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/electron/remote" 9 | }, 10 | "peerDependencies": { 11 | "electron": ">= 13.0.0" 12 | }, 13 | "devDependencies": { 14 | "@types/chai": "^4.2.11", 15 | "@types/chai-as-promised": "^7.1.2", 16 | "@types/dirty-chai": "^2.0.2", 17 | "@types/mocha": "^7.0.2", 18 | "@types/node": "^14.17.0", 19 | "chai": "^4.2.0", 20 | "chai-as-promised": "^7.1.1", 21 | "dirty-chai": "^2.0.1", 22 | "electron": "22.x", 23 | "mocha": "^10.1.0", 24 | "mocha-junit-reporter": "^1.23.3", 25 | "mocha-multi-reporters": "^1.1.7", 26 | "ts-node": "^8.10.2", 27 | "typescript": "^4.1.3", 28 | "walkdir": "^0.4.1", 29 | "yargs": "^15.3.1" 30 | }, 31 | "scripts": { 32 | "prepare": "tsc", 33 | "test": "electron test --extension=ts --require=ts-node/register --exit --js-flags=--expose_gc", 34 | "test:ci": "yarn test --reporter=mocha-multi-reporters --reporter-options=configFile=.circleci/mocha-reporter-config.json" 35 | }, 36 | "files": [ 37 | "README.md", 38 | "package.json", 39 | "main", 40 | "renderer", 41 | "dist/src", 42 | "index.d.ts" 43 | ], 44 | "types": "index.d.ts" 45 | } 46 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/renderer/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/src/renderer'; 2 | -------------------------------------------------------------------------------- /node_modules/@electron/remote/renderer/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/src/renderer') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NewFlow", 3 | "version": "0.0.1", 4 | "description": "A YouTube desktop client", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "NEWFLOW_DEBUG=1 electron . --user-data-dir=./electron-data --enable-features=UseOzonePlatform --ozone-platform=wayland" 8 | }, 9 | "author": "malisipi", 10 | "license": "Apache-2.0" 11 | } 12 | -------------------------------------------------------------------------------- /pip.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Picture-in-Picture 7 | 8 | 9 | 10 | 11 | 12 |
13 | close 14 |
0:00/0:00
15 | 16 |
17 | volume_up 18 | skip_previous 19 | fast_rewind 20 | play_arrow 21 | fast_forward 22 | skip_next 23 | fullscreen 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /preload.js: -------------------------------------------------------------------------------- 1 | window.electron = require('@electron/remote'); -------------------------------------------------------------------------------- /scripts/create_launcher.vbs: -------------------------------------------------------------------------------- 1 | Set fso = CreateObject("Scripting.FileSystemObject") 2 | Set shell = CreateObject("WScript.Shell") 3 | script_dir = fso.GetParentFolderName(WScript.ScriptFullName) 4 | app_dir = fso.GetParentFolderName(script_dir) 5 | electron_dir = fso.GetParentFolderName(fso.GetParentFolderName(app_dir)) 6 | desktop_dir = shell.SpecialFolders("Desktop") 7 | apps_dir = shell.ExpandEnvironmentStrings("%APPDATA%") & "\\Microsoft\\Windows\\Start Menu\\Programs\\" 8 | 9 | Set desktop_link = shell.CreateShortcut(desktop_dir & "\NewFlow.lnk") 10 | desktop_link.Arguments = app_dir 11 | desktop_link.Description = "Your Free Client" 12 | desktop_link.IconLocation = app_dir & "\\assets\\newflow.ico" 13 | desktop_link.TargetPath = electron_dir & "\\electron.exe" 14 | desktop_link.WorkingDirectory = app_dir 15 | desktop_link.Save 16 | 17 | Set app_link = shell.CreateShortcut(apps_dir & "\NewFlow.lnk") 18 | app_link.Arguments = app_dir 19 | app_link.Description = "Your Free Client" 20 | app_link.IconLocation = app_dir & "\\assets\\newflow.ico" 21 | app_link.TargetPath = electron_dir & "\\electron.exe" 22 | app_link.WorkingDirectory = app_dir 23 | app_link.Save -------------------------------------------------------------------------------- /scripts/create_launcher_with_global_electron.vbs: -------------------------------------------------------------------------------- 1 | Set fso = CreateObject("Scripting.FileSystemObject") 2 | Set shell = CreateObject("WScript.Shell") 3 | script_dir = fso.GetParentFolderName(WScript.ScriptFullName) 4 | app_dir = fso.GetParentFolderName(script_dir) 5 | desktop_dir = shell.SpecialFolders("Desktop") 6 | apps_dir = shell.ExpandEnvironmentStrings("%APPDATA%") & "\\Microsoft\\Windows\\Start Menu\\Programs\\" 7 | 8 | Set desktop_link = shell.CreateShortcut(desktop_dir & "\NewFlow.lnk") 9 | desktop_link.Arguments = app_dir 10 | desktop_link.Description = "Your Free Client" 11 | desktop_link.IconLocation = app_dir & "\\assets\\newflow.ico" 12 | desktop_link.TargetPath = "electron.exe" 13 | desktop_link.WorkingDirectory = app_dir 14 | desktop_link.Save 15 | 16 | Set app_link = shell.CreateShortcut(apps_dir & "\NewFlow.lnk") 17 | app_link.Arguments = app_dir 18 | app_link.Description = "Your Free Client" 19 | app_link.IconLocation = app_dir & "\\assets\\newflow.ico" 20 | app_link.TargetPath = "electron.exe" 21 | app_link.WorkingDirectory = app_dir 22 | app_link.Save -------------------------------------------------------------------------------- /scripts/install-desktop-file: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p ~/.local/share/icons/hicolor/scalable/apps/ 3 | cp ./assets/newflow.svg ~/.local/share/icons/hicolor/scalable/apps/newflow.svg 4 | cp NewFlow.desktop /tmp/_newflow 5 | echo Icon=$HOME/.local/share/icons/hicolor/scalable/apps/newflow.svg >> /tmp/_newflow 6 | echo "Categories=AudioVideo;Audio;Video;Player;TV;Network;" >> /tmp/_newflow 7 | echo Path=$(pwd) >> /tmp/_newflow 8 | echo Exec=electron $(pwd) $NEWFLOW_FLAGS >> /tmp/_newflow 9 | mkdir ~/.local/share/applications/ 10 | cp /tmp/_newflow ~/.local/share/applications/NewFlow.desktop 11 | rm /tmp/_newflow 12 | -------------------------------------------------------------------------------- /scripts/remove_launcher.vbs: -------------------------------------------------------------------------------- 1 | Set fso = CreateObject("Scripting.FileSystemObject") 2 | Set shell = CreateObject("WScript.Shell") 3 | 4 | desktop_dir = shell.SpecialFolders("Desktop") 5 | apps_dir = shell.ExpandEnvironmentStrings("%APPDATA%") & "\\Microsoft\\Windows\\Start Menu\\Programs\\" 6 | 7 | fso.DeleteFile(desktop_dir & "\\NewFlow.lnk") 8 | fso.DeleteFile(apps_dir & "\\NewFlow.lnk") -------------------------------------------------------------------------------- /scripts/uninstall-desktop-file: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm ~/.local/share/icons/hicolor/scalable/apps/newflow.svg 3 | rm ~/.local/share/applications/NewFlow.desktop -------------------------------------------------------------------------------- /windows-setup/.gitignore: -------------------------------------------------------------------------------- 1 | *.spec 2 | dist 3 | build 4 | *.exe 5 | -------------------------------------------------------------------------------- /windows-setup/build_setup.cmd: -------------------------------------------------------------------------------- 1 | :: pip install PyInstaller 2 | python -m PyInstaller setup.py --noconsole --windowed --onefile -i ../assets/newflow.ico -------------------------------------------------------------------------------- /windows-setup/setup.py: -------------------------------------------------------------------------------- 1 | setup_version = "0.0.3"; 2 | 3 | import urllib.request; 4 | from zipfile import ZipFile; 5 | import tempfile; 6 | import os; 7 | import os.path; 8 | import shutil; 9 | import webbrowser; 10 | import subprocess; 11 | import sys; 12 | import tkinter; 13 | from tkinter import ttk; 14 | 15 | temp_dir = tempfile.gettempdir()+"\\"; 16 | newflow_download = "https://github.com/malisipi/NewFlow/archive/refs/heads/main.zip"; 17 | electron_download = "https://github.com/electron/electron/releases/download/v31.4.0/electron-v31.4.0-win32-x64.zip"; 18 | ytextratorjs_download = "https://github.com/malisipi/yt-extractor.js/archive/refs/heads/main.zip"; 19 | electron_ver = "v31.4.0"; 20 | 21 | def read_license(): 22 | webbrowser.open("https://www.apache.org/licenses/LICENSE-2.0.txt"); 23 | 24 | def open_website(): 25 | webbrowser.open("https://github.com/malisipi/NewFlow/"); 26 | 27 | def update_state(info): 28 | state_text["text"] = info; 29 | show_progress(0,0,1); 30 | setup_window.update(); 31 | 32 | def show_progress(block_num, block_size, total_size): 33 | global state 34 | if(total_size<1): 35 | if(state_progress["mode"] != "indeterminate"): 36 | state_progress["mode"] = "indeterminate"; 37 | state_progress["value"]=(block_num%250)/250; 38 | else: 39 | if(state_progress["mode"] != "determinate"): 40 | state_progress["mode"] = "determinate"; 41 | state_progress["value"]=int((block_num*block_size*100)/total_size); 42 | setup_window.update(); 43 | 44 | def newflow_install(): 45 | update_state("Downloading Electron Launcher..."); 46 | if(not os.path.exists(temp_dir + "electron" + electron_ver + ".zip")): 47 | urllib.request.urlretrieve(electron_download, temp_dir + "electron" + electron_ver + ".zip", show_progress); 48 | 49 | update_state("Downloading NewFlow..."); 50 | urllib.request.urlretrieve(newflow_download, temp_dir + "newflow.zip", show_progress); 51 | 52 | update_state("Downloading yt-extractor..."); 53 | urllib.request.urlretrieve(ytextratorjs_download, temp_dir + "ytextractor.zip", show_progress); 54 | 55 | update_state("Extracting Electron Launcher..."); 56 | zip_archive = ZipFile(temp_dir + "electron" + electron_ver + ".zip"); 57 | zip_archive.extractall("C:/Programs/NewFlow/"); 58 | os.remove("C:/Programs/NewFlow/resources/default_app.asar"); 59 | 60 | update_state("Extracting NewFlow..."); 61 | zip_archive = ZipFile(temp_dir + "newflow.zip"); 62 | zip_archive.extractall("C:/Programs/NewFlow/resources/"); 63 | if(os.path.exists("C:/Programs/NewFlow/resources/app")): 64 | if(os.path.exists("C:/Programs/NewFlow/resources/app/dbs")): 65 | if(os.path.exists("C:/Programs/NewFlow/__dbs_backup")): 66 | shutil.rmtree("C:/Programs/NewFlow/__dbs_backup"); 67 | os.rename("C:/Programs/NewFlow/resources/app/dbs","C:/Programs/NewFlow/__dbs_backup"); 68 | shutil.rmtree("C:/Programs/NewFlow/resources/app"); 69 | os.rename("C:/Programs/NewFlow/resources/NewFlow-main","C:/Programs/NewFlow/resources/app"); 70 | if(os.path.exists("C:/Programs/NewFlow/__dbs_backup")): 71 | os.rename("C:/Programs/NewFlow/__dbs_backup","C:/Programs/NewFlow/resources/app/dbs"); 72 | else: 73 | os.rename("C:/Programs/NewFlow/resources/NewFlow-main","C:/Programs/NewFlow/resources/app"); 74 | if(not os.path.exists("C:/Programs/NewFlow/resources/app/dbs")): 75 | os.mkdir("C:/Programs/NewFlow/resources/app/dbs"); 76 | 77 | update_state("Extracting yt-extractor..."); 78 | zip_archive = ZipFile(temp_dir + "ytextractor.zip"); 79 | zip_archive.extractall("C:/Programs/NewFlow/resources/app/"); 80 | if(os.path.exists("C:/Programs/NewFlow/resources/app/yt-extractor")): 81 | shutil.rmtree("C:/Programs/NewFlow/resources/app/yt-extractor"); 82 | os.rename("C:/Programs/NewFlow/resources/app/yt-extractor.js-main","C:/Programs/NewFlow/resources/app/yt-extractor"); 83 | 84 | update_state("Creating Shortcuts"); 85 | subprocess.run(["wscript", "C:/Programs/NewFlow/resources/app/scripts/create_launcher.vbs"]); 86 | 87 | update_state("Finished!"); 88 | show_progress(1,1,1); 89 | 90 | def clear_cache(): 91 | if(os.path.exists(temp_dir + "electron" + electron_ver + ".zip")): 92 | os.remove(temp_dir + "electron" + electron_ver + ".zip"); 93 | if(os.path.exists(temp_dir + "newflow.zip")): 94 | os.remove(temp_dir + "newflow.zip"); 95 | if(os.path.exists(temp_dir + "ytextractor.zip")): 96 | os.remove(temp_dir + "ytextractor.zip"); 97 | 98 | setup_window = tkinter.Tk(); 99 | setup_window.title("NewFlow - Setup " + setup_version); 100 | setup_window.resizable(False, False); 101 | setup_window.geometry('400x200'); 102 | 103 | state_text = tkinter.Label(setup_window, text="Setup is ready!", justify="center"); 104 | state_text.place(x=60, y=40, width=280, height=25); 105 | state_progress = ttk.Progressbar(setup_window, length=100); 106 | state_progress.place(x=60, y=80, width=280, height=25); 107 | install_button = ttk.Button(setup_window, text='Install NewFlow', command=newflow_install); 108 | install_button.place(x=100, y=120, width=200, height=25); 109 | website_button = ttk.Button(setup_window, text='Open Website', command=open_website); 110 | website_button.place(x=20, y=155, width=100, height=25); 111 | license_button = ttk.Button(setup_window, text='Read License', command=read_license); 112 | license_button.place(x=280, y=155, width=100, height=25); 113 | clear_cache_button = ttk.Button(setup_window, text='Clear Cache', command=clear_cache); 114 | clear_cache_button.place(x=150, y=155, width=100, height=25); 115 | 116 | setup_window.mainloop(); 117 | --------------------------------------------------------------------------------