├── .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 
2 |
3 | 
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 | >
35 | > This project is intended to demonstrate technical concepts and is not designed or endorsed for production or commercial use.
36 | > This software is provided "as-is" without warranty of any kind, express or implied. The authors are not liable for any misuse, damage, or legal consequences resulting from the use of this software.
37 | >
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 |
--------------------------------------------------------------------------------
/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 | 
5 |
6 | ## Search
7 | 
8 | 
9 |
10 | ## Player
11 | 
12 |
13 | ## Side Navigation
14 |
15 | 
16 |
17 | ## Queue view
18 |
19 | 
20 |
21 | ## Comments
22 |
23 | 
24 |
25 | ## Following Page
26 |
27 | 
28 |
29 | ## Feeds
30 |
31 | 
32 |
33 | ## Pop-Up Video Player
34 |
35 | 
36 |
37 | ## Artist View
38 |
39 | 
--------------------------------------------------------------------------------
/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 |
61 |
62 |
63 |
64 |
65 |
local_fire_department
66 |
67 |
68 |
72 |
76 |
80 |
84 |
88 |
92 |
96 |
100 |
101 | search
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
0.25
114 |
0.50
115 |
0.75
116 |
117 |
1.25
118 |
1.50
119 |
1.75
120 |
2
121 |
2.50
122 |
3
123 |
3.50
124 |
4
125 |
126 |
127 |
128 |
filter_retrolux
129 |
video_settings
130 |
tune
131 |
edit_audio
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
Reset
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
+5
151 |
+4
152 |
+3
153 |
+2
154 |
+1
155 |
156 |
-1
157 |
-2
158 |
-3
159 |
-4
160 |
-5
161 |
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 |
notifications
198 |
199 |
200 |
201 | favorite
202 | playlist_add
203 | headphones
204 | picture_in_picture
205 | download
206 | share
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
Playlist
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
Name
237 |
238 |
Video Count
239 |
Followers
240 |
241 |
Description
242 |
notifications
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
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 | list
288 | pause
289 | close
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 | [](https://circleci.com/gh/electron/remote/tree/main)
4 | [](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 |
--------------------------------------------------------------------------------