Compare commits

...

1288 Commits

Author SHA1 Message Date
eWɘyn 59ea4472da
Merge branch 'nativefier:master' into gh-pages 2023-11-30 09:56:58 +02:00
Jia Hao 9078a3071a
Nativefier is unmaintained 2023-09-29 21:45:16 +08:00
Adam Weeden 45d7981761 Update changelog for `v52.0.0` 2023-08-25 14:11:56 -04:00
Adam Weeden 2e0fe9ea33
Update to Electron 25.7 (#1566) 2023-08-25 14:07:42 -04:00
Adam Weeden 64157c3c5d
Update to Electron 25 (#1559)
This is intended to get Electron updated to 25. There are no known bugs
in this release.

As well this includes a fix for an existing bug I noticed where child
windows in Windows received a menu bar when they should not have.
2023-08-25 09:10:05 -04:00
Adam Weeden be418d4349 Update changelog for `v51.0.1` 2023-08-04 18:36:48 -04:00
Adam Weeden c39932731d
`npm i` in the Dockerfile to esnure we have what we need to build + test (#1557) 2023-08-03 17:44:36 -04:00
Adam Weeden dea954bea8 Update changelog for `v51.0.0` 2023-08-03 14:43:33 -04:00
Adam Weeden 051622d58e
Update Electron to 21 + Node to 16 (#1550) 2023-08-03 13:35:19 -04:00
Matthew Ruzzi fae8edd183
Update link to Development Guide (#1544) 2023-07-20 12:33:49 -04:00
Adam Weeden 443d6cacdf Update changelog for `v50.1.1` 2023-03-27 13:28:23 -04:00
Adam Weeden 739c90b756 Fix shrinkwrap versions back to lockfileVersion 1 (node 12) 2023-03-27 13:27:15 -04:00
Tobias 0d33985c46
Fix typo "electon" -> "electron" (#1492) 2023-03-24 17:07:58 -04:00
Adam Weeden be3b0cf5de Update changelog for `v50.1.0` 2023-03-24 17:05:47 -04:00
Adam Weeden cef8e42251 Fix directory and add clarification for new smoke test 4 2023-03-24 16:48:16 -04:00
Adam Weeden dacfcd2cb8 Add some type hinting post-package update for Axios 2023-03-24 16:47:54 -04:00
Adam Weeden 6c867925bb Update outdated shrinkwrap files 2023-03-24 16:19:05 -04:00
Adam Weeden 69625f1a01
Fix broken tests (#1523) 2023-03-24 14:16:49 -04:00
Johan von Forstner 79009e87cd
Add getDisplayMedia and PipeWire support (#1477)
I'm picking up @RickStanley's abandoned PR #1321 again to add
screensharing support (fixes #927), with the following additional
changes:

- In newer Electron versions, `desktopCapturer.getSources` must be
called from the main process, so I solved this with an IPC call.
- Importing from `./helpers/helpers` in 'preload.ts' does not work, as
was mentioned by @DimICE in
https://github.com/nativefier/nativefier/pull/1321#issuecomment-1001518035.
I'm not very familiar with TypeScript or Electron, so not sure why that
is and how it could be solved - for now I just copied the referenced
`isWayland` function to `preload.ts`.
- Add a screensharing test to the manual test script, as requested by
@ronjouch in
https://github.com/nativefier/nativefier/pull/1321#issuecomment-1006725818

As far as I understood from the discussion in #1321, the last point was
basically the only thing that was missing to get this merged, correct?

---------

Co-authored-by: Rick Stanley <rick-stanley@outlook.com>
Co-authored-by: Rick Stanley <rick.stanley@lambda3.com.br>
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2023-03-23 11:50:19 -04:00
Ronan Jouchet 8d3396acc3 ESLint: scrap unused var 2022-11-07 18:37:03 -05:00
Ronan Jouchet ec0c1461ee Update changelog for `v50.0.1` 2022-11-07 18:29:29 -05:00
Ronan Jouchet 379e807bc2 Bump default Electron from 19.0.17 to 19.1.4 2022-11-07 18:18:23 -05:00
Ronan Jouchet 2335dbce7d Bump axios from 0.x to 1.x. No breaking changes for us, https://github.com/axios/axios/blob/v1.x/CHANGELOG.md#100---2022-10-04 2022-11-07 18:12:12 -05:00
Ronan Jouchet f42e94646a Relock app dependencies 2022-11-07 18:08:34 -05:00
Ronan Jouchet 14f8535050 Relock CLI dependencies 2022-11-07 18:07:51 -05:00
Adam777 f046b61a6d
Maximize window visual glitch on Windows fix (fix #1447) (PR #1448)
Fix for [Maximize window visual glitch on Windows · Issue #1447 · nativefier/nativefier](https://github.com/nativefier/nativefier/issues/1447)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2022-11-07 17:55:11 -05:00
Ronan Jouchet 1fd046798d CI: test on 12 and **19**, now that 19 is out 2022-11-07 17:34:20 -05:00
Ronan Jouchet ba7244f77a External URL protocols: add zoommtg as no-confirmation
Also, document the current state of things.
Follow-up of https://github.com/nativefier/nativefier/pull/1463 .
2022-11-07 17:29:28 -05:00
Alvaro f22750b41c
CATALOG.md: Microsoft Teams with css inject (PR #1469)
* Microsoft Teams with custom css injected

I wasted some time trying to setup Microsoft Teams using nativefier, and some issues pointed me in the right direction. I decided to not only stop there, but to inject some css to hide some unnecessary elements from a desktop app, like an app download button and a waffle button that sits on the top left corner.

* Update CATALOG.md

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2022-10-17 16:40:01 -04:00
Alvaro 229bc71935
CATALOG.md: WhatsApp custom CSS for native-looking app on macOS (#1468)
Added injected css to make WhatsApp more native-like in macOS
2022-10-04 22:30:18 -04:00
Ronan Jouchet b310315604 Update changelog for `v50.0.0` 2022-09-17 22:48:01 -04:00
Ronan Jouchet cc957e70d1 Bump default Electron to 19.0.17 (from .14), with security fixes
- https://github.com/electron/electron/releases/tag/v19.0.15
- https://github.com/electron/electron/releases/tag/v19.0.16
- https://github.com/electron/electron/releases/tag/v19.0.17
2022-09-17 22:35:29 -04:00
Ronan Jouchet 93205accd5 relock app 2022-09-17 22:31:05 -04:00
Ronan Jouchet f53827a8e0 relock cli 2022-09-17 22:30:33 -04:00
Ronan Jouchet 840fe4a199 Add validation to opening URLs in external desktop handler (fix #1459) 2022-09-17 22:22:19 -04:00
Ronan Jouchet eecc33c1aa Fix double-navigation to pages
setupNativefierWindow is already called in createMainWindow !
No need to call it again here in browser-window-created!
Double-navs weren't visible with internal links but they were with external links
2022-09-17 22:02:10 -04:00
Ronan Jouchet be2180b53f Update changelog for `v49.0.1` 2022-08-28 21:04:15 -04:00
Ronan Jouchet 5f1e2cbf55 Revert node types to 12.x, from 14.x accidentally set which is wrong since we still support 12.x 2022-08-28 21:01:19 -04:00
Ronan Jouchet 1933f226cc Bump default Electron to 19.0.14 (from .10)
- https://github.com/electron/electron/releases/tag/v19.0.11
- https://github.com/electron/electron/releases/tag/v19.0.12
- https://github.com/electron/electron/releases/tag/v19.0.13
- https://github.com/electron/electron/releases/tag/v19.0.14
2022-08-28 20:56:28 -04:00
Ronan Jouchet f337401e34 relock app 2022-08-28 20:42:16 -04:00
Ronan Jouchet c1a47ae6a8 relock cli 2022-08-28 20:41:15 -04:00
Adam Weeden c8fc0b6923
macOS: Move handling of "universal" apps to electron-packager instead of our own thing (#1443)
Supported since [electron-packager 15.5.0](https://github.com/electron/electron-packager/releases/tag/v15.5.0).

This should fix #1405  as well
2022-08-01 22:44:23 -04:00
Ronan Jouchet 357f4a9693 Update changelog for `v49.0.0` 2022-07-30 13:48:46 -04:00
Sirisak Lueangsaksri 57636b9022
macOS: Fix "main window cannot be activated" (fix #1415, PR #1417) 2022-07-30 13:31:18 -04:00
Ronan Jouchet b362fbec3b Relock deps to get gitcloud 0.2.4 with "fetch" log fix
https://github.com/nativefier/gitcloud-client/pull/4
2022-07-30 13:27:39 -04:00
Ronan Jouchet f59564fd6d Bump default Electron to 19.0.10
https://github.com/electron/electron/releases/tag/v19.0.10
2022-07-30 13:25:52 -04:00
Adam Weeden 347c06d250
Remove arch ia32 as it's no longer supported in Electron 19 (fix #1439, PR #1441) 2022-07-30 13:19:45 -04:00
Adam Weeden 82ff609a91
Fix playwright tests on Linux (#1440) 2022-07-30 13:15:30 -04:00
Ronan Jouchet 0de2463f2b Docker: actually let's use *lts*-alpine, for more futureproof-ness 2022-07-24 12:26:22 -04:00
Ronan Jouchet c725243152 Docker: upgrade base node-alpine img from 12 to 18
Node 12 makes PlayWright complain,
https://github.com/nativefier/nativefier/runs/7489079540?check_suite_focus=true
2022-07-24 12:25:09 -04:00
Ronan Jouchet 73bc316c99 HACKING / major-upgrading Electron: link to blog 2022-07-24 12:13:06 -04:00
Ronan Jouchet 01ef78e37c Update changelog for `v48.0.0` 2022-07-24 12:12:32 -04:00
Ronan Jouchet 1aa7760aa2
Major-bump default Electron from 18 to 19 (#1438)
https://github.com/electron/electron/releases/tag/v19.0.0
https://github.com/electron/electron/releases/tag/v19.0.1
https://github.com/electron/electron/releases/tag/v19.0.2
https://github.com/electron/electron/releases/tag/v19.0.3
https://github.com/electron/electron/releases/tag/v19.0.4
https://github.com/electron/electron/releases/tag/v19.0.5
https://github.com/electron/electron/releases/tag/v19.0.6
https://github.com/electron/electron/releases/tag/v19.0.7
https://github.com/electron/electron/releases/tag/v19.0.8
https://github.com/electron/electron/releases/tag/v19.0.9
2022-07-24 11:56:07 -04:00
Ronan Jouchet f6e585b3cc relock app 2022-07-24 11:25:33 -04:00
Ronan Jouchet 1f67a9f9a7
Bump jest to 28 (#1437)
https://jestjs.io/blog/2022/04/25/jest-28
https://jestjs.io/docs/upgrading-to-jest28
https://github.com/facebook/jest/blob/main/CHANGELOG.md#2800

Co-authored-by: Adam Weeden <adamweeden@gmail.com>
2022-07-24 11:24:59 -04:00
Ronan Jouchet a4ef7481de Make URL override smarter (thx Adam)
to not crash local PlayWright tests that here might get
Electron command-line options.

See https://github.com/nativefier/nativefier/pull/1437#issuecomment-1193315259
2022-07-24 10:34:31 -04:00
Ronan Jouchet e12da46064 Playwright tests: document DEBUG mode 2022-07-23 20:05:48 -04:00
Ronan Jouchet c3e010f881 re-introduce yargs fix but without upgrading jest to 28 breaking tests, to work on it separately 2022-07-23 19:27:26 -04:00
Ronan Jouchet 9db7ad050a Revert "nit" and "bump jest and fix yargs typing"
This reverts commit b737c40931.
This reverts commit 8eb05ded9b.
2022-07-23 19:20:24 -04:00
Ronan Jouchet b737c40931 nit 2022-07-23 19:10:44 -04:00
Ronan Jouchet 8eb05ded9b bump jest and fix yargs typing 2022-07-23 19:07:54 -04:00
Ronan Jouchet 207bb1790f bump jest **types**, so far it still builds 2022-07-23 18:43:09 -04:00
Ronan Jouchet 6b3db5de11 relock cli 2022-07-23 18:39:04 -04:00
Ronan Jouchet d499b5f1c3 bug.yml & feature.yml & question.yml: more 2022-07-23 18:31:24 -04:00
Ronan Jouchet 14c61af12c bug.yml & feature.yml & question.yml: more 2022-07-23 18:30:03 -04:00
Ronan Jouchet 79ff80ca53 bug.yml & feature.yml & question.yml: more 2022-07-23 18:28:22 -04:00
Ronan Jouchet eebdf90bc7 bug.yml & feature.yml: more 2022-07-23 18:23:58 -04:00
Ronan Jouchet 7fdc4b8515 bug.yml & feature.yml: more 2022-07-23 18:21:33 -04:00
Ronan Jouchet a042812dca bug.yml & feature.yml: more 2022-07-23 18:19:39 -04:00
Ronan Jouchet 7ab50a0e2d bug.yml: more 2022-07-23 18:05:25 -04:00
Ronan Jouchet 8e6363a634 bug.yml: more 2022-07-23 18:04:37 -04:00
Ronan Jouchet dae20c62e1 bug.yml: more 2022-07-23 17:59:45 -04:00
Ronan Jouchet 6c48f5b013 bug.yml: more 2022-07-23 17:58:41 -04:00
Ronan Jouchet 9c099ce6bc bug.yml: more 2022-07-23 17:57:05 -04:00
Ronan Jouchet 4967e47aee bug.yml: more 2022-07-23 17:55:28 -04:00
Ronan Jouchet 4f38119682 bug.yml: first edits 2022-07-23 17:54:23 -04:00
Matthew Ruzzi 9ed379024d
Switch issues from GitHub "issue templates" to new/beta "issue forms" (fix #1258) (#1425) 2022-07-23 17:43:48 -04:00
Octavio Ietsugu 8907b9dc97
CATALOG.md: add a new recipe for using interactive buttons on Notion (#1430)
I've used Nativefier along with Notion in the last couple days and I was able to use interactive buttons on my Notion exported page, something that was not possible originally. As such, please consider the following recipe.

Co-authored-by: Dev pop-os <dev+pop-os@mevio.com.br>
2022-07-23 17:39:00 -04:00
Ronan Jouchet c1d40ec181 Update changelog for `v47.2.1` 2022-06-27 16:01:22 -04:00
Ronan Jouchet 7221b39a2f Bump default Electron to 18.3.5
https://github.com/electron/electron/releases/tag/v18.3.2
https://github.com/electron/electron/releases/tag/v18.3.3
https://github.com/electron/electron/releases/tag/v18.3.4
https://github.com/electron/electron/releases/tag/v18.3.5
2022-06-27 15:56:57 -04:00
Ronan Jouchet eeac8885cc Relock dependencies 2022-06-27 15:49:33 -04:00
Ronan Jouchet bac08394c5 macOS: fix incorrect "Back" keyboard shortcut (fix #1426) 2022-06-27 09:34:13 -04:00
Ronan Jouchet 5146dc05e5 README: shorter 2022-05-30 12:26:05 -04:00
Ronan Jouchet 1d56cd77bc README: leftover 2022-05-30 12:23:57 -04:00
Ronan Jouchet 4d0b913133 Update changelog for `v47.2.0` 2022-05-30 10:22:03 -04:00
Ronan Jouchet ea0381f226 README: more readable 2022-05-30 10:18:34 -04:00
Ronan Jouchet 3f3108464c CATALOG: cleanup 2022-05-30 10:16:15 -04:00
Ronan Jouchet 0d99e98c2a CATALOG: move in troubleshooting previously in README 2022-05-30 10:15:03 -04:00
Ronan Jouchet c82f1a892e Bump default Electron to 18.3.1
https://github.com/electron/electron/releases/tag/v18.2.1
https://github.com/electron/electron/releases/tag/v18.2.2
https://github.com/electron/electron/releases/tag/v18.2.3
https://github.com/electron/electron/releases/tag/v18.2.4
https://github.com/electron/electron/releases/tag/v18.3.0
https://github.com/electron/electron/releases/tag/v18.3.1
2022-05-30 09:58:42 -04:00
Ronan Jouchet b123ebdd77 README: shorter 2022-05-30 09:52:47 -04:00
Ronan Jouchet 4dd92ef060 README: layout 2022-05-30 09:51:43 -04:00
Ronan Jouchet cbd6ee2c68 README: stop mentioning unmaintained icon repo 2022-05-30 09:51:27 -04:00
Ronan Jouchet c9c81f5583 README: attempt to be more concise, using gh "details" feature 2022-05-30 09:50:28 -04:00
Ronan Jouchet c0210265ab Relock dependencies 2022-05-30 09:42:40 -04:00
Sirisak Lueangsaksri 0df5b8f617
Handle 'open-url' event: support "deep-linking" e.g. for mailto links (PR #1418, fix #1412) 2022-05-30 09:40:13 -04:00
Ronan Jouchet 59a4bb87f9 API.md: fix link to Electron Frameless Window (fix #1382) 2022-05-19 23:04:00 -04:00
Ronan Jouchet 4c6d0b185b Update changelog for `v47.1.3` 2022-05-02 00:32:00 -04:00
Ronan Jouchet ed33836707 Revert accidental leftover package.json changes 2022-05-02 00:31:49 -04:00
Ronan Jouchet 052d5c75a5 Update changelog for `v47.1.2` 2022-05-02 00:29:21 -04:00
Ronan Jouchet 334cbe28a5 Publish: fix Docker build failing because of Playwright, and make a convenience npm task for it 2022-05-02 00:27:58 -04:00
Ronan Jouchet 8bbc7cacbd CI: restore needing successful playwright build to publish 2022-05-02 00:19:18 -04:00
Ronan Jouchet 0afff67c11 Update changelog for `v47.1.1` 2022-05-02 00:13:31 -04:00
Ronan Jouchet 27979cfd42 Fix publish.yml running on ubuntu and thus not able to run playwright tests 2022-05-02 00:13:09 -04:00
Ronan Jouchet 26bd19d6b0 Update changelog for `v47.1.0` 2022-05-02 00:09:07 -04:00
Ronan Jouchet eb81f0c3b2 CI: re-order tasks, for friendlier display in GH's popup limited to 6 lines
This should let us see without scrolling: lint, playwright, and latest-node*
2022-05-01 23:58:37 -04:00
Ronan Jouchet 78e330aa87 Bump default Electron to 18.2.0, from 18.0.3
https://github.com/electron/electron/releases/tag/v18.0.4
https://github.com/electron/electron/releases/tag/v18.1.0
https://github.com/electron/electron/releases/tag/v18.2.0
2022-05-01 23:55:21 -04:00
Ronan Jouchet 0963402264 Revert "Major-bump jest to 28": breaks tsc in weird ways, will retry later.
This reverts commit bd109099ce.
2022-05-01 23:51:04 -04:00
Ronan Jouchet bd109099ce Major-bump jest to 28
No breaking changes for us, I think:
https://jestjs.io/blog/2022/04/25/jest-28
https://jestjs.io/docs/upgrading-to-jest28
2022-05-01 23:30:26 -04:00
Ronan Jouchet 0ff6e87308 Major bump axios to 0.27.x
No breaking changes for us: https://github.com/axios/axios/blob/master/CHANGELOG.md
2022-05-01 23:24:57 -04:00
Ronan Jouchet a95814f5d4 Relock dependencies 2022-05-01 23:22:57 -04:00
Ronan Jouchet c3f0e4777f CATALOG.md: move new "general recipes" section where it belongs, and cleanup 2022-05-01 23:19:58 -04:00
ecarril6 0ab6bb6aed
CATALOG.md: add "Window Size and Position" section (PR #1349) 2022-05-01 23:17:26 -04:00
Ronan Jouchet 1e1c720aa9 CI: you cannot win 2022-05-01 23:10:11 -04:00
Ronan Jouchet eacbaf737f CI: yes yes 2022-05-01 23:06:56 -04:00
Ronan Jouchet 458c7ec178 CI: okay GHA, no variables 2022-05-01 23:06:25 -04:00
Ronan Jouchet 6af4e774e2 CI: fumbling around 2022-05-01 23:02:05 -04:00
Ronan Jouchet 430a129c39 CI: (attempt to) separate/parallelize Playwright tests, for speed 2022-05-01 22:59:39 -04:00
Ronan Jouchet 887347adbb Playwright tests: only run on windows, because mac keeps failing or being too slow 2022-04-26 19:55:30 -04:00
Ronan Jouchet 339fbfb933 Bump minimum macOS version from 10.9 to 10.10 (see #1404) 2022-04-26 19:40:16 -04:00
Adam Weeden d85aab718d
Extend timeout for playwright electron launch + playwright 1.21.1 (#1402) 2022-04-25 10:11:11 -04:00
Adam Weeden ce04b3337c
Add some debugging to the playwright script + add a timeout for the playwright ci (#1400) 2022-04-22 10:51:00 -04:00
Simon Smith 513b9dc93d
Windows: correctly set notifications name - not electron.app.YOURAPPNAME (PR #1394)
This sets the name correctly for Windows notifications.
Currently, notifications name shows as `electron.app.YOURAPPNAME`
2022-04-21 08:54:36 -04:00
Ronan Jouchet 60035a8e74 Bump max tested version of Node for CI/Publish from 17 to 18
Released yesterday: https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V18.md#18.0.0
2022-04-20 22:06:46 -04:00
Adam Weeden e664bc6af8
Add playwright integration testing to the app (PR #1397)
This PR allows us to code playwright integration tests that can potentially replace some of our manual tests and allow us automated testing of the app itself.

Current technical limitations:
* No app level keyboard simulation support (e.g, zoom in, zoom out, etc.)
* No code coverage support, so even though we are testing the app, the code coverage does not reflect this fact
2022-04-20 22:03:49 -04:00
Adam Weeden c42c63a8b0
Fix universal app path logic to resolve to absolute (fix #1398) 2022-04-19 09:56:16 -04:00
Adam Weeden 3a8f66a7b6
macOS: universal architecture app support (fix #1384, PR #1386)
As noted in `API.md`:

- When specifying `universal` you must be building for the `darwin`, `mas`, `mac`, or `osx` platforms. This will generate a universal (M1 and x86) app.
2022-04-18 19:11:31 -04:00
Adam Weeden 10fb19b377
macOS: fix Open In New Tab (fix #1260, PR #1385) 2022-04-18 19:06:44 -04:00
Pedro Algarvio 74bc2d4188
Auto-internal URLs: add VMWare Workspace ONE + SecurID (PR #1391, fix #1390)
Update internal URLs to include VMware's Workspace ONE and SecurID.
2022-04-18 18:46:31 -04:00
Adam Weeden f6852d5208
Update browser versions + add fix for Firefox user agent (#1388) 2022-04-18 18:42:25 -04:00
Adam Weeden f6a1e30085
Change Paste and Match Style shortcut to match Apple's HIG advice (PR #1387, fix #404) 2022-04-18 18:41:27 -04:00
Tristan Koch 6a949ca481
--counter: accept colon character; useful for time-tracking apps with hour:min in title (PR #1378)
Modify regexp to track hour counters such as `(1:23)`.

This is supported by macOS' dock and displays in the red badge:
https://user-images.githubusercontent.com/73974/162786478-609a90e1-5efb-44ba-9aa5-7a3038f0689b.png
2022-04-11 13:18:06 -04:00
Ronan Jouchet 567fede701 Update changelog for `v47.0.0` 2022-04-10 13:30:12 -04:00
Ronan Jouchet 77df5618f4 Bump default Electron to 18.0.3 (from 16.2.2)
As usual, we did our best to adapt to Electron breaking changes in 17/18,
but patches welcome to fix regressions. Release notes with breaking changes:

- https://www.electronjs.org/blog/electron-17-0
- https://www.electronjs.org/blog/electron-18-0

Detailed release notes:

- https://github.com/electron/electron/releases/tag/v17.0.0
- https://github.com/electron/electron/releases/tag/v18.0.0
- https://github.com/electron/electron/releases/tag/v18.0.1
- https://github.com/electron/electron/releases/tag/v18.0.2
- https://github.com/electron/electron/releases/tag/v18.0.3
2022-04-10 13:18:58 -04:00
Ronan Jouchet c215d7a7b2 Update changelog for `v46.2.1` 2022-04-10 11:32:04 -04:00
Ronan Jouchet 94c4131e39 Bump default Electron to 16.2.2 2022-04-10 11:22:30 -04:00
Ronan Jouchet 26e9569b25 Bump default Electron to 16.2.1 (from 16.1.0), with security fixes:
- https://github.com/electron/electron/releases/tag/v16.1.1
- https://github.com/electron/electron/releases/tag/v16.2.0
- https://github.com/electron/electron/releases/tag/v16.2.1
2022-04-10 11:20:40 -04:00
Ronan Jouchet 6f61d73d11 Upgrade dependencies lockfiles
with the usual `npm run relock` .
No major/breaking chages this time.
2022-04-10 11:18:41 -04:00
Ronan Jouchet 2477b59dda Update changelog for `v46.2.0` 2022-03-20 23:44:36 -04:00
Ronan Jouchet e06f6d929d Bump default Electron to 16.1.0 (from 16.0.9), with security fixes:
- https://github.com/electron/electron/releases/tag/v16.0.10
- https://github.com/electron/electron/releases/tag/v16.1.0
2022-03-20 23:36:07 -04:00
Ronan Jouchet 036122ad55 Upgrade dependencies lockfiles
with the usual `npm run relock` .
No major/breaking chages this time.
2022-03-20 23:32:19 -04:00
Tedward747 83dce91c47
Strip LRM and RLM in Linux names (fix #1361, PR #1365)
On Linux (SUSE at least), if `--name` isn't provided and is inferred,
the filename is prepended with the control character
LRM (https://en.wikipedia.org/wiki/Left-to-right_mark) and I'd assume
RLM for anyone who's using that setting.

This PR strips those control characters out of the file name.
2022-03-20 23:27:57 -04:00
Tedward747 2f97a7156b
Remove extra whitespace in UserAgent (fix #1357, PR #1367)
Fixes https://github.com/nativefier/nativefier/issues/1357 , by
not adding a new space when removing the app name from the User-Agent.

Co-authored-by: noxafy <hci@gmx.de>
2022-03-20 23:23:10 -04:00
Tedward747 8dbe7943d4
API.md: Update link for conceal flag (#1364)
Old link went 404, new one has an overview of what it is and has a link to the asar github for anyone wanting technical knowledge
2022-03-02 15:35:48 -05:00
Ronan Jouchet 388eebb58b Update changelog for `v46.1.1` 2022-02-14 23:06:37 -05:00
Ronan Jouchet ad74271b98 Bump default Electron to 16.0.9, from 16.0.8 2022-02-14 23:03:47 -05:00
Ronan Jouchet 5bcf165fe4 Upgrade dependencies
Including axios 0.25 -> 0.25, nothing breaking for us:
https://github.com/axios/axios/releases/tag/v0.26.0
2022-02-14 23:01:36 -05:00
Abhishek Mehandiratta 64ed2a856b
Feature: Add "copy as plain text" in edit menu (PR #1351, fix #1144)
This closes #1144 by adding a "Copy as plain text" function in Edit menu.

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2022-02-10 08:39:56 -05:00
Ronan Jouchet 35fb0fa7ff Update changelog for `v46.1.0` 2022-02-06 18:08:11 -05:00
Ronan Jouchet 5ce6c24a3f Fix lint not running in CI 2022-02-06 17:55:20 -05:00
Ronan Jouchet 5de8a307ab Bump default Electron to 16.0.8, from 16.0.6
- https://github.com/electron/electron/releases/tag/v16.0.7
- https://github.com/electron/electron/releases/tag/v16.0.8
2022-02-06 17:53:45 -05:00
Ronan Jouchet 6d46082220 Bump axios from 0.24.0 to 0.25
See https://github.com/axios/axios/blob/master/CHANGELOG.md#0250-january-18-2022
No obvious breaking changes, I think.
2022-02-06 17:51:20 -05:00
Ronan Jouchet 347df98c77 Make eslint happy 2022-02-06 17:49:30 -05:00
Ronan Jouchet 6b6ef1d12d Relock dependencies 2022-02-06 17:47:06 -05:00
Henry Bridge 9945a5dffe
Add flag --strict-internal-urls to disable domain and subpath matching (PR #1340)
I created this so that Google Meet links don't open in my Google Calendar app for me, but it looks like others have a similar issue (e.g. issue #1304).
2022-02-06 17:40:51 -05:00
Abhishek Mehandiratta 372c1d0b13
Fixes ignored --file-download-options (PR #1350, fix #1275) 2022-02-06 00:02:49 -05:00
Ronan Jouchet e7483549af NATIVEFIER_APPS_DIR: document "new in release", rephrase doc to be the most useful to a first-time user 2022-01-31 17:07:57 -05:00
Matthew Ruzzi c6debd72e0
Allow setting default app destination with env. var. NATIVEFIER_APPS_DIR (PR #1339, #1336) 2022-01-31 16:59:23 -05:00
Ronan Jouchet f8bd696e32 README: mention Snap & AUR repos
See https://github.com/nativefier/nativefier/pull/1348
2022-01-31 16:33:53 -05:00
Ronan Jouchet 088be2d258
HACKING.md: triage guidelines (PR #1338)
As discussed in https://github.com/nativefier/nativefier/issues/1258#issuecomment-1008522842 ,
here’s an attempt to formalize triage,

1. To agree on our triage practices.
2. To ease up recruiting more triagers.
3. To have a reference to point at when a user doesn't understand how we're triaging.
2022-01-31 16:23:47 -05:00
Zachary Talis 31fc580f1c
API.md: Fix broken "insecurity options" link (PR #1345) 2022-01-28 23:01:56 -05:00
Tyler Nickerson aeb6ba1a8c
Add "quiet" flag to suppress all log output (PR #1342)
In working on my own repo [hop](https://github.com/Nickersoft/hop), which uses nativefier under-the-hood, I found it very troublesome to suppress log output when using the programmatic API. This PR just adds a quick "quiet" option which will set the log level to "silent" and suppress all electron and nativefier output (except for errors, of course).

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2022-01-23 16:01:29 -05:00
Ronan Jouchet cd7bd26d3c HACKING.md: link to Hickey 2022-01-10 00:42:36 -05:00
Ronan Jouchet 2274053792 HACKING: markdown format 2022-01-10 00:03:47 -05:00
Ronan Jouchet 8d178b2507 CATALOG.md: Document GCal needs lying about useragent for working notifications (fix #1292) 2022-01-09 08:42:32 -05:00
Ronan Jouchet 25c4da6b4d README: more more more more more nits 2022-01-06 14:33:49 -05:00
Ronan Jouchet 1b30ba5cbb README: more more more more nits 2022-01-06 14:32:43 -05:00
Ronan Jouchet f88244746e README: more more more nits 2022-01-06 14:21:16 -05:00
Ronan Jouchet 8a8a5dd2cc README: more more nits 2022-01-06 14:20:41 -05:00
Ronan Jouchet 37e2f98801 README: more nits 2022-01-06 14:20:12 -05:00
Ronan Jouchet 2445395ad3 README: nits 2022-01-06 14:18:22 -05:00
Ronan Jouchet 8d05fc9b8b Docs: link to Docker Hub repo, lighten README and move some stuff to CATALOG 2022-01-06 14:09:15 -05:00
Ronan Jouchet e4ab2dba5b Update changelog for `v46.0.4` 2022-01-06 10:50:42 -05:00
Ronan Jouchet ae4a0f05b8 CI: (Attempt to) push tag, not unreadable SHA
Might have to retry this one as I'm not sure it's this one or GITHUB_REF,
https://docs.github.com/en/actions/learn-github-actions/environment-variables
2022-01-06 10:50:00 -05:00
Ronan Jouchet 776f2a846d Update changelog for `v46.0.3` 2022-01-06 10:33:22 -05:00
Ronan Jouchet 97cce9196d CI: (Attempt to) push image to our org, not my personal account 2022-01-06 10:32:48 -05:00
Ronan Jouchet 863377d814 Update changelog for `v46.0.2` 2022-01-06 10:16:45 -05:00
Pranav Shikarpur 7b4d172248
CI: Fix Docker Hub image build & push (#1100)
We tried using the auto Docker Hub thingie, but it's flaky.
Reverting to manually pushing as part of release CI.
2022-01-06 10:14:31 -05:00
Ronan Jouchet 0a561a62c0 Update changelog for `v46.0.1` 2022-01-06 09:58:15 -05:00
Ronan Jouchet e3a823c66b Bump default Electron from 16.0.5 to 16.0.6
https://github.com/electron/electron/releases/tag/v16.0.6
2022-01-06 09:43:10 -05:00
Ronan Jouchet 5e3c23267a Fix --widevine broken since 46.0.0
See https://github.com/nativefier/nativefier/pull/1288#issuecomment-1006307669 ,
"the version prefix changed from -wvvmp to +wvcus",

and message from CastLabs:
The v16 series of Electron for Content Security, labeled wvcus, moves to using the Component Updater Service to handle installation of the Widevine CDM, and has incompatible API updates compared to the previous wvvmp releases.
2022-01-06 09:34:20 -05:00
Ronan Jouchet 5276a839bb Update changelog for `v46.0.0` 2022-01-02 06:31:08 -05:00
Adam Weeden d483597320
Upgrade Electron from 13 to 16 (PR #1288)
https://www.electronjs.org/docs/latest/breaking-changes

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2022-01-02 06:17:06 -05:00
Ronan Jouchet 6f4ae587c4 build/ci workflow: pass the node version number with the format expected by GHA/setup-node
and avoid warning "Not found in manifest.  Falling back to download directly from Node"
2021-12-25 16:53:06 -05:00
Ronan Jouchet 16ec3b80fe empty commit to see if cache is indeed used and build is faster (npm ci used to take between 30s and 1m10s) 2021-12-25 16:46:59 -05:00
Ronan Jouchet 797922afb6 build/ci workflow: try to use setup-node-v2 cache to speed up build 2021-12-25 16:42:48 -05:00
Ronan Jouchet 24e8849564 Update changelog for `v45.0.8` 2021-12-06 23:48:19 -05:00
Ronan Jouchet e33aa4ebab Fix 45.0.7 broken because of missing "chalk" dep (fix #1324) 2021-12-06 23:45:09 -05:00
Ronan Jouchet e9e523d957 Update changelog for `v45.0.7` 2021-12-06 16:27:13 -05:00
Ronan Jouchet dab770f719 app pkgjson too 2021-12-06 16:12:40 -05:00
Ronan Jouchet f720f66eb8 Bump default Electron to 13.6.3 with fixes & security fixes
https://github.com/electron/electron/releases/tag/v13.6.3
2021-12-06 16:12:03 -05:00
Ronan Jouchet 58e504de32 document contextIsolation stuff 2021-12-06 16:05:41 -05:00
Ronan Jouchet ec77741494 re-relock and fix script failing to do app if cli failed 2021-12-06 16:00:17 -05:00
Ronan Jouchet df4ca5d079 relock dependencies 2021-12-06 15:56:02 -05:00
Adam Weeden 5f02f14626
Use userAgentFallback for userAgent injection (PR #1316)
Per the suggestion of @fireflinchdev , this seems to alleviate our issues with things like nuking service workers for WhatsApp. The core reason being those service workers were getting the original userAgent, and not the override (if specified). This PR should fix that.

So it should resolve #1312 #719 and hopefully a few future issues as this seems to come up frequently.
2021-11-29 16:53:12 -05:00
Adam Weeden b9c5e2b464
Fix upgrade not working (#1286)
* Attempt to get upgrade working right; in progress

* Got it fixed in Mac

* Fix some linting errors

* Finish fixing upgrade + tests

* Integration testing for global shortcuts

* Regenerate shrinkwrap

* Get rid of deprecated rmdirSync

* Remove instead of rm for 12.x support

* Make dereferencing platform dependent

* Fix folder copy funkiness

* Whoops

* Whoops 2: Extra Whoops

* Update Electron to 13.5.1; Fix auth manual tests

* Rework relock

* Add a request for help.

* Update @types/node to 14
2021-11-29 12:01:20 -05:00
Ronan Jouchet adcf7c4c0c Update changelog for `v45.0.6` 2021-11-22 16:24:24 -05:00
Ronan Jouchet 4405b5d28b Fix icon conversion scripts broken on recent macOS (fix #1277) 2021-11-22 16:18:02 -05:00
Ronan Jouchet 702ff1e4b6 Bump default Electron to 13.6.2
https://github.com/electron/electron/releases/tag/v13.6.2
2021-11-22 16:05:20 -05:00
Ronan Jouchet 601968dfef Relock dependencies 2021-11-22 16:03:07 -05:00
Alexander Weps ca7d25f432
Fix notifications broken since Nativefier 43 / Electron 12 defaulting to contextIsolation:true (PR #1308)
Copy-pastaing details from [Electron 12 breaking changes](https://www.electronjs.org/docs/latest/breaking-changes#planned-breaking-api-changes-120):

> ### Default Changed: `contextIsolation` defaults to `true`[​](https://www.electronjs.org/docs/latest/breaking-changes#default-changed-contextisolation-defaults-to-true "Direct link to heading")
> 
> In Electron 12, `contextIsolation` will be enabled by default. To restore the previous behavior, `contextIsolation: false` must be specified in WebPreferences.
> 
> We [recommend having contextIsolation enabled](https://www.electronjs.org/docs/latest/tutorial/security#3-enable-context-isolation-for-remote-content) for the security of your application.
> 
> Another implication is that `require()` cannot be used in the renderer process unless `nodeIntegration` is `true` and `contextIsolation` is `false`.
> 
> For more details see: [https://github.com/electron/electron/issues/23506](https://github.com/electron/electron/issues/23506)

I find the security drop acceptable, as reverting the new Electron 12 isolation brings us to the previous level of security, and I don't have the time/will to keep the isolation and migrate to the newer better safer thing that Electron >= 12 wants.

Co-authored-by: Radomír Polách <rp@t4d.cz>
2021-11-22 16:00:13 -05:00
Ronan Jouchet 431f531065 CI: bump top Node.js version to 17, which is now stable 2021-11-10 18:41:38 -05:00
Ronan Jouchet e63a275ec9 Update changelog for `v45.0.5` 2021-11-01 15:59:22 -04:00
Ronan Jouchet 916055d05a generate-changelog: don't break if npm out returns 1, which it normally does on outdated deps, which is all the time 2021-11-01 15:57:33 -04:00
Ronan Jouchet d68078f686 Bump axios to 0.24.0
https://github.com/axios/axios/blob/master/CHANGELOG.md
2021-11-01 15:54:03 -04:00
Ronan Jouchet 7cab31f974 Bump to eslint 8
https://eslint.org/docs/8.0.0/user-guide/migrating-to-8.0.0
2021-11-01 15:47:46 -04:00
Ronan Jouchet 94e6c6cf54 Bump default Electron to 13.6.1, with security fixes
- 13.5.2: https://github.com/electron/electron/releases/tag/v13.5.2
- 13.6.0: https://github.com/electron/electron/releases/tag/v13.6.0
- 13.6.1: https://github.com/electron/electron/releases/tag/v13.6.1
2021-11-01 15:37:33 -04:00
Ronan Jouchet 05e2439c56 HACKING.md: Document one more place to look when major-upgrading Electron 2021-10-04 09:32:52 -04:00
Adam Weeden 0d99ce4916
Update Electron to 13.5.1; Fix auth manual tests (#1287) 2021-10-02 22:17:06 -04:00
Ronan Jouchet fadcb73de7 Update changelog for `v45.0.4` 2021-09-24 23:03:48 -04:00
Ronan Jouchet 0570a20c99 Work around "npm shrinkwrap" failing to include some packages in lockfile 2021-09-24 23:01:15 -04:00
Ronan Jouchet 561beda96e Actually actually (TM) include lockfile in npm artifacts
Previous attempt failed by design of `npm pack` / `npm publish`,
as documented at https://docs.npmjs.com/cli/v6/configuring-npm/package-lock-json :

> One key detail about package-lock.json is that it cannot be published,
> and it will be ignored if found in any place other than the toplevel
> package. It shares a format with npm-shrinkwrap.json, which is
> essentially the same file, but allows publication.
>
> This is not recommended unless deploying a CLI tool or otherwise using
> the publication process for producing production packages.

, and we are a CLI tool. Switching to shrinkwrap.
2021-09-24 22:44:07 -04:00
Ronan Jouchet 8fdceee4dc Update changelog for `v45.0.3` 2021-09-24 22:21:33 -04:00
Ronan Jouchet 904f7d0a57 Actually include package-lock.json in npm artifacts, duh 2021-09-24 22:21:09 -04:00
Ronan Jouchet 94142955f1 Update changelog for `v45.0.2` 2021-09-24 22:16:59 -04:00
Adam Weeden 24115bb3bd
Fix regressions in opening windows/tabs, update browser versions (#1284)
* Update electron to 13.4.0 + update browser versions

* Fix injectCSS preventing new windows from opening

* Fix some window/tab opening weirdness/bugs

* Fix unit tests

* Switch to using onResponseStarted to avoid the issues with callbacks in the future

* Clear up comments on onResponseStarted
2021-09-24 22:03:03 -04:00
Adam Weeden d759695e5a
Workaround yargs coerce issue (#1283) 2021-09-22 09:26:49 -04:00
Adam Weeden 46424f9795
Make macOS "bundle identifier" mention Nativefier (fix #866) (#1259)
* Make macos bundle identifier mention nativefier

* Fix @types/debug here too

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-09-21 17:32:55 -04:00
Ronan Jouchet 0d78978d9e Update changelog for `v45.0.1` 2021-09-20 11:45:36 -04:00
Ronan Jouchet e222e99b56 Bump default Electron to 13.4.0 2021-09-20 11:38:15 -04:00
Ronan Jouchet 7ab2c14f4f Deps bumps: @types/jest, eslint-plugin-prettier 2021-09-20 11:32:47 -04:00
Ronan Jouchet 0fbe7d39cb Build: re-introduce a package-lock.json file
They were used a long time ago, then I scrapped them for simplicity to
new contributors. I'm re-considering this and re-introducing one, for
two (maybe three) reasons:

1. Reading on supply chain attacks
2. Build broken because of a dep change (see previous commit broken
   because of a change in yargs @ 17.1.0)
(3.) Performance
2021-09-20 11:25:43 -04:00
Ronan Jouchet fcc3906f52 build:watch script: make it watch project folders 2021-09-20 10:32:32 -04:00
Ronan Jouchet 1a54d286d8 Internal login pages: add (id|auth).atlassian.com (fix #1265) 2021-07-28 23:22:02 -04:00
Ronan Jouchet 33f293120d API.md: document need to use CSS "!important" keyword (fix #1264) 2021-07-28 08:10:31 -04:00
Ronan Jouchet 167f1e3be8 Update changelog for `v45.0.0` 2021-07-19 13:36:19 -04:00
Adam Weeden bf4be860cf
Upgrade to Electron 13 (#1230)
* Catch promise errors better

* Move subFunctions to bottom of createNewWindow

* Use parents when creating child BrowserWindow instances

* Some about:blank pages have an anchor (for some reason)

* Inject browserWindowOptions better

* Interim refactor to MainWindow object

* Split up the window functions/helpers/events some

* Further separate out window functions + tests

* Add a mock for unit testing functions that access electron

* Add unit tests for onWillPreventUnload

* Improve windowEvents tests

* Add the first test for windowHelpers

* Move WebRequest event handling to node

* insertCSS completely under test

* clearAppData completely under test

* Fix contextMenu require bug

* More tests + fixes

* Fix + add to createNewTab tests

* Convert createMainWindow back to func + work out gremlins

* Move setupWindow away from main since its shared

* Make sure contextMenu is handling promises

* v13.1.2

* v13.1.4

* Update Webkit version for Safari

* 13.1.6 -> NO CRASH!

* Fix types/debug build error on Ubuntu

* 13 -> 13.1.7

* Bump default Firefox version

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-07-19 13:03:46 -04:00
Ronan Jouchet 5d9cb91739 Fix 'npm install'-time warning about outdated companion DT types
```
npm WARN deprecated @types/loglevel@1.6.3: This is a stub types definition. loglevel provides its own type definitions, so you do not need this installed.
npm WARN deprecated @types/electron-packager@15.0.1: This is a stub types definition. electron-packager provides its own type definitions, so you do not need this installed.
```

Also, add `@types/debug` necessary to fix build, as already done by
@TheCleric in https://github.com/nativefier/nativefier/pull/1230/files

Also, for all deps, bump minimum version to current version
2021-07-16 18:55:10 -04:00
Ronan Jouchet c4327e97a7 Update changelog for `v44.0.7` 2021-07-10 16:06:47 -04:00
Ronan Jouchet 97b33b369f Bump default Electron from 12.0.12 to 12.0.14
- https://github.com/electron/electron/releases/tag/v12.0.13
- https://github.com/electron/electron/releases/tag/v12.0.14
2021-07-10 15:59:38 -04:00
Omoeba 95e2ff9a3f
App context menu: Add electron-context-menu image entries - Save, Copy, Copy Address (PR #1256) 2021-07-10 10:55:00 -04:00
Sam Potts 5e526fb000
Fix badge/counter icon not being removed correctly (fixes #1251) (PR #1252)
The current check for `count` means the value to reset/remove the badge (`''`) is treated as `false` and therefore the badge does not get removed. This PR changes the check for `undefined` instead which resolves the issue. 

Related issue: 
https://github.com/nativefier/nativefier/issues/1251

Testing performed:
- I ran `npm t` as per the docs. All tests passed. 
- I created a new app build for Twitter (`https://twitter.com`) and Messenger (`https://messenger.com`) to verify the correct behaviour. Both were showed the issue as resolved. 

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-07-05 18:35:50 -04:00
Ronan Jouchet 629369d885 API.md: help users not be confused by platform vs. architecture (fix #1254) 2021-07-05 17:22:44 -04:00
Ronan Jouchet a9bab01858
HACKING.md: document a few maintainer deps things (#1248) 2021-06-29 00:57:58 -04:00
Ronan Jouchet 9943d7f3cc HACKING.md: formatting & nits 2021-06-28 23:04:49 -04:00
Ronan Jouchet d0849ce794 generate-changelog: integrate running "npm out" for a reminder of outdated dependencies 2021-06-28 22:56:53 -04:00
Ronan Jouchet af80acf7e9 Update changelog for `v44.0.6` 2021-06-26 10:55:11 -04:00
Adam Weeden b74c0bf959
Make app strict TypeScript + linting (and add a shared project) (#1231)
* Convert app to strict typing + shared library

* Fix new code post-merge

* Remove extraneous lint ignores

* Apply suggestions from code review

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* Fix prettier complaint

* Dedupe eslint files

* Fix some refs after merge

* Fix clean:full command

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-06-26 09:59:28 -04:00
Ronan Jouchet f8f48d2f09 Update changelog for `v44.0.5` 2021-06-25 16:17:59 -04:00
Ronan Jouchet 9893d19085 Bump default Electron to 12.0.12 2021-06-25 16:10:12 -04:00
Adam Weeden a6c1dcf952
Fix "Reset Zoom" menu item (fix #1241) (PR #1243) 2021-06-25 16:05:23 -04:00
Adam Weeden dc0e6cb68f
Fix app keeping running in the background after closed (--tray false regression) (PR #1242)
By tightening up typing and logic around tray options.
2021-06-25 16:03:23 -04:00
Adam Weeden d69d4b253a
Fix tray icon always on (regression from #1235) 2021-06-22 19:09:11 -04:00
Adam Weeden a491e34966
Fix `--tray start-in-tray` (fix #1225) (#1235) 2021-06-22 01:22:46 -04:00
Adam Weeden 8b34c6d12d
Resolves #1228; Don't treat 'about:blank#blocked' as new tabs (#1229) 2021-06-16 23:03:49 -04:00
Ronan Jouchet 37769e463c HACKING.md: mention app pkgjson/devDeps/electron 2021-06-16 19:16:55 -04:00
Ronan Jouchet b2af6f5435 HACKING.md: clarification 2021-06-16 19:15:05 -04:00
Ronan Jouchet 564c616d12 HACKING.md: formatting 2021-06-16 19:13:11 -04:00
Ronan Jouchet 05c3cbfb76 HACKING.md: document major-updating Electron 2021-06-16 19:12:31 -04:00
Ronan Jouchet 996183b949 Update changelog for `v44.0.4` 2021-06-15 22:36:01 -04:00
Adam Weeden 7a08a2d676
Enable TypeScript strict:true, and more typescript-eslint rules (#1223)
* Catch promise errors better

* Move subFunctions to bottom of createNewWindow

* Use parents when creating child BrowserWindow instances

* Some about:blank pages have an anchor (for some reason)

* Inject browserWindowOptions better

* Interim refactor to MainWindow object

* Split up the window functions/helpers/events some

* Further separate out window functions + tests

* Add a mock for unit testing functions that access electron

* Add unit tests for onWillPreventUnload

* Improve windowEvents tests

* Add the first test for windowHelpers

* Move WebRequest event handling to node

* insertCSS completely under test

* clearAppData completely under test

* Fix contextMenu require bug

* More tests + fixes

* Fix + add to createNewTab tests

* Convert createMainWindow back to func + work out gremlins

* Move setupWindow away from main since its shared

* Make sure contextMenu is handling promises

* Fix issues with fullscreen not working + menu refactor

* Run jest against app/dist so that we can hit app unit tests as well

* Requested PR changes

* Add strict typing; tests currently failing

* Fix failing unit tests

* Add some more eslint warnings and fixes

* More eslint fixes

* Strict typing on (still issues with the lib dir)

* Fix the package.json import/require

* Fix some funky test errors

* Warn -> Error for eslint rules

* @ts-ignore -> @ts-expect-error

* Add back the ext code I removed
2021-06-15 22:20:49 -04:00
Ronan Jouchet 7807bbb327 Update changelog for `v44.0.3` 2021-06-15 09:06:44 -04:00
Adam Weeden 113d8448c1
Fix CSS injection (#1227)
This fixes https://github.com/nativefier/nativefier/pull/1222#issuecomment-860913698 , where:

1. When it works (e.g. initial page load), CSS is slower to inject (you can see pages without injected CSS)
2. CSS isn't injected when navigating to a page

On both Windows & Linux, a `git revert 9a6c6f870d && npm run build` fixes the issue.

--

I'm still not 100% sure what went wrong, but I suspect that the new version of Electron may not be firing onHeadersReceived for the actual navigation events, and only its child requests. To counteract it, I'm now injecting at the navigation event as well. I was able to reproduce the issue and this does seem to fix it. Please let me know if it does for you as well..

Also I noticed some funkiness in your logs where we're trying to inject on font files. So I realized the method is probably not nearly as important as the content-type, so I've switched blacklist methods to blacklist content-types.
2021-06-15 09:02:57 -04:00
Ronan Jouchet 9d0a2f5b44 Bump default Electron to 12.0.11 2021-06-14 14:39:51 -04:00
Ronan Jouchet ea24a0fdeb Fix gitcloud 0.2.3 import 2021-06-14 14:39:43 -04:00
Adam Weeden 9a6c6f870d
Selective css injection (#1222)
* Don't inject CSS for unneeded responses

* Remove some non-injectable methods

* Actually check for CSS to inject + unit tests

* Update app/src/helpers/windowHelpers.ts

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-06-09 09:49:35 -04:00
Peter Lewis d67f533fa5
README: Update node & npm requirements (#1224)
Update required `node` and `npm` versions to match what's in package.json
2021-06-09 08:14:46 -04:00
Ronan Jouchet 437ae55d2e Update changelog for `v44.0.2` 2021-06-07 17:28:34 -04:00
Ronan Jouchet 4badf3dc70 manual-test: fix shellcheck nits, make tests structure more distinct 2021-06-07 17:27:23 -04:00
Adam Weeden 826625f4a4
Fix HTTP basic auth (fix #1219) (#1220)
Should resolve #1219, as well adds a manual test for basic auth as suggested.

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-06-07 17:09:24 -04:00
Adam Weeden 363fa966ee
Fix tabs opening twice (fix #1209) (#1221)
* Only add a new window handler to the main window

* Update app/src/components/mainWindow.ts

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-06-07 16:49:06 -04:00
Ronan Jouchet 460d70f915 Update changelog for `v44.0.1` 2021-06-07 09:38:53 -04:00
Ronan Jouchet 709557c7ab
macOS: fix crash on activating main window (fix #1212) (PR #1213)
```
Uncaught Exception:
TypeError: Cannot read property 'show' of undefined
at App.<anonymous> (/src/main.ts:177:18)
```

Seems like an oversight of the recent refactor.

* macOS: fix crash on activating main window (fix #1210)
* Eliminate need for global mainWindow, refactor widevine / non-widevine events (#1218)

Co-authored-by: Adam Weeden <adamweeden@gmail.com>
2021-06-07 09:35:27 -04:00
Adam Weeden cdc6fa79c2
Fix fullscreen not working + menu refactor (fix #1206) (#1210)
* Catch promise errors better

* Move subFunctions to bottom of createNewWindow

* Use parents when creating child BrowserWindow instances

* Some about:blank pages have an anchor (for some reason)

* Inject browserWindowOptions better

* Interim refactor to MainWindow object

* Split up the window functions/helpers/events some

* Further separate out window functions + tests

* Add a mock for unit testing functions that access electron

* Add unit tests for onWillPreventUnload

* Improve windowEvents tests

* Add the first test for windowHelpers

* Move WebRequest event handling to node

* insertCSS completely under test

* clearAppData completely under test

* Fix contextMenu require bug

* More tests + fixes

* Fix + add to createNewTab tests

* Convert createMainWindow back to func + work out gremlins

* Move setupWindow away from main since its shared

* Make sure contextMenu is handling promises

* Fix issues with fullscreen not working + menu refactor

* Run jest against app/dist so that we can hit app unit tests as well

* Requested PR changes
2021-06-07 08:55:17 -04:00
Ronan Jouchet 16cacb0915 CATALOG.md: Spotify: mention faking user agent may not be necessary 2021-06-04 18:53:38 -04:00
Ronan Jouchet 69bc3306f9 Update changelog for `v44.0.0` 2021-06-04 17:03:35 -04:00
Ronan Jouchet 5d67156deb API.md: cleanup 2021-06-04 16:53:35 -04:00
Ronan Jouchet c6b50e8d0a API.md: add "New in x.y.z" info for (most) flags 2021-06-04 16:45:05 -04:00
Ronan Jouchet 5673989117 Bump default Electron to 12.0.10 2021-06-04 16:18:34 -04:00
Adam Weeden 72de7b3fca
Refactor app window creation/events + add some unit tests; fix #1197 (#1203)
* Catch promise errors better

* Move subFunctions to bottom of createNewWindow

* Use parents when creating child BrowserWindow instances

* Some about:blank pages have an anchor (for some reason)

* Inject browserWindowOptions better

* Interim refactor to MainWindow object

* Split up the window functions/helpers/events some

* Further separate out window functions + tests

* Add a mock for unit testing functions that access electron

* Add unit tests for onWillPreventUnload

* Improve windowEvents tests

* Add the first test for windowHelpers

* Move WebRequest event handling to node

* insertCSS completely under test

* clearAppData completely under test

* Fix contextMenu require bug

* More tests + fixes

* Fix + add to createNewTab tests

* Convert createMainWindow back to func + work out gremlins

* Move setupWindow away from main since its shared

* Make sure contextMenu is handling promises
2021-06-02 15:18:32 -04:00
Ronan Jouchet ec12702359 HACKING.md: video doesn't embed, just link to it 2021-06-02 09:52:28 -04:00
Ronan Jouchet 1ceffb1a0d HACKING.md: embed video showing what a live-reload experience looks like
Also, a paragraph on limiting breaking changes
2021-06-02 09:48:57 -04:00
Ronan Jouchet 62ee24662c More test:watch fix: bring upgrade to Jest 27 from TheCleric' closed jest-ts PR
See https://github.com/nativefier/nativefier/pull/1204
2021-06-02 00:35:19 -04:00
Ronan Jouchet 6b09d1467f More test:watch fixes & usability
See https://github.com/nativefier/nativefier/pull/1204#issuecomment-852679403
2021-06-01 23:37:04 -04:00
Ronan Jouchet 9c5dba7f07 Fix test:watch requiring two saves to actually run the code/test you just changed
Will fix https://github.com/nativefier/nativefier/pull/1204#issuecomment-852155755
See package.json in comments for description
2021-06-01 20:43:46 -04:00
Adam Weeden 86a27d4f39
Allow non-ascii app names like 微信读书 (fix #1056) (#1207)
* Resolves #1056 Allow non-ascii app names like 微信读书

* Update src/utils/sanitizeFilename.ts

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* Fix prettier

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-05-31 23:27:32 -04:00
Adam Weeden 6c55e1a9a1
Add login.microsoftonline.com to internal login pages (#1205)
* Add login.microsoftonline.com to internal login pages

* Update API.md

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* Add extra messaging for adding to internalLoginPages

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-05-29 20:38:24 -04:00
Ronan Jouchet 8801ca5150 app/tsconfig.json: document when to bump compilerOptions, and rationale 2021-05-29 20:07:38 -04:00
Ronan Jouchet 2b780e6c67 Test watcher: warn that it's necessary to run build:watch for the test watcher to work
See https://github.com/nativefier/nativefier/pull/1204#issuecomment-850915981
2021-05-29 19:58:46 -04:00
Adam Weeden d6730f7022
Improve user agent handling/provide user agent "short" codes (#1198) 2021-05-21 23:41:13 -04:00
Ronan Jouchet 4f3b449218 Deps: bump ts-loader from 8 to 9, now that we require Node 12
See https://github.com/TypeStrong/ts-loader/releases
2021-05-21 19:44:53 -04:00
Ronan Jouchet 45db4e7ec6 README: whoopsie, bad rename of animation 2021-05-21 18:17:59 -04:00
Ronan Jouchet cc02b87de7
Get rid of "docs" folder (#1194)
It contains a weird mix of stuff and hides valuable files from view at the root of the repo. Better to have:
- Docs at the root
- Rest of the github/release-related hodgepodge (screenshots, scripts) in hidden folder .github
2021-05-21 18:16:59 -04:00
Adam Weeden 1a810e5ce5
Organize CLI flags into groups (for better --help usability) (#1191)
* Organize CLI options for better UX

* Fix some documentation

* Whoops. Stupid VS Code linter.

* Fix prettier issues

* Make paths less unixy in tests

* Update src/cli.test.ts

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* Apply suggestions from code review

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* Add example to reference CATALOG.md

* Make honest appear near user-agent

* Standardize descriptions

* Hide flash options

* Add explanation of parsed._

* Redo groups in yargs

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-05-18 22:02:55 -04:00
Adam Weeden b3c202fd33
Bump minimum required version: node>=12.9, npm>=6.9 (#1192)
* Move minimum supported version: node=12, npm=6.9

* Add missing bits and documentation for future bumping

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-05-15 13:32:54 -04:00
Ronan Jouchet 9225114e77 Update changelog for `v43.1.3` 2021-05-15 13:28:16 -04:00
Ronan Jouchet 3fd34549d6 Bump to Electron 12.0.7 2021-05-15 13:12:34 -04:00
Ronan Jouchet 0216d4f9c4 CATALOG.md: add HBO Max
From https://github.com/nativefier/nativefier/issues/1153#issuecomment-840697278
2021-05-13 22:51:08 -04:00
Knallli 668ca723dc
CATALOG.md: Add Spotify (PR #1187)
Co-authored-by: Knallli <41125802+Knallli@users.noreply.github.com>
Co-authored-by: Adam Weeden <adamweeden@gmail.com>
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-05-12 19:53:47 -04:00
Ronan Jouchet b9615b4cbd
CATALOG.md: cleanup & add Google apps section (#1188) 2021-05-12 18:39:52 -04:00
Ronan Jouchet d61a4e709a Make prettier happier 2021-05-10 10:23:56 -04:00
Ronan Jouchet 03325b45ce README: remove broken npm badge, and btw useless build status 2021-05-10 10:11:34 -04:00
Adam Weeden 5fbf14d35d
Only try to inject CSS for valid web requests (fix #939: crash on tab close, PR #1181) 2021-05-08 10:12:10 -04:00
RippiN 067327e863
linkIsInternal: consider shop.foo.com and blog.foo.com as internal (still without extra dep or SLD list) (PR #1171)
Domain-ish URL test now also covers considering internal URLs with different sub-domains like
- `https://listen.tidal.com`
- `https://login.tidal.com`

Co-authored-by: Daniel Fuchs <dfuchs@multamedio.de>
Co-authored-by: Adam Weeden <adamweeden@gmail.com>
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-05-03 13:22:44 -04:00
deciacco d6f0a28a90
CATALOG.md: build command library (fix #1166) (PR #1178) 2021-05-03 13:08:32 -04:00
Ronan Jouchet 6cf533c5ac Update changelog for `v43.1.2` 2021-05-03 11:19:30 -04:00
Ronan Jouchet 331fe8db39
Fix logging out users on upgrade / app recreate with same URL (fix #1176) (PR #1179)
This is a follow-up of https://github.com/nativefier/nativefier/pull/1162#discussion_r623766247

PR #1162 introduced a new `generateRandomSuffix` helper function,
used it for its needs (avoiding collisions of injected js/css).

But it also replaced existing appname normalizing logic with it,
introducing randomness in a place that used to be deterministic.

As a result, starting with dd6e15fb5 / v43.1.0, re-creating an app would cause
the app to use a different appName, thus a different appData folder, thus
losing user data including cookies.

This PR leaves the `--inject` fixes of #1176, but reverts the appName logic
to the pre-#1176 code.
2021-05-03 11:16:50 -04:00
Ronan Jouchet beae6e872b development.md: expand on why I'm such a bore with limiting extra deps 2021-05-02 18:29:05 -04:00
Ronan Jouchet d9670d6bb0 Update changelog for `v43.1.1` 2021-05-02 18:15:08 -04:00
Adam Weeden 14d95da079
Fix crash in preload.js due to 3rd-party 'loglevel' (fix #1175, fix #1176) (PR #1177)
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-05-02 18:12:08 -04:00
Ronan Jouchet e58823985a Update changelog for `v43.1.0` 2021-05-01 11:09:05 -04:00
Ronan Jouchet 36a9e2d616 Bump to Electron 12.0.6 with security fixes
See https://github.com/electron/electron/releases/tag/v12.0.6
2021-05-01 10:48:29 -04:00
Adam Weeden 2774888924
App: fix child windows not inheriting mainWindow properties, including userAgent (#1174)
When a new child window is spawned (such as for a Google login popup), those child windows were not receiving the mainWindow's properties. Chiefly among this was the userAgent which caused a bug in #831
2021-05-01 10:46:40 -04:00
Adam Weeden bcdbd58f06
App: replace console.xyz calls with loglevel.xyz, with a level controlled by app argv --verbose (#1172)
In reference to request in https://github.com/nativefier/nativefier/pull/1168/files#r623753290 ,
this PR fixes a lot of the disparity in logging in the app, and fleshes the logging out a bit.
2021-04-30 23:21:37 -04:00
Adam Weeden 895c11a53e
Support setting the browserWindow's language (fix #175) (PR #1173 ) 2021-04-30 23:16:57 -04:00
Adam Weeden 7dc189ef3f
Support creating self-contained "portable" apps writing their app data to the app folder (fix #376) (PR #1168)
In response to #376

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-04-30 23:10:12 -04:00
Adam Weeden dd6e15fb5c
Fix injecting multiple css/js files (fix #458) (#1162)
* Add ability to inject multiple css/js files

* API doc: Move misplaced macOS shortcuts doc (PR #1158)

When I added this documentation originally, I guess I placed it in the wrong location.

* README: use quotes in example, to divert users from shell globbing pitfalls

Follow-up of https://github.com/nativefier/nativefier/issues/1159#issuecomment-827184112

* Support opening URLs passed as arg to Nativefied application (fix #405) (PR #1154)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* macOS: Fix crash when using --tray (fix #527), and invisible icon (fix #942, fix #668) (#1156)

This fixes:

1. A startup crash on macOS when using the `--tray` option; see #527.
  ![image](https://user-images.githubusercontent.com/22625791/115987741-99544600-a5b6-11eb-866a-dadb5640eecb.png)
2. Invisible tray icon on macOS; see #942 and #668.  
   ![image](https://user-images.githubusercontent.com/22625791/115988276-24364000-a5b9-11eb-80c3-561a8a646754.png)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* API.md / --widevine: document signing apps to make some sites like HBO Max & Udemy work (fix #1147)

* Prompt to confirm when page is attempting to prevent unload (#1163)

Should alleviate part of the issue in #1151

* Add an option to upgrade an existing app (fix #1131) (PR #1138)

This adds a `--upgrade` option to upgrade-in-place an old app, re-using its options it can.
Should help fix #1131

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>

* Bump to Electron 12.0.5 with Chrome 89.0.4389.128

* Add newly discovered Google internal login page (#1167)

* Fix Widevine by properly listening to widevine-... events, and update docs (fix #1153) (PR #1164)

As documented in #1153, for Widevine support to work properly, we need to listen for the Widevine ready event, and as well for certain sites the app must be signed.

This PR adds the events, and as well adds better documentation on the signing limitation.

This may also help resolve #1147

* Improve suffix creation + tests

* API: clarif in existing doc by the way

* Typo

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
Co-authored-by: Ben Curtis <github@nosolutions.com>
Co-authored-by: Fabian Wolf <22625791+fabiwlf@users.noreply.github.com>
2021-04-30 11:04:10 -04:00
Ronan Jouchet 1ad1844619 linkIsInternal: add test to ensure we don't regress on cases of SLDs 2021-04-30 07:52:58 -04:00
Ronan Jouchet c4356e48f1
Fix app not starting since widevine PR / #1164 (#1170) 2021-04-30 01:23:13 -04:00
Adam Weeden ec0ea4bd67
README: add troubleshooting section for common issues (#1169)
Per #1112, I'm adding some common troubleshooting steps to the README. As well, this adds a checkbox requesting users to check this section before submitting a ticket, which will hopefully lead to more self-help.
2021-04-29 20:02:43 -04:00
Adam Weeden f6e1ebd876
Fix Widevine by properly listening to widevine-... events, and update docs (fix #1153) (PR #1164)
As documented in #1153, for Widevine support to work properly, we need to listen for the Widevine ready event, and as well for certain sites the app must be signed.

This PR adds the events, and as well adds better documentation on the signing limitation.

This may also help resolve #1147
2021-04-29 13:27:55 -04:00
Adam Weeden d2c7de37a2
Add newly discovered Google internal login page (#1167) 2021-04-29 13:24:15 -04:00
Ronan Jouchet a19ccd5fda Bump to Electron 12.0.5 with Chrome 89.0.4389.128 2021-04-28 22:05:17 -04:00
Adam Weeden 9330c8434f
Add an option to upgrade an existing app (fix #1131) (PR #1138)
This adds a `--upgrade` option to upgrade-in-place an old app, re-using its options it can.
Should help fix #1131

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-04-28 22:00:31 -04:00
Adam Weeden bc6be8445d
Prompt to confirm when page is attempting to prevent unload (#1163)
Should alleviate part of the issue in #1151
2021-04-28 21:37:50 -04:00
Ronan Jouchet 83b284e727 API.md / --widevine: document signing apps to make some sites like HBO Max & Udemy work (fix #1147) 2021-04-28 21:09:25 -04:00
Fabian Wolf cf8e51e7ab
macOS: Fix crash when using --tray (fix #527), and invisible icon (fix #942, fix #668) (#1156)
This fixes:

1. A startup crash on macOS when using the `--tray` option; see #527.
  ![image](https://user-images.githubusercontent.com/22625791/115987741-99544600-a5b6-11eb-866a-dadb5640eecb.png)
2. Invisible tray icon on macOS; see #942 and #668.  
   ![image](https://user-images.githubusercontent.com/22625791/115988276-24364000-a5b9-11eb-80c3-561a8a646754.png)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-04-28 20:25:15 -04:00
Ben Curtis 41b208fcb7
Support opening URLs passed as arg to Nativefied application (fix #405) (PR #1154)
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-04-28 20:18:12 -04:00
Ronan Jouchet 9286fc8086
README: use quotes in example, to divert users from shell globbing pitfalls
Follow-up of https://github.com/nativefier/nativefier/issues/1159#issuecomment-827184112
2021-04-27 18:12:02 -04:00
Adam Weeden 12c8c9f4cb
API doc: Move misplaced macOS shortcuts doc (PR #1158)
When I added this documentation originally, I guess I placed it in the wrong location.
2021-04-26 12:25:47 -04:00
Ronan Jouchet 9f561c0091 CI: run less node versions, oldest supported / latest is enough 2021-04-23 21:52:03 -04:00
Ronan Jouchet 09accbd28a CI: run in node 16, stop node 15 2021-04-23 21:47:54 -04:00
Chris Dzombak b4ddd6865c
Support defining a custom bookmarks menu (fix #1065) (PR #1155)
This PR adds an optional, customizable menu of predefined bookmarks. In addition to containing a list of bookmarks, this file customizes the name of the menu and (optionally) allows assigning keyboard shortcuts to bookmarks. It adds a new command-line flag, `--bookmarks-menu <string>`, which can be set as the path to a JSON file containing configuration for the bookmarks menu.

Example of such a JSON file:

```json
{
    "menuLabel": "Music",
    "bookmarks": [
        {
            "title": "lofi.cafe",
            "url": "https://lofi.cafe/",
            "type": "link",
            "shortcut": "Cmd+1"
        },
        {
            "title": "beats to relax/study to",
            "url": "https://www.youtube.com/watch?v=5qap5aO4i9A",
            "type": "link",
            "shortcut": "Cmd+2"
        },
        {
            "type": "separator"
        },
        {
            "title": "RÜFÜS DU SOL Live from Joshua Tree",
            "type": "link",
            "url": "https://www.youtube.com/watch?v=Zy4KtD98S2c"
        }
    ]
}
```

## Checks
- [x] `npm run ci` passes

## Notes

Compared to the fork linked in #1065, this PR:
- adds no dependencies
- doesn't currently support submenus (this should be easy enough to add, but I didn't need it)

## Screenshot

<img width="853" alt="screenshot" src="https://user-images.githubusercontent.com/102904/115882015-5493a800-a41a-11eb-85ef-a190f3dbfe76.png">
2021-04-23 21:46:34 -04:00
Ronan Jouchet fa9bd2aba5 Update changelog for `v43.0.2` 2021-04-13 21:53:05 -04:00
Ronan Jouchet 83b1c10269 Bump to Electron 12.0.4 with Chrome 89.0.4389.114
With recent security fixes.
See https://github.com/electron/electron/releases/tag/v12.0.4
and https://github.com/electron/electron/releases/tag/v12.0.3
2021-04-13 21:49:31 -04:00
Ronan Jouchet f64c05f735 Update changelog for `v43.0.1` 2021-04-11 20:58:20 -04:00
Ronan Jouchet ecfb5e92bb Old build detection: doc 2021-04-11 20:54:44 -04:00
Ronan Jouchet 1474ebc8bc Bump to Electron 12.0.2 with Chrome 89.0.4389.90
See https://github.com/electron/electron/releases/tag/v12.0.2
2021-04-11 20:47:16 -04:00
Ronan Jouchet da06d933df Old build detection: bump to 90 days, let packagers customize message 2021-04-11 20:40:41 -04:00
Alec Mev d0ab749bd6
Add Apple ID to automatically-internal login pages (#1146)
Used by Notion, for example. TLDs other than `.com` don't appear to exist.
2021-04-01 18:03:07 -04:00
Evan Anderson 4298d2da06
Automatically-internal login URLs: add GitHub 2FA pages (PR #1140)
I have 2FA (FIDO/Yubikey) set up for GitHub, and the session login was redirecting to my browser. Looking at the redirect path, it appears that github.com/session is involved, so adding that to internal login details.

With this patched, I'm able to login in to https://octobox.io/ in nativefier.
2021-03-17 07:39:24 -04:00
Adam Weeden 50ce2f81dd
Add a `session-interaction` event to allow injected js to interact with apps Electron `session` object (PR #1132)
As discussed in #283 this PR will allow injected JS to do SOME interaction with the Electron session.

There is a full explanation of what this feature can, and cannot do, with examples in the api.md documentation.

This will provide a path for resolving many of our issues where users may "self-service" the solution by injecting JS that performs the task needed to meet their objectives.

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-03-13 21:24:48 -05:00
Ronan Jouchet da007305de dev.md: typo 2021-03-11 20:28:28 -05:00
Ronan Jouchet 3c2e84a752
Dev.md: guidelines (PR #1133)
An attempt to a few important things to newcomers, to reduce the risk
of contributors being disappointed / contradicted late at PR time.
2021-03-11 20:27:21 -05:00
Ronan Jouchet eaa9d8471b Old build detection: simpler error message 2021-03-11 09:37:33 -05:00
Ronan Jouchet 554c70c12d API.md: document Flash deprecation 2021-03-10 23:02:51 -05:00
Ronan Jouchet 96f3ab4ec7 Update changelog for `v43.0.0` 2021-03-10 20:37:37 -05:00
Ronan Jouchet dcadfb0bca Bump to Electron 12.0.1 with Chrome 89.0.4389.82 2021-03-10 20:25:33 -05:00
Ronan Jouchet 1521a28f45 Docker: fix build 2021-03-10 20:06:39 -05:00
Ronan Jouchet 12373b620e Doc/API: update outdated --internal-urls "second-level-domain" stuff
See https://github.com/nativefier/nativefier/pull/1126
and c0a6604676
2021-03-10 19:49:22 -05:00
Ronan Jouchet ffa421eb8e Docker: attempt to fix build
See https://hub.docker.com/api/audit/v1/action/05620acd-9984-4e10-9053-3a7edc0c8558/ :

```
The command '/bin/sh -c npm link && npm test && rm -rf /tmp/nativefier* ~/.npm/_cacache ~/.cache/electron && chmod +x $NPM_PACKAGES/bin/nativefier' returned a non-zero code: 243
```

Culprit could be `rm -rf /tmp/nativefier*` : at this point /tmp/nativefier
doesn't exist, so `sh` cannot evaluate glob `/tmp/nativefier*`, and exits 1
2021-03-10 19:44:44 -05:00
Ronan Jouchet 21665cac5f Bump deps 2021-03-10 19:41:02 -05:00
Ronan Jouchet 74a7d3375d App: revert addition of extra flag --internal-login-pages
See discussion at https://github.com/nativefier/nativefier/pull/1124#issuecomment-794751514 :

> @TheCleric I was about to merge this, then reconsidered one little thing (yes I wrote "little", I'm not reconsidering this whole thing 😅).
>
> I'm re-considering having the extra flag. I'm not so sure this will harm a lot of use cases. I'd like to 1. merge this PR, 2. immediately follow up with a small commit removing the flag & adjusting api.md, 3. release with the change well-documented / asking for feedback if this is problematic to anyone. (I'm not asking you any extra work, and like leaving an in-tree commit trace of considering the flag). If people complain with a valid reason, we'll restore the flag with a quick revert, else we're happy with one less flag and a reasonably-handled breaking change.
>
> Thoughts / objections?

Answered by:

> That seems reasonable to me.
>
> [discussion on an extra structured way to pass flags]
2021-03-10 19:36:20 -05:00
Adam Weeden 6f7e80bafd
App: Automatically consider known login pages as internal (fix #706) (PR #1124)
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-03-10 19:20:53 -05:00
Adam Weeden e9ccb35825
Docker: slim down image size, by removing temp/cache files (PR #1128)
Per the [note](https://github.com/nativefier/nativefier/pull/1122#discussion_r588922780) by @SuperSandro2000 in #1122 Docker will still cache files in intermediate layers if you delete them, so they'll still be part of the image.

Only solution seems to be to delete them as you create them so they don't cache:
Per https://stackoverflow.com/questions/53998310/docker-remove-file-from-intermediate-layer

Co-authored-by: Sandro <sandro.jaeckel@gmail.com>
2021-03-10 18:54:33 -05:00
Ronan Jouchet 9b455670c4 generate-changelog: integrate doc, creating tag, remind about smoke test 2021-03-04 21:27:13 -05:00
Ronan Jouchet 7ee2f97599 Update changelog for `v42.4.0` 2021-03-04 12:46:10 -05:00
Ronan Jouchet e59e056f59 Bump deps 2021-03-04 12:37:11 -05:00
Ronan Jouchet c0a6604676
Fix considering "same domain-ish" URLs as internal (PR #1126)
In 6b266b7815, as I got rid of deprecated dep `wurl`, I wrote:

> This one may be problematic, as it used to do TLD stuff:
> https://github.com/websanova/node-url/blob/7982a613bc/wurl.js#L4
>
> So, the new WHATWG-URL-based implementation will consider
> `asana.com` to be "external" to `app.asana.com`, contrarily to before.
> Given the nature of Nativefier, I think it's actually what to expect,
> that in this case you're "out of the app", and in e.g. asana's landing
> page, which you'd expect to see in your browser.

Turns out it's even more problematic: @TheCleric notices in https://github.com/nativefier/nativefier/pull/1124#issuecomment-790279403
that this breaks app `https://evernote.com` doing its login in `www.evernote.com`

The present change fixes this, by behaving mostly similarly to before,
but without re-introducing `wurl` or another dep needing a TLD/SLD list.
2021-03-04 10:00:53 -05:00
Adam Weeden 8f9135312b
Docker: fix Windows builds (fix #997), line endings, switch to Alpine (PR #1122)
- Docker builds for Windows are fixed (fixes #997)
- Switched over to use Alpine (as was indicated as desired in https://github.com/nativefier/nativefier/issues/375#issuecomment-304247033) - which may mean #375 is fixed as well.
- Fixed bug where Docker has the wrong line endings when copying from a Windows host
- Fixed the invalid `arm` arch to `armv7l`
- Add `npm t` to the docker build to ensure tests pass before we start trying to do builds
- Add a message to help the user when trying to build Mac apps on Windows as a non-Admin (currently an unhelpful exception)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-03-02 00:16:30 -05:00
C. Mangla cbb4380583
Icon conversion: support GraphicsMagick in addition to ImageMagick (PR #1002)
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-02-28 14:21:28 -05:00
Adam Weeden adcf21a3df
macOS: Prompt for accessibility permissions if needed by Global Shortcuts using Media Keys (Fix #1120) (PR #1121)
When setting a media key (play, pause, next/previous track) as global shortcut in Mac OS 10.14+, accessibility permissions must be given to the app for it to work (see https://www.electronjs.org/docs/api/global-shortcut?q=MediaPlayPause#globalshortcutregisteraccelerator-callback).

This PR will accomplish the following on generated app launch:
- Check if global shortcuts are being setup
- Check if the host OS is Mac OS
- Check if the global shortcuts were one of the media keys
- If the above are true, check if the app has accessibility permissions
- If the app does not have the accessibility permissions it will ask the user if they would like to be prompted for these permissions, and then ask Mac OS to prompt for accessibility permissions.

~~As well, a new command line flag is added (`--no-accessibility-prompt`) to preventatively suppress these prompts if desired.~~

Screenshots of the new behavior:
![Screen Shot 2021-02-26 at 2 41 21 PM](https://user-images.githubusercontent.com/547567/109356260-76bfde00-784e-11eb-8c36-3a51b911b780.png)
![Screen Shot 2021-02-26 at 2 41 28 PM](https://user-images.githubusercontent.com/547567/109356266-79223800-784e-11eb-94eb-66437c05fd10.png)
![Screen Shot 2021-02-26 at 2 41 50 PM](https://user-images.githubusercontent.com/547567/109356270-7aebfb80-784e-11eb-9e90-e09bb49752c6.png)

Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-02-28 10:24:14 -05:00
Ronan Jouchet 75aa10382b Move some tooling config (eslintignore, prettierrc) to package.json
To limit amount of dotfiles at repo root
2021-02-27 22:31:59 -05:00
Ronan Jouchet 5129dbafe1 Deps: bump gitcloud to 0.2 and page-icon to 0.4 2021-02-27 01:15:26 -05:00
Ronan Jouchet 519e65e7da TSConfig: bump to target/lib es2018, since we require node10 2021-02-26 22:50:59 -05:00
Ronan Jouchet d90d9f3d7f Scripts: get rid of `dev-up` and `dev-up-win` scripts
They used to be necessary when we did OS-specific stuff in here, but
we're no longer, and `foo && bar` is supported by both *nixes and Windows
2021-02-26 22:41:00 -05:00
Ronan Jouchet 292ac39328 CI: avoid npm funding messages 2021-02-26 22:19:45 -05:00
Ronan Jouchet e03e07e4bd Speed up CI by avoiding repeated npm install & build already done with "prepare" hook 2021-02-26 22:15:56 -05:00
Ronan Jouchet 0aa8276922 Update changelog for `v42.3.0` 2021-02-25 19:49:22 -05:00
Ronan Jouchet 17231d707f Bump eslint-config-prettier from ^7.2.0 to ^8.1.0 2021-02-25 19:10:11 -05:00
Ronan Jouchet 1a5d79ecf8 Bump commander from ^4.1.1 to ^7.1.0
Looked at https://github.com/tj/commander.js/blob/master/CHANGELOG.md
and tested a bit, seems like no change is needed
2021-02-25 19:08:37 -05:00
Ronan Jouchet 6b266b7815 (Attempt to) get rid of deprecated app dep `wurl`
This one may be problematic, as it used to do TLD stuff:
https://github.com/websanova/node-url/blob/7982a613bc/wurl.js#L4

So, the new WHATWG-URL-based implementation will consider
`asana.com` to be "external" to `app.asana.com`, contrarily to before.
Given the nature of Nativefier, I think it's actually what to expect,
that in this case your "out of the app", and in e.g. asana landing's page,
which you'd expect to see in your browser.

Let's see if users disagree with that.
2021-02-25 18:55:28 -05:00
Ronan Jouchet fe79fd622d Doc extra electron update stuff 2021-02-25 18:46:27 -05:00
Ronan Jouchet f4a0479a11 Bump default Electron to 11.3.0 with the icon fix (with Chromium 87.0.4280.141) 2021-02-25 18:28:29 -05:00
Ronan Jouchet 5ea4638aea Fix lint 2021-02-25 18:19:30 -05:00
Ronan Jouchet 9b52f210db Get rid of dependency "shelljs"
We don't need a fancy _"portable (Windows/Linux/macOS) implementation
of Unix shell commands on top of the Node.js API"_, we just want to run
a simple script. Replacing with using stdlib `child_process.spawnSync`.
2021-02-25 18:15:24 -05:00
Ronan Jouchet 4bf0226da0 Deps: come back to semver ^a.b.c syntax
Thinking about it again, the user-friendlier `a.x` syntax has one disadvantage
over `^a.b.c`: it doesn't force deps upgrades when they upgrade Nativefier.
`a.x` is fine on initial install, but a user with an insecure dep
(e.g. axios 0.19.0) will _not_ get fixed axios 0.21.1 on upgrading Nativefier.
-> Come back to `a.x` everywhere.

Still not introducing package locks, they're too confusing to new devs.
See https://github.com/nativefier/nativefier/pull/1099#issuecomment-761250232
2021-02-25 08:15:39 -05:00
Ronan Jouchet b99b2f9632 Deps: bump axios from 0.x to ^0.21.1, to reassure dependabot 2021-02-24 23:12:21 -05:00
Arseny 380c98b23d
API.md: fix typo in option "-v" (#1114) 2021-02-16 08:02:21 -05:00
Jia Hao 7a3730e5a9 Update changelog for `v42.2.1` 2021-01-30 05:04:51 +00:00
Jia Hao 6316d23762 Move to nativefier organization 2021-01-30 04:49:52 +00:00
Jia Hao 66ff02584e Temporarily increase timeout for network call in test 2021-01-30 04:49:36 +00:00
Milo 9c784dcfaf
Move TS @types from dependencies to devDependencies (PR #1102)
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2021-01-24 20:36:06 -05:00
Ronan Jouchet dcefe0074d Update changelog for `v42.2.0` 2021-01-18 09:24:28 -05:00
Ronan Jouchet 10f7fed290 Revert default Electron back to 11.1.1 (Chrome 87.0.4280.88) (fix #1101)
Not doing anything more complicated (adding macOS-specific code
or adding code always passing an icon), let's instead wait for
Electron to fix the issue.
2021-01-18 09:18:43 -05:00
Ronan Jouchet f4a7266783 README: more tweaking 2021-01-16 10:19:29 -05:00
Ronan Jouchet ef43bb6984 README: wording, fix broken links 2021-01-16 10:18:17 -05:00
Ronan Jouchet fc35b00f88 Make maintenance status (in /releases until now) more visible by putting it at the top of our README 2021-01-16 10:15:51 -05:00
Ronan Jouchet 422f72aa3b Update changelog for `v42.1.0` 2021-01-16 08:47:17 -05:00
Ronan Jouchet dc353bebaf More filename & appname sanitization 2021-01-16 08:40:04 -05:00
Ronan Jouchet 3b28dc46cc Bump default Electron to 11.2.0 (with Chromium 87.0.4280.141) 2021-01-16 08:18:33 -05:00
Ronan Jouchet b0a953eb2d Get rid of cheerio
Not sure this one will stick, maybe my regex is too naive.
Works for common websites. Let's see
2021-01-15 22:15:48 -05:00
Ronan Jouchet 35d926b959 Fix inferIcon error surfacing in full since recent axios 2021-01-15 21:57:19 -05:00
Ronan Jouchet 17f688de63 Get rid of lodash 2021-01-15 21:50:07 -05:00
Ronan Jouchet 0eaf72ced8 Publish TS types, for them to show up in npm
See https://github.blog/changelog/2020-12-16-npm-displays-packages-with-bundled-typescript-declarations/
2020-12-17 20:59:04 -05:00
Ronan Jouchet cf11a71a7c Update changelog for `v42.0.2` 2020-12-07 16:51:49 -05:00
Ronan Jouchet 7fd0c748ba Fix arg validation regression in #1080 with --{x,y} (fix #1084) 2020-12-07 16:50:59 -05:00
Ronan Jouchet 412b724292 Update changelog for `v42.0.1` 2020-12-06 23:20:07 -05:00
Ronan Jouchet f4af78018f Fix arg validation regression in #1080 (fix #1083) 2020-12-06 23:19:02 -05:00
Ronan Jouchet 46bc71cb63 release.md: fix pushing tags by using annotated tags, document new CI build based on creating release 2020-12-06 14:29:42 -05:00
Ronan Jouchet 9dc0bed95c Update changelog for `v42.0.0` 2020-12-06 14:08:18 -05:00
Ronan Jouchet 7c5b2bb68f Bump default Electron to 11.0.3, bump dep eslint-config-prettier to 7.x, bump version to something far away from current Electron version
Rationale for nonsensical major version bump: around Nativefier 8.x,
versions of Nativefier and Electron aligned, by release schedule coincidence.
Since Nativefier has little breaking changes, it was great: as Electron
releases are breaking, Nativefier had no breaking changes, I bumped our
major version on new major Electron, and everything was good.

Except *now*, as I have a breaking change, that would bump Nativefier to
12.x, which would be confusing since we'd still default to Electron 11 :-/ .

-> To keep respecting semver and reduce confusion, bumping Nativefier
   version to something far ahead. No it doesn't matter, version
   number are meaningless anyway (well, outside of semver, whose
   respect is precisely the point here).
2020-12-06 13:57:51 -05:00
erythros 27ecfcbeca
Check for improperly-formatted arguments (fix #885) (PR #1080)
Verifies short arguments do not contain an extra dash ( right: `-h`, error: `--h` )
Verifies long arguments contain at least two dashes ( right: `--help`, error: `-help` )
2020-12-06 12:34:32 -05:00
erythros 9b608d4d24
Correctly start in tray when both --maximize and --tray start-in-tray are passed (fix #1015) (PR #1079)
Co-authored-by: erythros <erythros.com>
2020-12-01 19:14:06 -05:00
Matthew Ruzzi e8d3530b43
Warn on old Electron/Chrome (fix #556) (PR #1076)
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2020-11-30 21:45:35 -05:00
erythros 3e0011a29c
Fix icon path error when passing asar (--conceal) flag (fix #975) (PR #1074)
By moving icon copy before packaging by `electron-packager` occurs.
2020-11-30 14:51:19 -05:00
Jia Hao f2c816795a Actually fix the badge in the README
According to https://docs.github.com/en/free-pro-team@latest/actions/managing-workflow-runs/adding-a-workflow-status-badge, we must reference the workflow by name if it contains the `name` keyword.
2020-11-22 02:35:30 +00:00
Jia Hao 125a3894d2 Fix badge in README 2020-11-22 02:30:53 +00:00
Jia Hao 59accaba8d Migrate from Travis CI to GitHub Actions 2020-11-22 02:28:14 +00:00
Ronan Jouchet b821ade761 Update changelog for `v11.0.2` 2020-11-21 14:52:08 -05:00
Ronan Jouchet 292416b83f Bump default Electron to 11.0.2 2020-11-21 11:28:00 -05:00
erythros bbd51ad988
Support using a Widevine-enabled Electron for DRM playback (fix #435) (PR #1073) 2020-11-21 09:39:07 -05:00
Ronan Jouchet 7531c3136e Update changelog for `v10.1.5` 2020-11-08 21:52:39 -05:00
Ronan Jouchet 4c8d1a1185 Travis: more attempting to get ts-estlint to install on npm7
Looking at https://travis-ci.org/github/jiahaog/nativefier/jobs/742333968 ,
I see that @typescript-eslint/eslint-plugin@3.10.1 is being "Found".
I didn't ask for it, and I see that Travis reports in its "cache.npm"
section that

    adding /home/travis/build/jiahaog/nativefier/node_modules to cache
    creating directory /home/travis/build/jiahaog/nativefier/node_modules

-> Maybe Travis is trying to reuse stuff and we had a leftover ts-eslint@3
   in the build machine??? Trying prefixing dev-up with a cleanup
2020-11-08 21:42:39 -05:00
Ronan Jouchet f9815cb49f Deps: more trying getting npm7 peerDeps to play nice 2020-11-08 21:31:04 -05:00
Ronan Jouchet 571717949d Deps: try to make npm7 peerDep resolution happy
see example failed build at https://travis-ci.org/github/jiahaog/nativefier/jobs/742329221
2020-11-08 21:15:51 -05:00
Ronan Jouchet 810f4a1e6a Deps: bump webpack from 4.x to 5.x (-cli from 3.x to 4.x) 2020-11-08 21:08:24 -05:00
Ronan Jouchet 1add237878 Deps: bump ts-eslint from 3.x to 4.x 2020-11-08 20:52:42 -05:00
Ronan Jouchet c78147225d CI: Add a node.js 15 build 2020-11-08 20:45:36 -05:00
Matthew Ruzzi 2c10d122f3
Bump default Electron to 10.1.5 (with Chromium 85.0.4183.121) (#1066)
Fixes [CVE-2020-15999](https://github.com/advisories/GHSA-pv36-h7jh-qm62) Heap overflow in the freetype library by upgrading to Electron [10.1.5](https://github.com/electron/electron/releases/tag/v10.1.5) 

https://github.com/electron/electron/pull/26070
2020-11-08 20:29:11 -05:00
adasauce 43073cb3f7
README: suggest docker "-rm" flag to clean up containers after build (#1064)
Otherwise it keeps around randomly-named containers forever.
2020-11-05 21:41:42 -05:00
Ronan Jouchet 6dbe4998b6 Update changelog for `v10.1.0` 2020-08-29 17:11:55 -04:00
Ronan Jouchet 7f3ebb1a69 On successful build, better explain how to run the app and what to do with it (fix #1029) 2020-08-29 16:41:33 -04:00
Ronan Jouchet cb4ab05e7b cli: document arm64 arch 2020-08-29 16:21:42 -04:00
Ronan Jouchet b1d0d6857f Bump default Electron to 10.1.0, bump to TS 4.x 2020-08-29 16:16:25 -04:00
Ronan Jouchet 06369f1213 api.md: document arm64 arch 2020-08-29 16:16:25 -04:00
Cassidy James Blaede 1c06af23fa
inferOs: Add support for arm64 (#1037)
Tested on a Pinebook Pro running elementary OS
2020-08-29 16:12:22 -04:00
Ronan Jouchet e24b22ea7f Update changelog for `v9.2.0` 2020-08-10 21:47:07 -04:00
Ronan Jouchet 85629434e6 Bump default Electron to 9.2.0 2020-08-10 21:42:57 -04:00
Ronan Jouchet b8b5cd5bc4 Document child process named "Electron" when building windows apps under non-Windows and without Wine (fix #1022) 2020-08-10 21:37:45 -04:00
Ronan Jouchet fd88d5199f Restore & pimp docker docs in README, now that Docker build has been fixed by Jia (thanks Jia) (fix #848) 2020-08-10 21:33:32 -04:00
Ronan Jouchet d16da7dd1f Type BrowserWindow 2020-08-10 21:21:24 -04:00
davidfant bd077756e8
Emit TS type declarations, and type NativefierOptions (#1016)
This PR adds better Typescript support when using the Node module directly:
1. Generate type declarations when running `tsc`
2. Created the `NativefierOptions` type for the `buildNativefierApp` function instead of using `any`
2020-08-05 20:26:41 -04:00
Joe Skeen 3e5f1fabad
[dev] Add unified {build,test} watch mode, using "concurrently" (#1011)
I noticed that the development README suggested using multiple console 
windows/tabs for a good development experience. Using the package `concurrently`,
we can streamline that and require only one window with output for both watch processes:

![image](https://user-images.githubusercontent.com/12286274/88694827-477d9e80-d0be-11ea-898c-ee9a509db4bb.png)


Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2020-08-05 12:52:49 -04:00
Joe Skeen 8e8cd24e0d
Add `--block-external-urls` flag to forbid external navigation attempts (Fix #978 - PR#1012)
Fixes #978 

Adds a `--block-external-urls` option (default: `false`) that prevents opening external links (as classified by the `--internal-urls` option).

Documentation and tests updated.


Example:
```
nativefier --internal-urls "classroom\.google\.com" --block-external-urls
```
![image](https://user-images.githubusercontent.com/12286274/88739501-f12d5180-d0f7-11ea-9821-86f3e9bfa070.png)
![image](https://user-images.githubusercontent.com/12286274/88739512-fab6b980-d0f7-11ea-877c-7bd565352a93.png)
2020-08-02 14:31:47 -04:00
Ronan Jouchet 5d9a7ae4bc nit 2020-07-22 21:08:07 -04:00
Ronan Jouchet 6f50ac6f4f A little less aggressive 2020-07-22 21:07:22 -04:00
Ronan Jouchet 3bf3f2c0a8 One more 2020-07-22 21:04:58 -04:00
Ronan Jouchet bed84e5735 One more nit 2020-07-22 20:57:48 -04:00
Ronan Jouchet c87204cce7 Typo 2020-07-22 20:56:58 -04:00
Ronan Jouchet 7c84785e33 typo 2020-07-22 20:56:29 -04:00
Ronan Jouchet f8e0e9d6cb Detail question.md 2020-07-22 20:56:14 -04:00
Ronan Jouchet b631ee23d6 Nit 2020-07-22 20:54:27 -04:00
Ronan Jouchet 6b73b4e8f8 Improve issue templates 2020-07-22 20:53:46 -04:00
Ronan Jouchet 99ceec59c8 Forbid creating template-less issues 2020-07-22 20:37:04 -04:00
Ronan Jouchet 8684887fc6 Bump eslint to 7.x, fix new lint errors 2020-07-18 11:19:58 -04:00
Ronan Jouchet 30f7aff7a9 README: fix links to bugs & feature requests 2020-07-18 10:30:22 -04:00
Ronan Jouchet 1a36d24597 More README nits 2020-07-18 10:27:56 -04:00
Ronan Jouchet cc8d65063a Simplify README, move unmaintained docker doc to Dockerfile 2020-07-18 10:19:58 -04:00
Ronan Jouchet aa5a98b5e7 Update changelog for `v9.1.0` 2020-07-18 02:19:44 -04:00
Ronan Jouchet 53a2e9b4d3 Remove @types/electron-packager, they're built-in since 15.x 2020-07-18 02:18:33 -04:00
Ronan Jouchet 792156f376 Bump deps: electron-packager, ts-loader, types 2020-07-18 01:47:46 -04:00
Ronan Jouchet c18fc1ef9a Bump default Electron to 9.1.0 2020-07-18 01:36:32 -04:00
Ronan Jouchet f23447312d Fix 'Image could not be created' app error on run (fix #992)
Electron 9.x now crashes when passed a non-existent icon.
Also, it accepts both ico and png.
So, do our best to pass it a file that exists.
2020-07-18 01:26:23 -04:00
Pranav Shikarpur e592c6bca6
Bumped up NODE docker image version from 8 to 12.18.1 (#996)
Updated Node version in the Dockerfile from 8-stretch to 12.18.1-stretch. 12.18.1 is currently the most recent LTS version of NodeJS.
2020-06-28 16:15:30 -04:00
Ronan Jouchet 9b0252d597 release.md: document creating & pushing npm-release-causing tag 2020-06-13 11:27:38 -04:00
Ronan Jouchet ab852c3f6c Update changelog for `v9.0.0` 2020-06-13 11:03:58 -04:00
Ronan Jouchet 3b344306b0 README: mention Node 10 is required 2020-06-13 10:51:00 -04:00
Ronan Jouchet 8fa394a1c0 [BREAKING CHANGE] Require Node.js >= 10
- Several deps started requiring it
- CI started breaking on Node 8
- Node 8 is end-of-life, no longer maintained
- Even latest Debian stable and Ubuntu LTS ship Node 10:
    https://packages.debian.org/search?suite=stable&keywords=nodejs
    https://packages.ubuntu.com/search?searchon=names&suite=all&section=all&keywords=nodejs

So, requiring Node 10 and npm 6 going with it.
2020-06-13 10:46:27 -04:00
Ronan Jouchet e5ba8c779f Bump default Electron to 9.0.4, bump deps (jest, electron-context-menu) 2020-06-13 10:23:15 -04:00
Luke Hamburg 6b81324531
--help: fix typo, clarify --icon helptext (PR #976)
- correct small typo (`he`→`the`)
- clarify that `--icon` accepts .icns on macOS
2020-06-13 10:05:43 -04:00
Alexander Weps 1d3bed5f09
Fix: notifications (fix #88, fix #956), processEnvs, using as git (#955)
1. Fix (broken since 2016): Notifications broken by lambda constructor
2. Fix: `--processEnvs` broken by additional processEnvs object, the result was:
`processEnvs: {processEnvs: {...}}` which caused the conversion of the inner object into string `[object Object]`, no nesting allowed there probably. Compatibility introduced.
3. Fix: package.json missing `prepare` (or even prepublish), which breaks using as git dependency.
2020-04-27 11:52:21 -04:00
Ronan Jouchet 9ccda87938 Update changelog for `v8.0.7` 2020-04-22 22:12:36 -04:00
Ronan Jouchet eef320a150 Bump default Electron to 8.2.3, and bump app/electron-context-menu to 1.x 2020-04-22 22:09:13 -04:00
Ronan Jouchet 4fbc3f7503 Fix 'Unable to load preload script' (fix #934)
No reason to use loglevel in the *app*.
It makes sense in the CLI, but in the app console.{info,log,...} is totally fine.
2020-04-22 22:02:34 -04:00
Ronan Jouchet fafaead442 Bump default Electron to 8.2.1 2020-04-06 22:01:32 -04:00
Ronan Jouchet fd0395a860 Update changelog for `v8.0.6` 2020-03-27 09:11:58 -04:00
Ronan Jouchet 34e443c832 Update changelog for `v8.0.5` 2020-03-27 09:00:39 -04:00
Ronan Jouchet 139a5745c8 Fix unintentionally *global*/os-wide keyboard shortcuts (fix #930) 2020-03-27 08:47:44 -04:00
Ronan Jouchet 0c2510f31e Bump default Electron to 8.2.0 and Prettier 2020-03-24 20:27:25 -04:00
Ronan Jouchet 91426d2e3e Travis: fix warning 'deploy: key api_key is an alias for api_token, using api_token' 2020-03-20 22:46:56 -04:00
Ronan Jouchet 86cf42844f Log a helpful error when failing to parse JSON arg (fix #928) 2020-03-20 21:03:37 -04:00
Ronan Jouchet 9f11976b3c Bugreport issue template: add checkmark for latest version 2020-03-19 18:09:37 -04:00
Ronan Jouchet 3c4b743c67 Improve bugreport issue template 2020-03-19 12:30:08 -04:00
Ronan Jouchet 4068497120 App/menu: back & forward: expose standard shortcuts first & handle mac, keep old weird shortcuts for backward compat 2020-03-18 08:51:46 -04:00
Ronan Jouchet f7215814d7 App/menu: cleanup, typing, properly hide devtools when asked (fix #842) 2020-03-18 00:21:17 -04:00
Ronan Jouchet 72a9eae6d6 Update changelog for `v8.0.4` 2020-03-16 21:34:20 -04:00
Ronan Jouchet 35eb72edfd Fix travis build
We no longer have a postinstall so we need to explicitly
'npm run dev-up' for cli+app deps, like we did before.
2020-03-16 21:20:38 -04:00
Ronan Jouchet cde5c1e13b Fix failing to global-sudo-install due to postinstall script (fix #923)
As documented in https://github.com/jiahaog/nativefier/issues/923#issuecomment-599300317 ,

- #923 is caused by installing placeholder app deps at nativefier
  *install* time, with yarn (8.0.2) or npm (8.0.3). This is new in
  Nativefier 8.x, for the motivations behind it, see
  https://github.com/jiahaog/nativefier/pull/898#issuecomment-583865045

- During testing, I did test global installs, but never to a
  system / non-user-writable path (my `$npm_config_prefix` is set to
  `"$HOME/.node_modules"`)

- But without such a config and when installing globally to a
  non-user-writable/system path with `sudo npm i -g nativefier`,

    - Installation of nativefier core works...

    - ... but then `postinstall` tries to do its job of installing
    app deps, and fails in various OS-dependent ways, but all about
    access rights.
    I suspect that, although main nativefier install runs as `su` with
    access rights to system paths, `postinstall` scripts are run *out*
    of `su`.
    That would make sense for security reasons: out of hook scripts,
    npm knows exactly what will be touched in your filesystem: it's the
    static contents of the published tarball; a postinstall script with
    sudo rights could do nasty dynamic stuff. So, although I don't see
    any mention of that in
    [npm-scripts docs / hooks](https://docs.npmjs.com/misc/scripts#hook-scripts)
    and I haven't dug npm/cli's code, I can understand it.

So, reverting back to `webpack`ing the placeholder app, as done pre-8.0.
2020-03-16 21:06:03 -04:00
Ronan Jouchet 0a380bd0f4 Update changelog for `v8.0.3` 2020-03-15 20:57:48 -04:00
Ronan Jouchet f1f6dda4d1 Fix failing to install due to app yarn install
Actually not sure this will work, but let's try.
If that works, that means we're back to pre-
https://github.com/jiahaog/nativefier/pull/898#issuecomment-583865045 ,
with a 60s timeout due to npm bug https://github.com/npm/cli/issues/757

Looking at a real fix, potentially coming back to `webpack` the app.
2020-03-15 20:54:21 -04:00
Ronan Jouchet dbf12e4f78 README: fix path to images 2020-03-15 17:57:22 -04:00
Ronan Jouchet 2c036cb8a2 Update changelog for `v8.0.2` 2020-03-15 17:41:20 -04:00
Ronan Jouchet b16e893237 Travis: only attempt to deploy with linux/node12 build, take 2 2020-03-15 17:40:43 -04:00
Ronan Jouchet f600047463 Update changelog for `v8.0.1` 2020-03-15 17:24:51 -04:00
Ronan Jouchet fd37a6a4c8 Align full tests (linter+tests) with deployment platform 2020-03-15 17:23:48 -04:00
Ronan Jouchet d6e7aa6f41 Fix inferTitle test broken on Node 8 2020-03-15 17:19:19 -04:00
Ronan Jouchet 20de73c559 Travis: only attempt to deploy with linux/node12 build 2020-03-15 17:09:56 -04:00
Ronan Jouchet 93c2d32c87 Update changelog for `v8.0.0` 2020-03-15 16:51:09 -04:00
Ronan Jouchet c9ee6667d4
Revamp and move to TypeScript (#898)
## Breaking changes

- Require **Node >= 8.10.0 and npm 5.6.0**
- Move to **Electron 8.1.1**.
- That's it. Lots of care went into breaking CLI & programmatic behavior
  as little as possible. **Please report regressions**.
- Known issue: build may fail behind a proxy. Get in touch if you use one:
  https://github.com/jiahaog/nativefier/issues/907#issuecomment-596144768

## Changes summary

Nativefier didn't get much love recently, to the point that it's
becoming hard to run on recent Node, due to old dependencies.
Also, some past practices now seem weird, as better expressible
by modern JS/TS, discouraging contributions including mine.

Addressing this, and one thing leading to another, came a
bigger-than-expected revamp, aiming at making Nativefier more
**lean, stable, future-proof, user-friendly and dev-friendly**,
while **not changing the CLI/programmatic interfaces**. Highlights:

- **Require Node>=8**, as imposed by many of our dependencies. Node 8
  is twice LTS, and easily available even in conservative Linux distros.
  No reason not to demand it.
- **Default to Electron 8**.
- **Bump** all dependencies to latest version, including electron-packager.
- **Move to TS**. TS is great. As of today, I see no reason not to use it,
  and fight interface bugs at runtime rather than at compile time.
  With that, get rid of everything Babel/Webpack.
- **Move away from Gulp**. Gulp's selling point is perf via streaming,
  but for small builds like Nativefier, npm tasks are plenty good
  and less dependency bloat. Gulp was the driver for this PR: broken
  on Node 12, and I didn't feel like just upgrading and keeping it.
- Add tons of **verbose logs** everywhere it makes sense, to have a
  fine & clear trace of the program flow. This will be helpful to
  debug user-reported issues, and already helped me fix a few bugs.
    - With better simple logging, get rid of the quirky and buggy
      progress bar based on package `progress`. Nice logging (minimal
      by default, the verbose logging mentioned above is only used
      when passing `--verbose`) is better and one less dependency.
- **Dump `async` package**, a relic from old callback-hell early Node.
  Also dump a few other micro-packages unnecessary now.
- A first pass of code **cleanup** thanks to modern JS/TS features:
  fixes, simplifications, jsdoc type annotations to types, etc.
- **Remove GitHub integrations Hound & CodeClimate**, which are more
  exotic than good'ol'linters, and whose signal-to-noise ratio is too low.
- Quality: **Add tests** and add **Windows + macOS CI builds**.
  Also, add a **manual test script**, helping to quickly verify the
  hard-to-programatically-test stuff before releases, and limit regressions.
- **Fix a very small number of existing bugs**. The goal of this PR was
  *not* to fix bugs, but to get Nativefier in better shape to do so.
  Bugfixes will come later. Still, these got addressed:
  - Add common `Alt`+`Left`/`Right` for previous/next navigation.
  - Improve #379: fix zoom with `Ctrl` + numpad `+`/`-`
  - Fix pinch-to-zoom (see https://github.com/jiahaog/nativefier/issues/379#issuecomment-598612128 )
2020-03-15 16:50:01 -04:00
Ronan Jouchet f115beed0d Issue templates: nits 2020-03-10 09:19:02 -04:00
Ronan Jouchet 9b50ebf3ca Issue templates / bugreport: nit 2020-03-10 09:17:05 -04:00
Ronan Jouchet a4c373db80 Issue templates: comment header, enrich featurerequest 2020-03-10 09:15:53 -04:00
Ronan Jouchet e57dc16cd3
Update bug_report.md 2020-03-10 09:12:46 -04:00
Ronan Jouchet dc46b16247
Update feature_request.md 2020-03-10 09:11:18 -04:00
Ronan Jouchet 92ef0a4f08
Delete ISSUE_TEMPLATE.md 2020-03-10 09:07:39 -04:00
Jia Hao ba37e5ccd0
Add modern github issue templates: bug, feature request (#901)
Co-authored-by: Ronan Jouchet <ronan@jouchet.fr>
2020-03-10 09:06:34 -04:00
Roberto Leinardi 261ee2913b
Fix #903: JS error when using Electron 7+ and --tray (PR #904)
The API `tray.setHighlightMode(mode)` has been be removed in electron v7.0
without replacement.

This causes the display of an error dialog every time an app is
shown/hidden if the parameter `--tray` is used when nativefying. This is
completely independent form the website you are nativefying and it
happens with all the version of electron after 6.x.

Source: https://www.electronjs.org/docs/api/breaking-changes#tray
2020-02-16 18:11:48 -05:00
Ronan Jouchet b927b401b0 Update changelog for `v7.7.1` 2020-01-23 20:00:43 -05:00
Ronan Jouchet 97f092b77b Bump default Electron to 5.0.13 2020-01-23 19:57:18 -05:00
Siddharth Srinivasan 010f88aa1b Fix weirdly platform-dependent folder naming logic (PR #850, fix #708) 2020-01-15 09:09:37 -05:00
lockwiser 18813776ca Doc: Clarify background-color arguments (#891) 2020-01-14 13:52:49 -05:00
Darío Hereñú 2edbf799c1 Fixed duplicate word in doc for `-bounce` (#883) 2019-12-13 18:40:24 -05:00
Adrian DC 359f57c1f7 app: fix: nativefier application menu support on Electron 5.0 (#876) 2019-10-27 15:42:02 -04:00
otato.z ef13ff1e1d feat: proxy rules with `--proxy-rules` flag (#854)
See https://electronjs.org/docs/api/session?q=proxy#sessetproxyconfig-callback
2019-10-22 19:38:39 -04:00
Adrian DC 84f06e3e79 app: handle nativefier.json readonly access with options.maximize (#856)
**Resolves #855**

**Example result upon Terminal launch:**

`WARNING: Ignored nativefier.json rewrital (Error: EACCES: permission denied, open '/usr/lib/jupyter-remote-client/resources/app/nativefier.json')`

**Behaviour:**

+ Instead of crashing, the application properly opens and is always maximized.

+ When permissions are R/W, the application is maximized on first launch and the closing states are remembered by the OS throughout launches.
2019-10-22 19:36:40 -04:00
shun e00f08e5d6 Fix filter exception when injecting CSS (PR #837)
When injecting CSS, an `onBeforeRequest paramater 'filter' must have property 'urls'` error is thrown.

![image](https://user-images.githubusercontent.com/599164/63574823-a3dd6180-c5c3-11e9-89c0-a9debdd4f696.png)

This is caused by [Electron PR #19337: Throw on invalid webRequest filters](https://github.com/electron/electron/pull/19337).

So, pass filter argument properly, like Electron expects it strictly.
2019-08-28 07:28:18 -04:00
Ronan Jouchet 4f235c550a Update changelog for `v7.7.0` 2019-08-22 21:55:44 +02:00
Clement Fradet Normand 99b1808dde Improve doc for --internal-urls (PR #833) 2019-08-22 21:46:20 +02:00
Alex D'Andrea b4d1ee0d39 Restore login functionality broken since Electron 5.x (PR #826)
nodeIntegration is required if eg. Javascript code makes use of the
`require` expression to import classes into the current scope. login.js
uses an electron import - without it, the callback mechanism does not
work, and thus the whole login functionality.

Electron seems to have changed the default value for a windows `nodeIntegration` to `false` since version 5 (see https://stackoverflow.com/questions/55093700/electron-5-0-0-uncaught-referenceerror-require-is-not-defined)

Without the integration, the login component's functionality is broken, though. This PR enables the nodeIntegration feature for the login window and makes it properly propagate the given credentials.

Tested with Electron 6.0.0 on Linux.
2019-08-22 21:41:37 +02:00
FabulousCupcake d0a0614ba3 Add --browserwindow-options to completely expose Electron options (PR #835)
This adds a new flag `--browserwindow-options`, taking a stringified JSON object as input.  
It will be inserted directly into the options when BrowserConfig is initialized.

This allows total configurability of the electron BrowserWindow configuration via nativefier.

An example use case is added to the documentation, which modifies the default font family in the browser.

#### References

- https://github.com/electron/electron/blob/master/docs/api/browser-window.md#new-browserwindowoptions
  - See the following for electron v3.1.7
  - https://github.com/electron/electron/blob/v3.1.7/docs/api/browser-window.md#new-browserwindowoptions
2019-08-22 21:37:49 +02:00
Umair Ahmed 81c422d4e0 Support setting background color (fixes #795) (PR #819)
Adds a `--background-color` flag for the background of the window when the app isn't loaded.
2019-08-22 21:25:58 +02:00
Nate Woolls 5433569921 Support macOS 10.4+ Dark Mode, default to Electron 5.x (#796)
Introduces a `--darwin-dark-mode-support` flag. 
(I kept the same flag used by Electron Packager.)
This required bumping Electron and Electron Packager.

Addresses:

- https://github.com/jiahaog/nativefier/issues/733
- https://github.com/jiahaog/nativefier/issues/727
2019-08-22 21:05:39 +02:00
Adrian DC b959956a38 Squirrel: resolve .quit() issue with missing ../screen (PR #784)
* As explained in: https://github.com/electron/electron/issues/8862#issuecomment-294303518
    an issue with .quit() exists with a "Cannot find module '../screen'" issue,
    while using the .exit() alternative avoids the issue

* Validated on Windows with the same logic as #744 where the issue recently appeared
2019-04-19 13:21:36 -04:00
Ronan Jouchet 070efe6fa9 Update changelog for `v7.6.12` 2019-03-25 19:14:25 -04:00
Ronan Jouchet 5cdfe96cbe Bump default electron to 3.1.7 2019-03-25 11:19:33 -04:00
Ying 479611ecc4 Fix crash when launching a second instance using option --single-instance (Fixes #664)
This refactors the deprecated call to `app.makeSingleInstance()`.
See [electron / app.requestSingleInstanceLock()](https://electronjs.org/docs/all?q=app.requestSingleInstanceLock).
2019-03-25 11:16:28 -04:00
Anas Emad a27bb0fd35 Prevent menu from opening on Alt+Shift, by defining Alt+... menu shortcuts (#768)
On Linux if you try to change the keyboard layout with Alt + Shift,
the menu will pop up. Shortcuts are needed because Electron opens
the first menu on pressing `Alt` if no hotkey is assigned.

[Similar issue in RocketChat](https://github.com/RocketChat/Rocket.Chat.Electron/issues/50)
2019-03-17 11:29:15 -04:00
Ronan Jouchet 07faeb1881 Update changelog for `v7.6.11` 2019-02-10 22:20:40 -05:00
Ronan Jouchet 726f44d266 Bump default Electron to 3.1.3 2019-02-09 21:46:15 -05:00
Adrian DC 5b6cc89f22 Fix #316: Add --clear-cache flag to cleanup session on start/exit ("incognito" mode) (PR #747)
- Add a new `clearCache` option and `--clear-cache` parameter
  to trigger session cleanups upon window launch and close

- Covers the feature request from issue #316 

- Use case example: Forcing authentification / login between sessions without limiting cache size
2019-02-08 10:03:29 -05:00
Ronan Jouchet 85aacaaa52
Doc for squirrel installers 2019-02-08 09:59:20 -05:00
Adrian DC 310d63e397 Support packaging nativefier applications into Squirrel-based installers (#744)
[Squirrel](https://github.com/Squirrel/Squirrel.Windows) is *"an installation and update
framework for Windows desktop apps "*.

This PR adds `electron-squirrel-startup`, allowing to package nativefier applications 
into squirrel-based setup installers. Squirrel require this entrypoint to perform
desktop and startup menu creations, without showing the UI on setup launches.

- References: https://github.com/mongodb-js/electron-squirrel-startup

- Resolves `electron-winstaller` and `electron-installer-windows` support of desktop / startup menu shortcuts for nativefier packaged applications.

- The `electron-squirrel-startup` entrypoint has no effect on both Linux and Darwin, only on Windows

- Supporting it directly inside `nativefier` avoids having to "hack" around the existing `main.js`
  and including dependencies from `electron-squirrel-startup` in an intermediate package
  to be included in a third layer for the final installer executable

- The following script based on both `nativefier` and `electron-winstaller` templates
   represents a portable proof of concept for this merge request :

```js
var nativefier = require('nativefier').default;
var electronInstaller = require('electron-winstaller');

var options = {
  name: 'Web WhatsApp',
  targetUrl: 'http://web.whatsapp.com',
  platform: 'windows',
  arch: 'x64',
  version: '0.36.4',
  out: '.',
  overwrite: false,
  asar: false,
  counter: false,
  bounce: false,
  width: 1280,
  height: 800,
  showMenuBar: false,
  fastQuit: false,
  userAgent: 'Mozilla ...',
  ignoreCertificate: false,
  ignoreGpuBlacklist: false,
  enableEs3Apis: false,
  insecure: false,
  honest: false,
  zoom: 1.0,
  singleInstance: false,
  fileDownloadOptions: {
    saveAs: true
  },
  processEnvs: {
    GOOGLE_API_KEY: '<your-google-api-key>'
  }
};

nativefier(options, function(error, appPath) {
  if (error) {
    console.error(error);
    return;
  }
  console.log('App has been nativefied to', appPath);

  resultPromise = electronInstaller.createWindowsInstaller({
    appDirectory: 'Web WhatsApp-win32-x64',
    outputDirectory: './',
    authors: 'Web WhatsApp',
    exe: 'Web WhatsApp.exe'
  });

  resultPromise.then(() => console.log('It worked!'), e => console.log(`No dice: ${e.message}`));
});
```
2019-02-08 09:57:32 -05:00
Dword 3588f8a85f Icon conversion: don't crash if source/destination paths have spaces (PR #736) 2019-01-09 10:08:17 -05:00
Ronan Jouchet c722f8a5ac Update changelog for `v7.6.10` 2019-01-01 23:44:57 -05:00
Ronan Jouchet d6b4b23f0a Bump default Electron to 3.0.13 2019-01-01 23:42:50 -05:00
Dominic Hopton e064f765cb Fix CSS & JavaScript injection (Fixes #703, Fixes #731, PR #732)
* Fix for CSS Injection not working (#703)

Issue:
When using `onHeadersReceived`, the code was passing `null` for the filters.
This appears to trigger behaviour that matches _no_ urls at all.
This results in it never being called to inject the CSS.

Fix:
Pass an empty array instead. Now it's called for all URLs.

Tests pass & linting is clean

* Fix JavaScript injection (#731)

Issue:
It appears that on low endd evices (Core m3 MacBook), the attachment to
`DOMContentLoaded` happens _after_ the event has been raised, so does
not have a chance to inject the script.

Fix:
Move the attachment to the top of the file -- before the imports. This
triggers a bunch of linting erros, so also added disablement inplace.

Additional:
Clarified when the injected JS gets loaded, and what it can assume about
the DOM.
2019-01-01 23:38:45 -05:00
Robert Barat 9acd85bb65 README: fix typo (#723) 2018-12-14 08:37:08 -05:00
Ronan Jouchet ae3eafe653 Update changelog for `v7.6.9` 2018-12-01 14:07:47 -05:00
Felix Eckhofer 8600c78d1a Add --start-in-tray CLI flag to let app start in background (PR #564, Fixes #522) 2018-12-01 14:04:55 -05:00
Ronan Jouchet 1f6e74b1e3
README: fix broken link to changelog 2018-12-01 00:21:32 -05:00
Ronan Jouchet d3a6acc22d Let Jest install its own regenerator-runtime dep
See: https://jestjs.io/docs/en/getting-started
Note: Explicitly installing regenerator-runtime is not needed if you use npm 3 or 4 or Yarn
2018-12-01 00:15:13 -05:00
Ronan Jouchet cb7e17b8e3 Bump default Electron to 3.0.10 2018-11-30 23:42:28 -05:00
Luccas Clezar 114b4a9724 Fix CSS injection broken with Electron 3 (PR #709, Fixes #703)
See Electron 3.0 breaking API changes:
https://electronjs.org/blog/electron-3-0#breaking-api-changes

  [#12477] https://github.com/electron/electron/pull/12477
           refactor: removed `did-get-response-details` and `did-get-redirect-request events`
2018-11-30 21:14:20 -05:00
Ronan Jouchet 038812cd46
Travis: add Node.js 11 run 2018-11-30 21:09:17 -05:00
Ronan Jouchet 5bee4710e3
Fix ESLint contradiction between CodeClimate & npm run ci task
CodeClimate is drunk, it says it runs eslint, but
yields different results from the eslint task.

Disabling it in codeclimate.
2018-11-30 20:53:02 -05:00
Benedikt Rötsch 3029cba01f Support global shortcuts that trigger input events (PR #698, Fixes #15)
This adds a new flag, allowing the user to define global shortcuts that trigger input events within the main window.

That way, I could easily wrap SoundCloud and Deezer to create a native app which reacts on my keyboard media buttons.
2018-11-04 21:03:52 -05:00
Hugo Locurcio 35c66b02b5 Update documentation URLs in the CLI help message (#683)
This updates URLs in the command-line help to avoid redirects.
2018-10-26 21:45:19 -04:00
Ronan Jouchet 11644e34d8 Update changelog for `v7.6.8` 2018-10-06 22:20:21 -04:00
Ronan Jouchet 0f88a761de Bump default electron to 3.0.3, deps (eslint-plugin-prettier) 2018-10-06 22:13:47 -04:00
Kevin Jalbert c974014465 Remove mention to older badge option in docs (#693)
The `--counter` option mentioned a _badge option_, which doesn't appear in the docs. This change removes this as it is not relevant in the context.
2018-10-02 14:15:29 -04:00
André Werlang ad2f47df9c Update docs about icon requirements on Windows (#663)
On Windows, the supplied icon needs to be in .ico format
2018-08-22 16:18:46 -04:00
Sascha Ißbrücker 27ea3fc4a3 Show application window on notification click (#640) 2018-08-22 16:06:44 -04:00
Ronan Jouchet e228f0cff8 Bump default Electron to 2.0.8, upgrade dep 2018-08-22 16:01:45 -04:00
Ronan Jouchet 7aae5084d1 Bump default electron to 2.0.7 2018-08-08 19:55:32 -04:00
Ronan Jouchet 2b8f1390fb Update changelog for `v7.6.7` 2018-07-31 21:29:04 -04:00
Ronan Jouchet d99c7bec1f Bump electron to 2.0.6 2018-07-31 21:17:59 -04:00
Ronan Jouchet 0c9ef088a5 Fix broken --version 2018-07-31 20:29:26 -04:00
Ronan Jouchet 38edc9191e README: document node 4 is not supported/tested 2018-07-22 13:27:18 -04:00
Goh Jia Hao 93a9833c47 Update npm token
Tokens were invalidated in https://status.npmjs.org/incidents/dn7c1fgrr7ng
2018-07-22 09:55:51 -07:00
Ronan Jouchet eb23217766 README: link to bugs and feature requests, cleanup 2018-07-22 11:49:50 -04:00
Ronan Jouchet 94efbc44c4 Update changelog for `v7.6.6` 2018-07-22 10:44:44 -04:00
Ronan Jouchet b31be18303 Update changelog for `v7.6.5` 2018-07-21 09:23:03 -04:00
Ronan Jouchet 19561e9ad6 Make eslint happy 2018-07-21 09:16:02 -04:00
Ronan Jouchet bbef14ccc6 Bump default Electron to 2.0.5, upgrade deps 2018-07-21 08:58:53 -04:00
chocolateboy 22ef3d39b6 Fix doc typo (#656) 2018-07-20 12:29:37 -04:00
Ronan Jouchet 1b66fcd8c8 Bump default Electron to 2.0.4 2018-07-10 15:54:26 -04:00
David Kramer c065cad01a Fix #633 - "Copy Current URL" causing TypeError(#634)
When the `getCurrentUrl` function was refactored to make use of the `withFocusedWindow` function in ac99c6424d, it stopped returning a value and broke the "Copy Current URL" feature (#633).

This change restores the original behavior of the getCurrentURL function and makes the "Copy Current URL" feature functional again.
2018-06-14 08:06:30 -04:00
Goh Jia Hao 0b47999c3e Fix lint error 2018-06-10 11:07:06 -07:00
Goh Jia Hao 147a02743a Add jest --watch helper for npm scripts 2018-06-10 11:00:22 -07:00
Goh Jia Hao bd89f90a3d Remove workaround for slashes in page title
This was reported in #22 and has been fixed upstream in electron-userland/electron-packager#308
2018-06-10 11:00:22 -07:00
Goh Jia Hao 49272d1a89 Add test for inferTitle 2018-06-10 11:00:16 -07:00
Kevin Schaich 04e2f64ba7 Add Unix/Mac-conventional `-v` version flag (PR#628)
Most Unix-based command line utilities respond to a _lowercase_ `-v` flag which outputs the current version. Adding that as an alias here in addition to the already present `--version` and `-V` flags :)
2018-05-31 18:15:24 -04:00
Ronan Jouchet 778418cdfb Update changelog for `v7.6.4` 2018-05-31 08:15:08 -04:00
Khai Nguyen b8c1d35ba4 Add --title-bar-style flag (macOS only) (PR#627) 2018-05-31 08:05:12 -04:00
David Kramer 9e587e5fe3 Fix #610 - Make the counter regexp allow punctuation (#626)
When using an app such as Gmail, the unread count is included in the title, and the --counter feature displays that number on the dock icon. However, when the number is larger than 999, it includes commas in the number (e.g. "1,000"), and the number is no longer displayed on the dock icon, because the regular expression used to detect the counter value does not permit punctuation. This change modifies the regular expression used to match the counter value to permit "." and ",".
2018-05-28 08:14:54 -04:00
David Kramer aa243b6f80 Fix sites that use about:blank redirect technique (#623)
* Fix sites that use about:blank redirect technique

When you open some links with Google Calendar, instead of opening the link directly, the site opens a new window with the location 'about:blank' and then sets the new window's document content to include a refresh directive to open the actual link. This change causes the 'about:blank' links to be handled internally so that the technique can actually work.

* Hide 'about:blank' windows while they perform the redirect

After a new window is created for an 'about:blank' link, the redirect occurs, which causes another window to be opened. This change causes the 'about:blank' to be created hidden, and then closed entirely once the redirect finishes.

* Add tests for `linkIsInternal`

* Refactor onNewWindow to make it testable
2018-05-27 17:18:59 -04:00
David Kramer fe6fd9d2a1 Fix `prettier` lint error (#625) 2018-05-27 17:18:33 -04:00
David Kramer bde4ea68fd Force all external links to be opened externally (#624) 2018-05-27 14:04:08 -04:00
David Kramer 1afc480923 Fix #621 - Always open external links externally (#622)
The tab feature introduced by #579 included a change that checks the `disposition` parameter and conditionally creates tabs, and that check was placed prior to the check to see if the URL is internal. This change moves the `linkIsInternal()` check earlier so that external links are always opened externally, regardless of disposition.
2018-05-27 14:02:23 -04:00
David Kramer 587d615085 Fix #616 - Only override the default window opening behavior when necessary (#620)
As part of #591, all window creation was routed through a createNewWindow function.  That change introduced the regression reported in #616 in which popup windows could not communicate with their parent windows. This change reverts that behavior for windows opened via JavaScript (that aren't being opened as tabs and aren't being opened in external browsers), thereby fixing the reported regression.
2018-05-26 22:50:36 -04:00
Goh Jia Hao 16aae0a937 Refactor tests to use async/await 2018-05-24 22:46:49 -07:00
Goh Jia Hao 04a1460460 Add babel object spread 2018-05-24 22:23:43 -07:00
Goh Jia Hao 4350ccf15d Don't run tests on node 4 and 5 2018-05-24 00:38:07 -07:00
Goh Jia Hao f0da2407f7 Separate e2e tests 2018-05-24 00:12:22 -07:00
Goh Jia Hao 95fc46d38d Integrate prettier 2018-05-24 00:02:44 -07:00
Goh Jia Hao 949dcfadd8 Migrate Mocha tests to Jest 2018-05-23 23:44:03 -07:00
Ronan Jouchet 17ccda36f0 Update changelog for `v7.6.3` 2018-05-23 14:52:40 -04:00
Ronan Jouchet 99ee5161a1 Bump default Electron to 2.0.2 2018-05-22 16:39:44 -04:00
Ronan Jouchet fe48684ee2 Bump default Electron to 2.0.1 and deps:validator 2018-05-16 15:32:27 -04:00
Ronan Jouchet 039ad50fa6 eslint 2018-05-11 16:58:36 -04:00
Ronan Jouchet 6a8114e505 Fix deprecations: electron-packager cb->promise, mocha compilers->require 2018-05-11 16:55:50 -04:00
David Kramer 69a5b2cbbe Fix Gmail complaining window creation was prevented by a popup blocker (PR #603)
By changing incorrect window `guest` property to `newGuest`. See
https://github.com/electron/electron/blob/master/docs/api/web-contents.md#event-new-window

> Calling `event.preventDefault()` will prevent Electron from 
> automatically creating a new BrowserWindow. If you call
> `event.preventDefault()` and manually create a new BrowserWindow
> then you must set `event.newGuest` to reference the new BrowserWindow
> instance, failing to do so may result in unexpected behavior.
2018-05-10 23:54:34 -04:00
Ronan Jouchet ef755b53c7
Travis: try node 10 2018-05-02 21:51:27 -04:00
David Kramer be25eab45d Fix #547 - Default to Electron 2.0.0 and review deprecations (PR #587) 2018-05-01 19:29:03 -04:00
David Kramer ac99c6424d macOS: Add native tabs (PR #579)
Electron supports using native tabs on macOS (API added in Electron 1.8.1). This change adds a context menu item on platforms that support it (macOS for now) to open links in new tabs, and also adds support for {command,middle}-clicking links to open them in a new tab.

Maintainer (@ronjouch) note: this feature is macOS-only. Windows/Linux patches welcome 🙂.
2018-05-01 19:24:35 -04:00
Ronan Jouchet 025936e9c5 Update changelog for `v7.6.2` 2018-05-01 19:06:46 -04:00
Ronan Jouchet e62b45b697 Default to electron 1.8.6, dep bump electron-packager 2018-05-01 18:56:23 -04:00
David Kramer 0848143096 Fix #590, Fix #439 - Ensure children windows have the same behavior as the mainWindow (PR #591) 2018-04-30 21:36:45 -04:00
Ronan Jouchet 27d3d5a537 pngquant screenshot 2018-04-26 09:50:40 -04:00
Ronan Jouchet 9bae3891bf Homogenize shebangs: convertTo{Ico,Png} 2018-04-26 09:49:24 -04:00
David Kramer dc257052c5 Fix #199 - macOS: Perform image conversion tasks using sips when available (PR #583)
Eliminates the requirement for imagemagick to be present on macOS for icon conversion.
Based off of the code from PR #279.
2018-04-26 07:42:03 -04:00
Ronan Jouchet eeef1facf3
Stay on npm@5.8.x for a little while
npm6 breaks our node4+5 builds:

https://travis-ci.org/jiahaog/nativefier/jobs/369940904
https://travis-ci.org/jiahaog/nativefier/jobs/370872623
2018-04-25 20:33:58 -04:00
David Kramer ec1023d7ef Fix #95, #384 - Use electron-context-menu, supporting cut/copy/paste (PR #588)
The electron-context-menu package uses the context-menu event emitted by WebContents (API added in Electron 1.0.2) to add a general context menu supporting generic actions (e.g. cut/copy/paste) that can be customized. This change replaces the existing context menu, which relies on adding an event listener in preload.js, with one built using the new package.
2018-04-22 19:48:56 -04:00
David Kramer 92bc44a712 Fix #94, #575: Fix run-time crash due to insufficient permissions (PR #581) 2018-04-22 16:55:46 -04:00
David Kramer 2d09455c17 Fix #574 - Allow build to continue if icon conversion fails (PR#585)
By unsetting `options.icon` if input cannot be converted.
2018-04-22 15:57:58 -04:00
David Kramer bbb513d420 Fix #364 - Add --disable-gpu flag to disable hardware acceleration (PR #584) 2018-04-22 15:56:10 -04:00
David Kramer 454ab1e7bd Fix #474: Remember custom zoom level (PR #582)
... by using setZoomFactor instead of sending change-zoom event.
2018-04-22 15:54:29 -04:00
Matt Rose 574205ab0d macOS: Add --bounce option for dock counter (PR #570) 2018-04-14 17:17:25 -04:00
Goh Jia Hao cec29c88ed Update changelog for `v7.6.1` 2018-03-29 22:24:47 -07:00
Goh Jia Hao 4f5b36f731 Fix CD with Travis #482 2018-03-29 22:22:38 -07:00
Goh Jia Hao a8052823ba Update changelog for `v7.6.0` 2018-03-29 21:52:09 -07:00
Goh Jia Hao bb67448328 Fix infer icon url #529 2018-03-29 19:38:04 -07:00
sgroh 46d381481c Fix #549: Add --always-on-top build flag (PR #551) 2018-03-16 18:15:44 -04:00
Ronan Jouchet 8981e55783 Update deps, default to Electron 1.8.4 stable
Semver-major deps upgrades: axios electron-packager require-dir
2018-03-16 17:54:19 -04:00
Ronan Jouchet fc4d365987 Update deps, default to Electron 1.8.2 stable
Semver-major deps upgrades: babel-jest electron-packager gulp-mocha jest shelljs
2018-02-06 21:56:58 -05:00
C. Bess 7270f7d295 Document internal-urls option (PR #465) 2018-02-06 17:19:39 -05:00
David Ollerhead e1909bf5c8 Support Mac App Store (--mas) builds (PR #532) 2018-02-02 08:06:29 -05:00
Bob Roth cec9c4b819 Fix #499: Add options to control file download behavior (PR #526) 2018-01-26 09:59:58 -05:00
Matt Harris 28dca8c647 Fix #325 - Add --x and --y window position flags (PR #515) 2017-12-26 13:00:39 -05:00
Matt Harris eb08d85830 Fix #480 - Move all console.* to loglevel.* calls, eslint-fail on console.* (PR #507) 2017-12-19 08:42:06 -05:00
Paul-Louis NECH 2ca1b67265 Fix #496 - Recommend .png for --icon on all platforms, even macOS (PR #502) 2017-12-09 22:01:28 +02:00
omouren 1c8d04e532 Fix #486 : --tray flag crashes nativefied app under Windows (PR #495) 2017-11-25 23:27:19 -05:00
Ronan Jouchet 8963544afa
Fix #462 - When minimized to tray and single-instance, re-running the app should activate and focus it (#490) 2017-11-24 10:31:08 -05:00
Ronan Jouchet 2b377a7916
Fix #461 & address #375 in Docker: move Dockerfile to Debian and use wine32 (#488)
- Should address #375 (building a win app from linux) under Docker,
  thanks @dipenpatel235 for the suggestion to use `wine32` at
  https://github.com/jiahaog/nativefier/issues/375#issuecomment-304247033
- Regarding #461, Docker build seems fine but it was already
  fine for some time, not sure what happened.

Also clean up a bit the Dockerfile using suggestions from
[hadolint](https://github.com/lukasmartinelli/hadolint/).
2017-11-24 10:28:59 -05:00
Goh Jia Hao db74db8911 7.5.4 2017-11-24 11:36:37 +08:00
Goh Jia Hao 09b236e8eb Update changelog for `v7.5.2` 2017-11-24 11:15:47 +08:00
Ronan Jouchet 6ac4f33661 Update Dockerfile to node8-alpine, fix typos 2017-11-16 14:08:30 -05:00
Ronan Jouchet 6fb3b92eb8
Upgrade dependencies and default to latest Electron 1.7.9 (PR #483)
* Update deps except eslint
* Update eslint and lint:fix (WIP, needs manual fixing for remaining 44 problems)
* Manually fix remaining eslint errors
* Document deprecation of `version-string` as of electron-packager 9.0.0
* Upgrade to Electron 1.7.9 (chrome-58, node-7.9.0, v8-5.8)
* npm: Disable generation of package-lock.json and gitignore it
  --Trying this, package-lock is a pain in PRs. May not be a good idea
  (obviously we lose deps pinning), will revert if necessary.--
* npm tasks: add dev-up-win for Windows developers,
  and e2e for end-to-end tests. Update docs.
* Move normalizeUrl test to a jest unit test, makes no sense to be in the mocha e2e tests
* Switch from babel-preset-es2015 to babel-preset-env,
  with target.node=4.0. Seem like it's today's most convenient
  way to support the latest ES and let babel transpile to what
  makes sense for our currently minimal node version
2017-11-14 08:05:01 -05:00
Goh Jia Hao 78bedc62ac 7.5.1 2017-11-13 00:19:46 +08:00
Goh Jia Hao fef8ad2040 Remove node v9 from travis builds
We have to keep this until `npm install -g npm` supports node v9, see nodejs/node#16649
2017-11-12 17:47:32 +08:00
Goh Jia Hao 300bf28b51 Update changelog for `v7.5.0` 2017-11-12 01:14:58 +08:00
omouren 885790bc22 Fix #304 - Add --tray CLI flag to let app running in background on window close. Supports in-title counter. (#457) 2017-10-05 19:32:48 -04:00
Devin Buhl 4c581f9067 Fix #275 - Add HTTP --basic-auth-{username,password} CLI flags (#444) 2017-10-05 18:44:03 -04:00
Bob Roth 3e7cec2ecb Fix #438 - Add missing params to buildApp (#450) 2017-10-03 12:02:41 -04:00
Matt Harris ede0bd8ca6 Fix #448 - Add offline build detection and advice (#452) 2017-10-03 11:58:00 -04:00
Sayed Hadi Hashemi 84dcde9d5b Fix #404: Add 'Paste and Match Style' to Edit Menu (#447) 2017-10-03 11:51:34 -04:00
ReadmeCritic 952290899e Correct the capitalization of Xcode in README (#459) 2017-10-03 11:44:31 -04:00
Bob Roth c9d2040327 Allow nativefier to set process.env variables (#419) 2017-08-16 10:20:43 -04:00
Bob Roth fc7a213a87 Fix #226 - Added support for app-copyright, app-version, build-version, version-string and win32metadata (#244) 2017-08-15 14:18:44 -04:00
Lukas Kurucz 094cac23bc Make title counter regex match '+' after number, used by certain sites (#424) 2017-08-13 21:22:15 -04:00
Goh Jia Hao b5913b086e Don't run jshint on hound 2017-08-12 11:44:00 +08:00
Goh Jia Hao 33fe71a855 Add package-lock for npm
This fixes dependency issues that were experienced in notably #425 and
other master commits I've tried to restart on 12/08/17
2017-08-12 11:34:48 +08:00
Ronan Jouchet c6019ad13e Release 7.4.1 (#425) 2017-08-10 22:51:32 +08:00
Brian Blakely 1f07f148c6 Force fullscreen when options.fullScreen is true (#403)
Fixes #402, as electron-window-state does not appear to remember fullscreen in all cases.
2017-08-06 20:01:34 -04:00
Bob Roth 6ed2bd0672 fixes issue #353: missing companyName in crashReporter.start (#417) 2017-07-21 15:55:39 -04:00
Ronan Jouchet 2b061e497f ISSUE_TEMPLATE: Add common questions (#415) 2017-07-18 11:24:41 -04:00
David Pacheco 38825e8b71 Add options (--ignore-gpu-blacklist and --enable-es3-apis) to allow for WebGl apps to work on legacy or unsupported graphics cards by Chrome (#410) 2017-07-13 12:23:07 -04:00
romo-dw ab435ee5a6 Add support for --disk-cache-size Electron flag (PR #400) 2017-07-05 09:07:31 -04:00
Ronan Jouchet f41c376761 Fix build broken in Node 8.x (#387)
See https://github.com/tjunnone/npm-check-updates/issues/355
2017-06-30 00:22:23 +08:00
Stefan Koshy 91505c90fe Fix #28 - Executable name being 'Electron' always under Windows (PR #389) 2017-06-18 19:13:35 -04:00
Jia Hao Goh 751eef1fd7 Update changelog for `v7.4.0` 2017-05-21 20:44:45 +08:00
Jia Hao Goh bc594e24e6 Add jq to docs as release dependency 2017-05-21 20:42:36 +08:00
Tobias Schneck 95cc30983b Run Nativefier with Docker (#311)
TODO: windows are currently not possible, because of non 64-bit version of `node-rcedit`, see https://github.com/electron/node-rcedit/issues/22. Also there is currently no 32-bit alpine package for wine, what possible solve this image.
2017-05-19 21:21:16 +08:00
Jia Hao f7881a246b Add hound config (#369)
Make hound use eslint
2017-05-19 21:03:02 +08:00
Jia Hao Goh f91b2ba43d Fix bug resolving promises
When a explicit argument is passed for `--icon` or `--user-agent`, the
promise chain will fail because we do not return a resolved promise.
2017-05-19 20:50:09 +08:00
Jia Hao Goh 0f0df27133 Add codeclimate config 2017-05-07 16:02:30 +08:00
Jia Hao Goh 1505933826 Promisfy and parallelise config, add unit tests
Instead of optionsMain exporting an async function, this commit changes
it to return a promise instead. We split all the needed async
helpers for this config builder into smaller promises, in `src/options/*`. Another side
effect of this is that we perform all our async config inferring in
parallel, which speeds up the nativefier CLI.

Add proper unit tests as well for all of these promises. Switch to
Jest for these unit tests, and we are temporarily running both Jest and
mocha together in `npm test`. To refactor all the Mocha code to use Jest in
a future commit.
2017-05-07 15:49:15 +08:00
Matt Harris 10eaa53b26 Add ARM build support (#360) 2017-05-06 15:11:51 -04:00
Alex Ryan fc4cfc51bd Add entry for hide-window-frame to Table of Contents in api.md (#357) 2017-05-03 07:33:22 -04:00
Jia Hao Goh 68b0c0ac27 Only release to NPM on one job 2017-04-30 03:10:01 +08:00
Jia Hao Goh 18cf90809f Remove quotes around changelog version 2017-04-30 02:54:47 +08:00
Jia Hao Goh 561eadba54 Update changelog for `v7.3.1` 2017-04-30 02:51:18 +08:00
Jia Hao Goh a6d8ff8d6a Add script to update version and changelog 2017-04-30 02:50:07 +08:00
Jia Hao Goh 7a13892b3a Update changelog for 7.3.0 2017-04-30 00:54:15 +08:00
Jia Hao Goh 20fc09799b Remove Windows tests
We don't want to install wine on travis, as it slows down the whole CI
process.

We also shouldn't be testing if we can build the electron app,
and instead our unit tests should test that we pass the correct
parameters to electron packager.

TODO ^
2017-04-30 00:41:15 +08:00
Jia Hao Goh 28e1e9ee40 Cleanup travis config 2017-04-30 00:39:59 +08:00
Jia Hao Goh 8f78dd03af Update eslint and use Airbnb style
- Add `npm run lint:fix` command
- Cleanup inferIcon.js logic slightly
2017-04-29 22:52:12 +08:00
Darren Haken 461c7a38f0 Change Mocha to not need a babel build to run (#349)
* Change Mocha to not need a babel build to run

- Also add tests around normalizeUrl

* PR 359 Apply changes due to comments

- Remove babelrc as its in the package.json
- Change tdd npm task to use gulp
- Remove source map support file from import list for normalizeUrlSpec
- Change gulp tdd task to run mocha on first run
 359 Apply changes due to comments

 - Remove babelrc as its in the package.json
 - Change tdd npm task to use gulp
 - Remove source map support file from import list for normalizeUrlSpec
 - Change gulp tdd task to run mocha on first run
 359 Apply changes due to comments

 - Remove babelrc as its in the package.json
 - Change tdd npm task to use gulp
 - Remove source map support file from import list for normalizeUrlSpec
 - Change gulp tdd task to run mocha on first run
2017-04-25 16:04:57 +01:00
Jia Hao Goh b467ac7a51 Promisify inferTitle module
Also remove the request dependency, use Axios instead
2017-04-21 00:24:48 +08:00
Jia Hao Goh eeaa531083 Add autodeploy to NPM on tag
Removing the development branch, we want to use GitHub releases to
autodeploy our changes to NPM.
2017-04-20 10:10:40 +08:00
Jia Hao Goh 34f91c0a20 7.2.0 2017-04-20 01:33:18 +08:00
Jia Hao Goh 1706d2acf1 Update changelog for `v7.2.0` 2017-04-20 01:32:33 +08:00
Ronan Jouchet bbce1e88d4 Fix #253 - Better honor --zoom option. (#347)
* When zooming in/out, start from the options zoom, not 1 (don't jump)
* Add 'Zoom Reset' feature bound to Ctrl+0, with indicative label for non-100% zoom value
2017-04-19 07:47:54 -04:00
Ronan Jouchet be4b9a7436 Fix #327 - Update dependencies (except eslint), default to Electron 1.6.6 (#341) 2017-04-18 17:30:54 -04:00
Ronan Jouchet f4f74224de Fix #308 - Allow mDNS addresses (ending with 'local.') during URL validation (#346) 2017-04-18 17:30:00 -04:00
Ronan Jouchet a09ae9fe4e CLI help: clarify --inject supports both JS & CSS (#340) 2017-04-16 17:19:03 +01:00
Ronan Jouchet 25eada1fd5 Cleanup 7.1.0 changelog (#339)
Removing duplicate entries, separate new features as such, order by features>fixes>rest
2017-04-16 17:16:09 +01:00
Krzysztof Zbiciński da637ebf73 Add --single-instance switch (#323) 2017-04-09 22:02:49 -04:00
Roman Masyhar f633eca5ae Remove duplicate dependencies (#337) 2017-04-09 21:39:42 -04:00
Jia Hao Goh 36e34c95a5 Remove `$` from docs
This allows shell commands to be copy and pasted easily
2017-04-09 10:36:27 +08:00
Jia Hao Goh 1658db53a6 Document minimum macOS version in docs 2017-04-09 10:35:46 +08:00
Lucas Caton 7512701a50 Replace OSX with macOS in README (#331) 2017-04-07 22:29:38 -04:00
Roman Masyhar df585cbe59 Rename 'Open in default browser' contextMenu to 'Open with default browser' (#338)
`Open with` more popular and familiar rather than `Open in`
2017-04-07 22:28:35 -04:00
Jia Hao Goh a540326237 7.1.0 2017-04-07 01:13:58 +08:00
Jia Hao Goh dbc39a0d56 Update changelog for `v7.1.0` 2017-04-07 01:13:32 +08:00
Callum Macdonald e83ffcd481 Use absolute link to changelog 2017-02-12 23:39:44 +08:00
Pierre-Yves 5f848a57da Reorder confusing documentation for dev
https://github.com/jiahaog/nativefier/issues/290
https://github.com/jiahaog/nativefier/issues/286
2017-02-12 23:38:39 +08:00
Andrew Murray ef45732142 Fix typo in changelog (#269) 2016-10-09 19:28:50 +08:00
Goh Jia Hao 1286372f95 Add editorconfig to trim trailing whitespace 2016-10-09 19:05:43 +08:00
Goh Jia Hao c660f2a79a Make documentation for optional dependencies clearer in API docs for icon (#258)
Merge branch 'vrunjeti-patch-1' into development
2016-10-09 14:21:35 +08:00
Goh Jia Hao ac317a88ac Use relative link 2016-10-09 14:20:28 +08:00
Goh Jia Hao 0410f5ddfa Merge branch 'patch-1' of https://github.com/vrunjeti/nativefier into vrunjeti-patch-1 2016-10-09 14:18:37 +08:00
Goh Jia Hao 2892e09db0 Fix context menu actions broken on <a> elements containing nested markup (#263)
Merge branch 'ronjouch-patch-1' into development
2016-10-09 14:16:59 +08:00
Goh Jia Hao 2529153ca1 Use let instead of const 2016-10-09 14:16:44 +08:00
Goh Jia Hao d26d5b9bbe Merge branch 'patch-1' of https://github.com/ronjouch/nativefier into ronjouch-patch-1 2016-10-09 14:15:00 +08:00
Goh Jia Hao 026fb45d19 Fix counter notifications (#256)
- Merge branch 'thomsbg-development' into development
2016-10-09 14:03:58 +08:00
Goh Jia Hao b85bde4963 Merge branch 'development' of https://github.com/thomsbg/nativefier into thomsbg-development
# Conflicts:
#	app/src/main.js
2016-10-09 14:01:02 +08:00
Goh Jia Hao f63f2e9e2b Merge branch 'master' into development
- Pull request was accidentally merged into master
2016-10-09 13:52:50 +08:00
Goh Jia Hao c362108c73 Fix travis tests which require wine
Somehow now wine is not included on travis
2016-10-09 13:19:06 +08:00
follower b9974c55b7 Fix typo in documentation (#237) 2016-10-09 12:06:01 +08:00
Ronan Jouchet a42554fe1d Fix context menu actions broken on <a> elements containing nested markup
Test case: open nativefier on

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Test</title>
</head>
<body>
  <a href="https://google.com/">Google</a>
  <br>
  <a href="https://google.com/"><span>Google, in span</span></a>
</body>
</html>
```

* **Expected**: both links open in default browser
* **Actual under nativefier 7.0.1**: Nothing happens when clicking the second link in which the `<a>` contains a `<span>`
2016-09-29 14:10:28 -04:00
Varun Munjeti d9b3887dd1 Linked optional dependencies for generating icons
It was a bit unclear that we needed those dependencies, especially if just referring to the api docs and not the main readme.
2016-09-12 14:52:13 +08:00
Blake Thomson f3293502a7 Fix badge notifications 2016-09-07 17:44:59 -07:00
Lex Neva 9243f6689f add "copy link location" and "--internal-urls <regex>" features (#230)
* add arg to specify pattern for urls to be considered internal

* add 'copy link location' to context menu

* fix lint
2016-08-07 02:03:53 +08:00
Peng Chen 48f0872ee5 Remove all non-ascii characters or use default for app name (#217) 2016-07-12 14:21:42 +08:00
Mats Högberg a407b9ea52 Added a --zoom option for setting default zoom (#218)
* Added a --zoom option for setting initial zoom

* Added information about --zoom to documentation
2016-07-12 09:32:40 +08:00
Goh Jia Hao 707cc8b730 7.0.1 2016-06-16 16:30:57 +08:00
Goh Jia Hao c34aa5cf10 Update changelog for `7.0.1` 2016-06-16 16:30:47 +08:00
Jia Hao 612da8ce36 Fix/performance issues with FOUC (#214)
Fix/performance issues with FOUC #191
2016-06-16 15:28:36 +08:00
Goh Jia Hao 729b5d5749 Fix bug where convert script fails silently if dependency is not found 2016-05-28 19:22:32 +08:00
Goh Jia Hao 6fccbbfaa5 Use original eslint module for linting instead of gulp
- Workaround for google/eslint-config-google#10
2016-05-27 02:23:37 +08:00
Goh Jia Hao 2c4aa548b3 7.0.0 2016-05-27 00:23:07 +08:00
Goh Jia Hao 2e576f3bc0 Update changelog for `v7.0.0` 2016-05-27 00:22:59 +08:00
Goh Jia Hao 1865e97e1a Remove accidentally introduced spacing 2016-05-27 00:15:48 +08:00
Goh Jia Hao 785a3b54bf Update docs to reflect node support for >=4 only 2016-05-27 00:11:24 +08:00
Goh Jia Hao 0c588b7219 Allow gitcloud matching of icons for multiple words 2016-05-27 00:10:23 +08:00
Goh Jia Hao 4e7fe7fa55 Add dependency status 2016-05-26 23:08:32 +08:00
Goh Jia Hao e2f3b476b1 Update validator to v5.2.0 2016-05-26 23:07:23 +08:00
Goh Jia Hao d5f11003e3 Update shelljs to v0.7.0 2016-05-26 23:07:15 +08:00
Goh Jia Hao ffa217db04 Update cheerio to v0.20.0 2016-05-26 23:06:11 +08:00
Goh Jia Hao 5264a96e3e Update axios to v0.11.1 2016-05-26 23:05:56 +08:00
Goh Jia Hao 960f3362c9 Update eslint to v2.0.0 2016-05-26 23:05:44 +08:00
Goh Jia Hao f262252ade Implement downloading of files
- Fixes #185
2016-05-26 22:50:40 +08:00
Goh Jia Hao a70433959b Refactor to use ES6 imports for electron modules 2016-05-26 22:50:15 +08:00
Goh Jia Hao 5833347ad8 Fix parsing of title including more than the title tag
- Fixes #195
2016-05-26 18:16:02 +08:00
Goh Jia Hao 1de9eec356 Implement disabling of developer tools
- Partially addresses #194
2016-05-26 18:02:43 +08:00
Goh Jia Hao 537dc01fd1 Fix linting errors 2016-05-26 18:01:33 +08:00
Goh Jia Hao c12006ebea Add note on electron-packager workaround 2016-05-26 18:00:14 +08:00
Goh Jia Hao 6616fbbf53 Remove support for node v0.12 and iojs 2016-05-26 17:48:02 +08:00
Goh Jia Hao c3ae29a0ea Fix FOUC #159 2016-05-26 17:41:25 +08:00
Goh Jia Hao f9bf66caa5 Increase timeout for test
It takes some time for electron-packager to download electron
2016-05-26 17:12:23 +08:00
Goh Jia Hao 7945f7802e Implement min/max window width and height
Fixes #82
2016-05-26 17:11:51 +08:00
Goh Jia Hao fcab2a9a95 Update browserWindow options to follow new electron api
- Use camel casing instead
2016-05-26 16:52:26 +08:00
Goh Jia Hao 2d2f107c84 Update electron version to v1.1.3 2016-05-26 16:51:31 +08:00
Goh Jia Hao 286e5bf7d6 Use destructuring for ES6 import 2016-05-26 16:51:07 +08:00
Goh Jia Hao c91def53af Fix placeholder for electron app to google 2016-05-26 16:50:42 +08:00
Goh Jia Hao 60823a21bb Temporary workaround for unable to delete OSX folder
Problem occurs with electron packager v7.01, see electron-userland/electron-packager#375
2016-05-26 16:01:23 +08:00
Goh Jia Hao a3f9a0411c Remove deprecated electron api from webpack config 2016-05-14 17:28:43 +08:00
Goh Jia Hao 6fd275f063 Update default electron version to v1.0.2 2016-05-13 12:51:05 +08:00
Goh Jia Hao 1f0d170021 Update electron-packager to v7.0.1 2016-05-13 12:50:40 +08:00
Goh Jia Hao 67482ec981 6.14.0 2016-05-08 15:33:10 +08:00
Goh Jia Hao bfb7c091f0 Update changelog for `6.14.0` 2016-05-08 15:31:23 +08:00
Goh Jia Hao 80b10d690e Properly log errors with injected files 2016-05-08 15:23:41 +08:00
Jia Hao 9f3dfdc315 Merge pull request #202 from lexelby/fix-slowdown
Fix deteriorated responsiveness #191 and polluted console #172
2016-05-08 14:26:40 +08:00
Jia Hao 319589de7b Merge pull request #178 from matthewdias/development
make keeping os x apps running optional
2016-05-08 14:20:44 +08:00
Lex Neva 52f7ea511d Revert "Fix FOUC with inject CSS files"
This reverts commit eeb661b6cd.

This was causing CSS to pile up over and over each time javascript did an HTTP request, which resulted in massive slowdown over time.
2016-05-06 09:22:43 -04:00
matthewdias 49ab72352e merge upstream, reverse default 2016-04-25 17:09:01 -05:00
Jia Hao ece19efba2 Merge pull request #188 from priezz/patch-1
Add an option to hide window frame and controls
2016-04-21 23:41:11 +08:00
Alexandr Priezzhev f5d6980bfe Merge with upstream/development 2016-04-17 23:33:08 +02:00
Jia Hao 810d282ede Merge pull request #187 from janjongboom/disable_context_menu
Add an option to disable the context menu
2016-04-17 12:55:01 +08:00
Jia Hao f520626d33 Merge pull request #181 from huyhong/development
Rebind 'Copy Current URL' to 'CmdOrCtrl+L' to mimic 'Open Location'
2016-04-17 12:52:54 +08:00
Alexandr Priezzhev f7240fb748 Update mainWindow.js 2016-04-17 01:32:52 +02:00
Alexandr Priezzhev c1b7ef9a60 Add an option to hide window frame 2016-04-17 01:06:42 +02:00
Alexandr Priezzhev 511c5f784e Add an option to hide window frame 2016-04-17 01:05:17 +02:00
Alexandr Priezzhev fd67f17129 Add an option to hide window frame 2016-04-17 01:04:10 +02:00
Alexandr Priezzhev eb03d801e3 Add an option to hide window frame 2016-04-17 01:01:46 +02:00
Alexandr Priezzhev 2f769bdb82 Add an option to hide window frame 2016-04-17 00:56:40 +02:00
Jan Jongboom ccdef6d5de Add an option to disable the context menu 2016-04-16 16:07:09 +02:00
Huy Hong e6454ca7a0 Rebind 'Copy Current URL' to 'CmdOrCtrl+L' to mimic 'Open Location' in browsers 2016-04-08 14:10:24 -07:00
Jia Hao f388ef83c6 Fix typo 2016-03-27 20:43:16 +08:00
Jia Hao 2cd251739e Add walkthrough gif in readme
Use relative link for dock screenshot
2016-03-27 11:36:04 +08:00
Jia Hao 404bab30c3 No longer enable flash by default
Flash should be enabled with `--flash`, which will also enable the `--insecure` flag
2016-03-26 15:06:50 +08:00
Jia Hao 62a43c76ed Bump default electron version to 0.37.2
Uses chrome 49
2016-03-26 14:54:14 +08:00
Jia Hao a0df9eff88 6.13.0 2016-03-25 21:14:35 +08:00
Jia Hao ed80fe74de Update changelog for `v6.13.0` 2016-03-25 21:14:28 +08:00
Jia Hao 7ebec43dec Do not include `app/src` in packaged app 2016-03-25 21:11:12 +08:00
Jia Hao d020d5d659 Fix mainWindow state not managed properly
Need to call manage `mainWindowState.manage(mainWindow)` immediately instead of at the end of `createMainWindow()`, if not certain changes such as maximizing the main window before it has been registered will not be saved.
2016-03-25 21:06:59 +08:00
Jia Hao e7390b9e33 Implement setting of verbose log level 2016-03-25 20:50:52 +08:00
Jia Hao 7b10e16ddb Merge branch 'lostfictions-development' into development 2016-03-25 20:12:20 +08:00
Jia Hao 26cfb1a86f Add test for connection error 2016-03-25 20:10:56 +08:00
Jia Hao 94c9fdb424 Contain fallback user agent within inferUserAgent 2016-03-25 20:10:45 +08:00
Jia Hao 9a9a63c15a Use axios instead of request 2016-03-25 19:58:03 +08:00
Jia Hao d2ed86e62c Add test for infer user agent 2016-03-25 19:29:33 +08:00
Jia Hao d890078e6e Should throw error on invalid platform 2016-03-25 19:29:13 +08:00
Jia Hao f4f315bb5f Merge pull request #170 from BugiDev/feature/maximization
Added option for maximization of main window ( #133 )
2016-03-23 03:41:24 +08:00
Bogdan Begovic 39e2459aad Added option for maximization of main window ( #133 ) - Fixed linting errors 2016-03-22 18:16:34 +01:00
Bogdan Begovic 7351c61664 Added option for maximization of main window ( #133 ) 2016-03-22 18:09:21 +01:00
sa 4b6716efef Infer user agent from Electron's online version list 2016-03-19 21:14:41 -04:00
Jia Hao 266a239104 Merge pull request #162 from iammarkoradak/development
Fix #159 FOUC with injected CSS files
2016-03-17 00:13:41 +08:00
Marko Radak eeb661b6cd Fix FOUC with inject CSS files 2016-03-14 07:39:41 +01:00
Jia Hao aeddcea4cb No need to run CI test for gulp release
CI tests should be run before starting the release process
2016-03-14 12:44:30 +08:00
Jia Hao ffbe3fe93a Change release docs for easier copy pasting 2016-03-14 12:43:44 +08:00
Jia Hao d1e3d30935 6.12.1 2016-03-14 12:41:22 +08:00
Jia Hao c5d4a4cc33 Update changelog for `v6.12.1` 2016-03-14 12:41:12 +08:00
Jia Hao a8072d2234 Hopefully fix icon retrieval bugs
Icon ext from page-icon contains `.` before ext now, e.g. `.png`
2016-03-14 12:28:14 +08:00
Jia Hao 9dc47704ab Prevent ico too large error 2016-03-14 12:27:19 +08:00
Jia Hao f28ac6baef Cleanup 2016-03-14 00:51:58 +08:00
Jia Hao 826ba0d779 6.12.0 2016-03-14 00:43:59 +08:00
Jia Hao ac4cbc32db Update changelog for `v6.12.0` 2016-03-14 00:43:45 +08:00
Jia Hao 08575ca75f Put changelog in `./docs` 2016-03-14 00:35:46 +08:00
Jia Hao 09e5d3b7c7 Use separate progress bar 2016-03-14 00:27:13 +08:00
Jia Hao 013e988391 Override logs for electron-packager properly 2016-03-13 20:12:45 +08:00
Jia Hao 517cfd7df7 Add link to api docs 2016-03-13 15:31:15 +08:00
Jia Hao 67dc46adf5 Split docs into multiple files 2016-03-13 15:27:32 +08:00
Jia Hao 5bad1d08dd Use `windows` and `osx` to specify platform 2016-03-12 16:47:18 +08:00
Jia Hao cbff6ba50d Add note about nativefier-icons 2016-03-12 16:38:24 +08:00
Jia Hao e6dc4b8441 Override output directory by default 2016-03-12 16:07:21 +08:00
Jia Hao 7ad7c346ef Add progress bar 2016-03-12 15:46:07 +08:00
Jia Hao e1f2e662e9 Add check for correct icon format 2016-03-12 15:17:03 +08:00
Jia Hao 6eaf335ba5 Remove unnecessary `identify` dependency check 2016-03-12 15:03:13 +08:00
Jia Hao 1f517886bb Allow todo in comments 2016-03-12 15:02:49 +08:00
Jia Hao 7ba016bf50 Support only node 0.12 onwards 2016-03-12 02:00:00 +08:00
Jia Hao b17c31e188 Fix lint error 2016-03-12 01:42:53 +08:00
Jia Hao 136c599f92 Use polyfill to support node >0.10 2016-03-12 01:36:18 +08:00
Jia Hao 7783f99e12 Update babel
Attempt to fix error

```
Error: only one instance of babel-polyfill is allowed
```
for Nodejs<=4
2016-03-11 13:47:00 +08:00
Jia Hao 54b251ee1c Implement conversion to ico for windows target 2016-03-11 13:05:54 +08:00
Jia Hao 1862d7cafe Fix bug in wrong icon format 2016-03-11 12:56:55 +08:00
Jia Hao 4c708fac26 Try to use assets store for icons before infer
Also promisified inferIcon
2016-03-11 12:40:08 +08:00
Jia Hao b09021dc3a 6.11.0 2016-03-11 03:49:26 +08:00
Jia Hao c36afb71d9 Update changelog for `6.11.0` 2016-03-11 03:49:14 +08:00
Jia Hao a49decbae8 Add optional dependencies 2016-03-11 02:29:13 +08:00
Jia Hao dea22336d9 Update Readme for Windows dev setup 2016-03-11 02:27:49 +08:00
Jia Hao 3ceafcb2ee Remove singleIco bin 2016-03-11 02:11:28 +08:00
Jia Hao cedbb443af Do not try to make ico singular 2016-03-11 00:03:28 +08:00
Jia Hao 3a6ae0d660 Implement conversion to .png on linux
Refactored icon shell helpers to a single file with helper functions
2016-03-10 23:36:00 +08:00
Jia Hao d04cf0ad3a Properly fail if icon is not found by pageicon 2016-03-10 15:52:02 +08:00
Jia Hao 34a5a52a28 Rename 'pngToIcns to convertToIcns' 2016-03-10 15:50:45 +08:00
Jia Hao ca44ee28aa Use separate file to convert to png 2016-03-10 15:41:25 +08:00
Jia Hao dab00a80e6 Check if unix before doing singleIco 2016-03-10 13:09:10 +08:00
Jia Hao 79c7767bb0 Add note about wine 2016-03-10 13:03:40 +08:00
Jia Hao a576d4e217 Copy icon with correct extension for windows 2016-03-10 12:54:09 +08:00
Jia Hao b7aada7967 Fix bug in setting icon on windows for windows 2016-03-10 12:46:09 +08:00
Jia Hao c3374618d9 Prefer correct extension for page icon 2016-03-09 14:50:25 +08:00
Jia Hao a5e3c5c2f8 Add conversion of multiple ico to single ico 2016-03-09 14:45:08 +08:00
Jia Hao 38a15baac2 Make section on optional dependencies clearer 2016-03-09 14:24:00 +08:00
Jia Hao 459894cc2a Add check for imagemagick identify 2016-03-09 14:22:11 +08:00
Jia Hao ac2260c395 Remove non ascii chars from filename
Non-ascii characters cause weird wine errors when building for win32 on darwin

```
$ nativefier stackedit.io ~/Desktop -o -p win32
```

Stack trace:
```
fixme:heap:RtlSetHeapInformation 0x0 1 0x0 0 stub
Fatal error: Unable to load file
```
2016-03-09 13:51:22 +08:00
Jia Hao 34039baefd Trim whitespace from inferred page title 2016-03-09 00:04:54 +08:00
Jia Hao 9d1276353e Remove dependency on Sips
Also update documentation for icon conversion dependencies
2016-03-09 00:01:27 +08:00
Jia Hao f48ad3790b Allow `.ico` to be changed into an `.icns`
Update shell script to support `.ico` files that contain sequential images
2016-03-08 23:58:38 +08:00
Jia Hao 3f6f632d51 Fix inferred file name extension 2016-03-08 23:57:19 +08:00
Jia Hao 9a8c18d0a4 Cleanup 2016-03-08 20:55:52 +08:00
Jia Hao e2d762b721 Fix bug where icns conversion fails silently 2016-03-08 20:55:42 +08:00
Jia Hao c8d6cef815 Remove dependency on bestIcon api
Use page-icon instead
2016-03-08 20:29:04 +08:00
Jia Hao e0e5b16f0b DRY for building es6 with babel 2016-02-28 01:07:52 +08:00
Jia Hao 8ef3137d02 Change eslint to disallow unused variables 2016-02-28 00:49:54 +08:00
Jia Hao 99fcb60a44 Split gulpfile into multiple parts 2016-02-28 00:45:44 +08:00
Jia Hao 45bcccd17e 6.10.1 2016-02-26 03:14:34 +08:00
Jia Hao ca19307e53 Update changelog for `v6.10.1` 2016-02-26 03:14:14 +08:00
Jia Hao 0ed5bd8c7b Fix #117 ENOENT when infering flash 2016-02-26 03:13:11 +08:00
Jia Hao 866991d6a1 Fix typo in header 2016-02-25 19:09:08 +08:00
Jia Hao a4462b191d 6.10.0 2016-02-25 19:04:52 +08:00
Jia Hao 474eab4e89 Update changelog for `v6.10.0` 2016-02-25 19:04:30 +08:00
Jia Hao c4700fcb7b Fix bug in mocha where next task is executed before mocha callback 2016-02-25 19:01:56 +08:00
Jia Hao 31ee80f84f Implement command line flag to start app in full screen, resolves #109 2016-02-25 18:26:28 +08:00
Jia Hao 1c334a3d7d Fix bug when no injection scripts is passed 2016-02-25 16:51:23 +08:00
Jia Hao 9b3de78d37 Fix bug where css is always injected 2016-02-25 15:25:14 +08:00
Jia Hao 166aa8962e Add readme for injection 2016-02-25 15:09:26 +08:00
Jia Hao e1426b849a Implement injection of css 2016-02-25 14:56:32 +08:00
Jia Hao 4d49c01ff3 Implement injection of `.js` 2016-02-25 14:11:48 +08:00
Jia Hao 4351906a42 6.9.1 2016-02-25 12:28:08 +08:00
Jia Hao 76d65d8aa2 Update changelog for `v6.9.1` 2016-02-25 12:27:54 +08:00
Jia Hao 564d829c73 Do not npm ignore binaries 2016-02-25 12:27:10 +08:00
Jia Hao 961d711cd7 6.9.0 2016-02-25 12:22:55 +08:00
Jia Hao b395310e4e Update changelog for `v6.9.0` 2016-02-25 12:22:31 +08:00
Jia Hao 4c03e72137 Add note about flash needing testing 2016-02-25 12:12:23 +08:00
Jia Hao 6faba7da42 Refactor menu arguments into options 2016-02-25 10:37:06 +08:00
Jia Hao 875149bcf7 Merge pull request #149 from garymoon/development
Make app data folder consistent per URL and allow clearing of app data from menu
2016-02-25 09:54:10 +08:00
Gary Moon ce76d0be7f add mainWindow and options objects to createMenu args and refactor 2016-02-24 09:45:22 -05:00
Gary Moon 546bad7035 remove clear cache menu item 2016-02-24 09:44:39 -05:00
Gary Moon df6f958904 Fix linter errors 2016-02-23 19:14:11 -05:00
Gary Moon e4a4476e35 Added clear app data and clear cache options 2016-02-23 18:19:52 -05:00
Gary Moon 76fe17e39f make app data folder consistent per URL 2016-02-23 13:05:56 -05:00
Jia Hao 1b0351cd01 Change flag usage
- `--ignore-certificate` to ignore invalid certificate errors,
- `--insecure` to disable web security to allow mixed content
2016-02-23 21:31:47 +08:00
Jia Hao 2a31561fda Add documentation about flash and web security 2016-02-23 21:25:20 +08:00
Jia Hao 0c17f19631 Merge branch 'feature/flash' into development 2016-02-23 21:21:29 +08:00
Jia Hao e76d587c6d NPM ignore everything except compiled files 2016-02-23 21:20:26 +08:00
Jia Hao 9510014dd6 Put github files in `.github` 2016-02-23 21:18:58 +08:00
Jia Hao 7bfa42ef71 Merge branch 'development' into feature/flash
# Conflicts:
#	README.md
#	src/build/buildApp.js
#	src/cli.js
#	src/options/optionsMain.js
2016-02-23 21:13:20 +08:00
Jia Hao a6d9de4e01 Merge branch 'wiederkehr-feature/disable-web-security' into development 2016-02-23 21:09:25 +08:00
Jia Hao 6182432175 Merge branch 'feature/disable-web-security' of https://github.com/wiederkehr/nativefier into wiederkehr-feature/disable-web-security 2016-02-23 21:09:01 +08:00
Jia Hao ba071cf5a8 Fix escaped string 2016-02-23 17:21:09 +08:00
Jia Hao dab9a06774 Update documentation for flash 2016-02-23 17:13:51 +08:00
Jia Hao 14939800d9 Allow manual override of flash plugin path 2016-02-23 17:13:39 +08:00
Jia Hao 6ca4e794cd Implement rudimentary inferment of flash 2016-02-23 16:46:14 +08:00
Benjamin Wiederkehr dc51407ccb Added option to disable web security. 2016-02-22 21:33:33 +01:00
Benjamin Wiederkehr 481793b02f Promise to add insecure-content option. 2016-02-22 18:39:41 +01:00
Jia Hao 88bb589de8 Fix #146 Specifying `--electron-version` does not work 2016-02-22 02:46:42 +08:00
Jia Hao cf24858ba6 Ignore coverage file from lint 2016-02-20 10:39:59 +08:00
Jia Hao 6ddf84bee1 Refactor options to seperate folders 2016-02-20 10:39:51 +08:00
Jia Hao 4663b99050 Merge pull request #142 from jiahaog/master
Add version flag for readme

Former-commit-id: a6280c7234af246df0a13cad45b5870715f25a09
2016-02-19 12:32:10 +08:00
Jia Hao 738b7b9d5c Merge pull request #126 from jiahaog/revert-79-flash_support
Revert "flash support #2"

Former-commit-id: 4651171c4f9ea696149f289df4ac6fa125c5b690
2016-02-19 01:20:15 +08:00
Jia Hao 69a4d97ac2 Update example api usage for `require('nativefier').default`
Fixes #120

Former-commit-id: 6cf9926e3f7ff59639f4df58b9db77b99f33f82b
2016-02-19 00:54:43 +08:00
Jia Hao fdecb6b8ac Add issue template
Former-commit-id: 273ad3cd153b50ea2112454359a9d16398689397
2016-02-19 00:16:20 +08:00
Jia Hao fa347cc8b0 Revert "flash support #2"
Former-commit-id: 03d7d2df2cbc6f3075491e5dd2e6d48d759f4beb
2016-02-06 01:12:30 +08:00
Jia Hao add30d5a80 Merge pull request #79 from brunoqueiros/flash_support
flash support #2

Former-commit-id: b0672ef48c8431c7480b9b3185ed231c23179398
2016-02-06 01:06:35 +08:00
Jia Hao 165654da37 Remove sips dependency 2016-02-04 14:43:08 +08:00
Jia Hao 13e477c238 Hide token 2016-02-03 12:00:33 +08:00
Jia Hao 99278cfa49 Temporarily undo linux tray to fix other bugs 2016-02-02 23:15:07 +08:00
Jia Hao 362b48c411 Add codeclimate coverage 2016-02-01 15:23:29 +08:00
Jia Hao 6625446b01 Update readme 2016-01-31 22:02:35 +08:00
Jia Hao 81f83ec0ef Merge pull request #110 from zweicoder/feature/tray
Feature/tray
2016-01-31 21:58:47 +08:00
Jia Hao 29b5af94c3 Merge pull request #114 from superdump/square-and-curly
counter: Allow for [x] and {x} forms of notification count
2016-01-31 21:57:35 +08:00
Jia Hao e38b9a5689 Merge pull request #112 from taiyoslime/master
Update Readme to add usage of -V option
2016-01-31 21:57:04 +08:00
Robert Swain d386598770 counter: Allow for [x] and {x} forms of notification count 2016-01-30 16:42:08 +01:00
taiyoslime 4c21d3ac8e Update Readme to add usage of -V option 2016-01-30 18:16:21 +09:00
zweicoder 494aa06685 Satisfy Travis 2016-01-30 12:27:00 +08:00
zweicoder 97425f9ab5 Fix incorrect defaults for minimizeToTray option 2016-01-30 12:11:29 +08:00
zweicoder e54e108ce2 Remove log 2016-01-30 12:06:14 +08:00
zweicoder a910914aa4 Fix Window closing behavior 2016-01-30 12:03:40 +08:00
zweicoder 0c9d6312a4 Fix app not hiding and add quit option 2016-01-30 11:03:38 +08:00
zweicoder e184aa13ae Add tray icon with hardcoded default icon. 2016-01-30 11:03:38 +08:00
zweicoder 7273a63885 Refactorize hardcoded icon path to main.js for more general usage 2016-01-30 11:03:38 +08:00
Jia Hao 8041663d82 Merge branch 'krishkumar-development' into development 2016-01-30 02:13:21 +08:00
Jia Hao 52c518f4b7 Merge branch 'development' of https://github.com/krishkumar/nativefier into krishkumar-development
- #90 Add keyboard shortcuts for back, forward
2016-01-30 02:13:10 +08:00
Jia Hao de4fc27859 Merge branch 'development' of github.com:jiahaog/nativefier into development 2016-01-30 02:09:22 +08:00
Jia Hao 2b616e0e4a Add note about not putting spaces in user defined app name 2016-01-30 02:08:53 +08:00
Jia Hao b229fe9d9a Merge pull request #107 from zweicoder/fix/respect-user-choice
Respect user choice for naming
2016-01-30 02:05:52 +08:00
Jia Hao 60f1375c25 Do not print done statement if app already exist and `--overwrite is not passed` 2016-01-30 01:57:56 +08:00
Jia Hao f471fe240e Update readme 2016-01-30 01:57:30 +08:00
zweicoder 3d4faf6c77 Respect user choice for naming 2016-01-30 01:42:36 +08:00
Jia Hao 795d4ee632 Allow npm publish to log to stdout 2016-01-30 00:42:47 +08:00
Jia Hao 43d50cdaba 6.8.0 2016-01-30 00:40:38 +08:00
Jia Hao 54f7b07969 Update changelog for 2016-01-30 00:40:17 +08:00
Jia Hao 15a7304e1b Run npm prune before running tests, use shelljs instead of child process 2016-01-30 00:31:27 +08:00
Jia Hao 8f06c555e5 Don't modify the window title 2016-01-30 00:30:25 +08:00
Jia Hao a9ccaa5c10 Add note about `.png` to `.icns` conversion only supported on OSX 2016-01-29 22:09:33 +08:00
Jia Hao 421838d986 Update badge to reference development branch 2016-01-29 22:05:52 +08:00
Jia Hao b2e05c925b Use ES6 syntax for placeholder app 2016-01-29 22:04:41 +08:00
Jia Hao c8e650d061 Increase timeout for test case 2016-01-29 14:46:31 +08:00
Jia Hao 7417c7da0d Update readme 2016-01-29 14:46:09 +08:00
Jia Hao 0d27dd6ae0 Split buildApp to separate files 2016-01-29 14:26:35 +08:00
Jia Hao ce8e762ac9 Put files in separate directories 2016-01-29 14:09:36 +08:00
Jia Hao bf2d5a2929 Cleanup 2016-01-29 13:39:31 +08:00
Jia Hao 4bdc0d27d2 Cleanup build app code 2016-01-29 13:39:23 +08:00
Jia Hao a9c294b4b6 Fix wrong parameter for test options 2016-01-29 13:34:53 +08:00
Jia Hao 0abde3e865 Cleanup 2016-01-29 12:37:25 +08:00
Jia Hao 51acbaaa9c Cleanup options.js, rename `--app-name` to `--name` 2016-01-29 11:37:54 +08:00
Jia Hao 0d408b001d Fix #103 App name should not be capitalized 2016-01-29 00:20:32 +08:00
Jia Hao c852dc088f Use longer timeout for tests 2016-01-28 23:58:25 +08:00
Jia Hao e5f8155d24 Use DO instance for API 2016-01-28 23:56:29 +08:00
Jia Hao 43d336c00d Reduce besticon parameter to get the apple touch icon if possible 2016-01-28 23:44:57 +08:00
Jia Hao a4cee33080 Update readme for icons 2016-01-28 23:40:19 +08:00
Jia Hao 3eac549902 Remove electron prebuilt as a dev dependency to speed up ci builds 2016-01-28 23:23:36 +08:00
Jia Hao 6787aff19c Use besticon api to automatically retrieve icon 2016-01-28 23:15:46 +08:00
Jia Hao 30fd51fc85 Fix test for non darwin platforms 2016-01-28 23:10:15 +08:00
Jia Hao 288f80301c Implement check for wine before attempting to pass icon to electron pacakger 2016-01-28 23:02:42 +08:00
Jia Hao d6f6bfdfbf Cleanup and skip png conversion test if not OSX 2016-01-28 22:43:43 +08:00
Jia Hao 947a8c2a8b Add workaround to skip passing icon to electron packager if output platform is win32 2016-01-28 22:39:54 +08:00
Jia Hao e0bee65cfc Fix problem with async each and creation of temporary directories 2016-01-28 22:39:13 +08:00
Jia Hao df698a24ba Update gulpfile
- Build tests in `gulp build`
- Watch test files
- Clean test files as well
2016-01-28 22:36:34 +08:00
Jia Hao ecbb84146f Implement automatic retrieval of png 2016-01-28 21:13:57 +08:00
Jia Hao 63a539cd69 Update changelog for 2016-01-28 11:52:09 +08:00
Jia Hao c3f6707b74 6.7.0 2016-01-28 11:48:39 +08:00
Jia Hao 1174cbc28c Cleanup 2016-01-28 11:47:40 +08:00
Jia Hao 2bdb2ab753 Update readme for `.png` conversion 2016-01-28 11:38:58 +08:00
Jia Hao 5475c6e968 Add build step to infer and convert pngs to icns 2016-01-28 10:00:28 +08:00
Jia Hao 8eaa3a39e0 Allow using png to build for OSX if OS is OSX 2016-01-27 10:36:30 +08:00
Jia Hao 77b2d93fdd Remove extension from shell script 2016-01-27 10:25:46 +08:00
Jia Hao d38912ab36 Disable test for app name check after sanitizing app name on linux 2016-01-27 10:24:24 +08:00
Jia Hao 44efe76a47 Use manual compiling of mocha so that sourcemaps can be used 2016-01-27 10:24:02 +08:00
Jia Hao dbd660b78f Add script to convert png to icns on osx 2016-01-27 10:22:22 +08:00
krishkumar 6f98248a60 Add keyboard shortcut for back, forward 2016-01-26 12:25:02 +05:30
Jia Hao f4130f940f Convert app name capitalized camel case if building for linux to prevent dock problems 2016-01-26 14:44:19 +08:00
Jia Hao 0cb4098eb8 Remove logs 2016-01-26 14:43:33 +08:00
Jia Hao 6b0d10f9d4 Fix the icon parameter for linux and windows 2016-01-26 14:24:47 +08:00
Jia Hao d0cddfde43 Make Browserwindow always reference `app/icon.png` for the icon 2016-01-26 14:13:33 +08:00
Jia Hao f45098d543 Add development notes 2016-01-26 13:30:23 +08:00
Jia Hao 7296674473 6.6.2 2016-01-26 10:04:13 +08:00
Jia Hao a037b22a4d Update changelog for 2016-01-26 10:03:20 +08:00
Jia Hao 03440e9227 Fix #87, Fix #89 - Sanitize app name
- Appname that contains unsafe characters which will cause electron packager to hang, so we sanitize them before passing them to electron-packager
2016-01-26 09:51:39 +08:00
Jia Hao 56b7abacf8 Fix bug in console warning when not overwritting an existing executable 2016-01-26 00:37:00 +08:00
Jia Hao d74c368627 Add command line flag to make the packaged app ignore certificate errors, fixes #69 2016-01-25 23:42:28 +08:00
Bruno Queiros ea3945a836 flash support #2
Former-commit-id: b83223d4c9a9e6fb52e5f2f78fce144cbea3be3a
2016-01-25 10:44:10 -02:00
Jia Hao 78a624c23f Fix #32 Ability to copy and paste a URL 2016-01-25 18:34:44 +08:00
Jia Hao d8fb87ccb5 Implement support for clicking buttons so that users can control where links open as referenced in #73 2016-01-25 16:49:11 +08:00
Jia Hao 949fce5e3c Implement right click context menu for regular href links 2016-01-25 15:56:33 +08:00
Jia Hao dfd0f83b63 Allow es6 for `app/static/**/**.js` 2016-01-25 12:43:55 +08:00
Jia Hao 1625e0f351 Merge pull request #75 from radarhere/patch-1
Fixed typo
2016-01-25 10:38:02 +08:00
Jia Hao 2044bcfcf7 6.6.1 2016-01-25 10:33:55 +08:00
Jia Hao da55bd48a6 Update changlog for `v6.6.1` 2016-01-25 10:33:51 +08:00
Jia Hao 1eb2a8296e Remove unused file 2016-01-25 10:32:35 +08:00
Jia Hao a6df09fda5 Fix #76 where all placeholder app modules are treated as externals 2016-01-25 10:31:54 +08:00
Jia Hao a8d020f3e2 Add contributing 2016-01-25 09:53:09 +08:00
Andrew Murray c58ec15272 Fixed typo 2016-01-25 12:15:35 +11:00
Jia Hao e1aec8ae8f Update gulp release to also run lints 2016-01-25 09:14:49 +08:00
Jia Hao 37a74f7a2c 6.6.0 2016-01-25 09:12:13 +08:00
Jia Hao d88a28fe70 Update changelog for `v6.6.0` 2016-01-25 09:11:30 +08:00
Jia Hao ba0010840e Remove ci for `0.10` which causes problems with electron-prebuilt 2016-01-25 01:47:53 +08:00
Jia Hao c1c50bab9f Configure travis to run ci test 2016-01-25 01:33:55 +08:00
Jia Hao e3dc3ee175 Update dev dependencies and add ci scripts 2016-01-25 01:31:32 +08:00
Jia Hao 68a5f46798 Make main test try multiple platforms 2016-01-25 01:31:11 +08:00
Jia Hao 0fece8dfb7 Add globals for mocha linting and fix code style errors 2016-01-25 01:14:25 +08:00
Jia Hao cb07b13cad Add mocha basic test to check if nativefier.json is set properly 2016-01-25 01:10:28 +08:00
Jia Hao 0a90cb2ee7 Somehow electron packager returns appPath as an array instead of a string, this fixes it 2016-01-25 01:10:03 +08:00
Jia Hao b7e2fbf86e Add sourcemaps support for placeholder app 2016-01-24 21:20:29 +08:00
Jia Hao 00ddedf32d Fix invalid json 2016-01-24 21:17:56 +08:00
Jia Hao afbb5c2544 Add sourcemaps support 2016-01-24 21:07:22 +08:00
Jia Hao 3fa34dead5 Exposes buildApp as a programmatic api for npm, and also shifted the `main` function from cli.js to within `buildApp`
Merge branch 'zweicoder-expose-main'
2016-01-24 20:42:26 +08:00
Jia Hao ffc5450e00 Merge branch 'expose-main' of https://github.com/zweicoder/nativefier into zweicoder-expose-main
# Conflicts:
#	src/cli.js
2016-01-24 20:40:32 +08:00
Jia Hao 7d0b41ae1e Remove shorthand command for height and width to fix conflicts with `-h`. Closes #30, closes #64 and closes #67
Merge branch 'taiyoslime-master'
2016-01-24 15:34:13 +08:00
Jia Hao 303d439192 Merge branch 'master' of https://github.com/taiyoslime/nativefier into taiyoslime-master
# Conflicts:
#	src/cli.js
2016-01-24 15:33:57 +08:00
Jia Hao ba1a270ee4 Update README so that Windows users know that they can press `alt` to show the application menu 2016-01-24 15:31:35 +08:00
Jia Hao b83a34934f Merge branch 'fearenales-feature/show-menu-bar' 2016-01-24 15:24:48 +08:00
Jia Hao e86119ba96 Merge branch 'feature/show-menu-bar' of https://github.com/fearenales/nativefier into fearenales-feature/show-menu-bar
# Conflicts:
#	app/nativefier.json
#	app/src/main.js
#	src/buildApp.js
2016-01-24 15:24:27 +08:00
Jia Hao fbf956e7be Build files immediately after install so npm link does not cause problems 2016-01-24 15:19:48 +08:00
Jia Hao 231467c9f2 Add build status 2016-01-24 02:10:56 +08:00
Jia Hao 7c25e82c44 Add travis configuration 2016-01-24 02:05:03 +08:00
Jia Hao 82c29de231 Lint all files 2016-01-24 02:02:23 +08:00
zweicoder 9c19c144e7 Expose main instead of buildApp 2016-01-23 23:10:44 +08:00
taiyoslime c85696f218 Fixed -h option's conflict on cli 2016-01-24 00:02:44 +09:00
Jia Hao b7433dfc3b Close #62 Merge branch 'zweicoder-feature/window-state-manager' 2016-01-23 16:33:43 +08:00
Jia Hao 9b3fd029c1 Merge branch 'feature/window-state-manager' of https://github.com/zweicoder/nativefier into zweicoder-feature/window-state-manager
# Conflicts:
#	app/src/components/mainWindow/mainWindow.js
#	app/src/main.js
2016-01-23 16:29:36 +08:00
Jia Hao 312af963d9 Merge branch 'master' of github.com:jiahaog/nativefier 2016-01-23 16:21:41 +08:00
Jia Hao fd8fe2df77 Merge pull request #63 from zweicoder/fix/win-linux-icon
Add icon hotfix
2016-01-23 15:21:05 +07:00
Jia Hao 74c8ffbea6 Add script for release to npm 2016-01-23 16:01:58 +08:00
zweicoder f0d1adb6b0 Add icon hotfix 2016-01-23 15:37:45 +08:00
Jia Hao 01ae53c590 Remove prepublish script which messes up `npm install` locally 2016-01-23 15:27:42 +08:00
zweicoder c77dfe5f27 Remove log 2016-01-23 15:22:25 +08:00
zweicoder b6862d0c06 Merge branch 'upstream' into feature/window-state-manager 2016-01-23 15:20:21 +08:00
Jia Hao c143b3b461 Fix error with badge on non-osx systems
- Instead of checking every instance we use the setBadge, we define an empty function and only override it if it is OSX
2016-01-23 15:12:53 +08:00
zweicoder 26ce134de8 Fix obsession with Mac 2016-01-23 15:10:02 +08:00
zweicoder d818f6cfaa Remove prepublish from causing errors 2016-01-23 14:59:33 +08:00
zweicoder 1685407e7a Integrate window state manager with BrowserWindow 2016-01-23 14:52:13 +08:00
Jia Hao 5a04a9886e Fix error in gulpfile 2016-01-23 14:47:55 +08:00
Jia Hao 40da693cb5 Fix #59 Fullscreen goes to a black screen when clicking close 2016-01-23 14:47:32 +08:00
zweicoder 09777f38c8 Remove unused watchify import 2016-01-23 14:47:07 +08:00
zweicoder 9ac18e071e Implement Electron Window State Manager 2016-01-23 14:33:55 +08:00
Jia Hao aa384dc317 Set window title immediately when the window is created, fixes #54 2016-01-23 14:22:03 +08:00
Jia Hao c5a76ade59 Remove notes about back button 2016-01-23 14:16:01 +08:00
Jia Hao e0de7bf463 Implement navigating backward and forward from the application menu 2016-01-23 14:15:25 +08:00
Jia Hao eabe6de892 Update README for modified build 2016-01-23 14:04:52 +08:00
Jia Hao 264037a867 Merge branch 'refactor/splitcomponents'
- Merge in gulp dev envirnoment
- Zoom functionality
- Split app into multiple components
2016-01-23 13:43:33 +08:00
Jia Hao 07acbced3e Cleanup notification integration #360 2016-01-23 13:32:20 +08:00
Jia Hao bed4260f55 Merge pull request #60 from contra/master
add notification monkey-patch, closes #38
2016-01-23 12:15:34 +07:00
Jia Hao 3347f30c7e Add es6 support for app, update npm scripts for gulp and cleanup
- Remove random console.log statements
- Remove unused dependencies
2016-01-23 12:42:09 +08:00
Jia Hao 0a42a830e2 Implement watching of src files 2016-01-23 12:29:40 +08:00
Jia Hao ddd728bd81 Implement gulp webpack to build app 2016-01-23 11:47:28 +08:00
Jia Hao 9c0e65e89f Merge branch 'master' into refactor/splitcomponents 2016-01-23 10:22:22 +08:00
Jia Hao 91ea8c2a48 Update build script for static folder, will break windows 2016-01-23 10:10:35 +08:00
Jia Hao 90ee346914 Refactor main.js into separate files, and put static files such as preload and login.html into `app/src/static` 2016-01-23 10:09:47 +08:00
Jia Hao 0e68fd61dc Implement changing of zoom which fixes #17
- Take note that the keyboard shortcut for zoom in appears to be 'command + =', due to a bug in atom/electron#1507
2016-01-23 09:27:09 +08:00
Contra 9393f863e2 clean up 2016-01-22 10:48:04 -08:00
Contra f75382d635 add monkeypatching notification support 2016-01-22 10:44:42 -08:00
Jia Hao 55157afe9a Hotfix for windows mkdir -p 2016-01-22 23:28:36 +08:00
Jia Hao c4e8ab3086 Update changelog for `v6.5.6` 2016-01-22 23:28:18 +08:00
Jia Hao bd6fedc0ef Workaround for windows `mkdir -p`, fixes #57
- Add a placeholder to keep the `app/lib` directory
- Remove `mkdir -p` postinstall script
2016-01-22 23:27:12 +08:00
Felipe Arenales 329d82a18c Hiding menu bar by default 2016-01-22 12:47:56 -02:00
Jia Hao 4089cd36cb Add changelog to readme 2016-01-22 21:16:45 +08:00
Jia Hao df9ab8b3c7 Precompile ./app 2016-01-22 21:10:38 +08:00
Jia Hao 8946ff8ed5 Update changelog for `v6.5.5` 2016-01-22 21:10:04 +08:00
Jia Hao c89f6a877b Implement script to set up dev environment 2016-01-22 20:49:37 +08:00
Jia Hao 18387854fa Ignore screenshots 2016-01-22 20:49:15 +08:00
Jia Hao b2171a68f4 Merge branch 'feature/app-build' 2016-01-22 19:58:36 +08:00
Jia Hao 5c204eec77 Cleanup app files and directories
- All source files are now in `src` and will be compiled and copied over to `lib` on `npm run build-app`
- Add logging of `appArgs` in preload on startup
- Use node `path` module to resolve pathnames instead of concatenating strings together
2016-01-22 19:57:39 +08:00
Jia Hao c1f844f072 Fix invalid argument in build app
- Fix `--detect-globals` which was causing `__dirname` to be a constant
- Add `cp` of preload script from `src` to `lib`
2016-01-22 19:54:40 +08:00
Jia Hao 263d713177 6.5.4 2016-01-22 19:07:38 +08:00
Jia Hao 398d8fac4f Update changelog for v6.5.4 2016-01-22 19:07:16 +08:00
Jia Hao d7f04e8b65 Fix #46 Url is not defined 2016-01-22 19:05:42 +08:00
Jia Hao 2f67eaf99d Fix bug in invalid parameter for link in default browser 2016-01-22 18:04:02 +08:00
Jia Hao 1f44e93659 Use browserify to precompile app npm modules 2016-01-22 18:03:35 +08:00
Jia Hao f789a51dd2 Update Readme 2016-01-22 12:31:30 +08:00
Jia Hao a6e580aa0a Override user agent by default,
- Remove `--pretend` flag. Pass `--honest` instead to disable the fake user agent
2016-01-22 11:59:57 +08:00
Jia Hao 59e9f6e104 Implement counter which closes #33, thanks to @jfouchard 2016-01-22 11:35:05 +08:00
Jia Hao 4a8ab13622 Improve automatic retrieval of app name by faking a user agent to make the request 2016-01-22 10:47:53 +08:00
Jia Hao 12c85af1a2 6.5.3 2016-01-22 10:17:12 +08:00
Jia Hao dac28bf665 6.5.2 2016-01-22 10:17:06 +08:00
Jia Hao 7b7ebfde99 6.5.1 2016-01-22 09:45:49 +08:00
Jia Hao cee171c481 Hotfix to fix #44 2016-01-22 09:45:21 +08:00
Jia Hao 6f5b78dc09 Remove wurl node modules 2016-01-22 09:42:53 +08:00
Jia Hao a85c85f74d Merge pull request #26 from kmelia/master
Add the missing http://
2016-01-22 07:48:47 +07:00
Jia Hao c39e66646a Authentication support 2016-01-22 08:47:30 +08:00
Jia Hao 0177725dee Update changelog for `6.5.0` 2016-01-22 08:46:51 +08:00
Jia Hao 1c644fd642 Minor style changes 2016-01-22 03:26:01 +08:00
Jia Hao f877928354 Complete implementation of http authentication, fixes #19 2016-01-22 03:25:23 +08:00
Jia Hao 7ef855a297 Implement basic http authentication 2016-01-22 02:39:52 +08:00
Jia Hao 5cb20e9244 Implement authentication that requires a new window to be opened referenced in #19
- Issue https://github.com/jiahaog/nativefier/issues/19#issuecomment-173364234
- When a link that requests a new window is clicked, the app will check if the link is an external link. If so, the default desktop browser (Chrome, Safari etc.) will be opened. Otherwise, a new Electron BrowserWindow will be opened, which supports the use case of logging in to OAuth websites such as feedly.com
2016-01-22 02:05:50 +08:00
Jia Hao 1a79048d76 Remove unused html and css after refactor to use browserwindow url directly 2016-01-22 01:33:34 +08:00
Jia Hao 7a00fd8533 Remove use of `webview` tag and instead rely on browser window 2016-01-22 01:32:21 +08:00
Jia Hao 871c2c66aa Add script for easy rebuilding of an OAuth app 2016-01-22 00:06:28 +08:00
Maxime c75bfa9755 Add the missing http:// 2016-01-21 09:27:18 +01:00
Jia Hao 76975c18c9 6.4.0 2016-01-21 13:46:07 +08:00
Jia Hao 663c633938 Add changelog 2016-01-21 13:45:59 +08:00
Jia Hao e723d59f25 Make debug script automatically open the packaged app on OSX 2016-01-21 13:43:12 +08:00
Jia Hao e8a7c634cd Remove "About Electron" from app menu, add nativefier version to help, which fixes #18
Also refactor buildMenu function arguments
2016-01-21 13:42:27 +08:00
Jia Hao 82317a08d3 Implement `--pretend` flag to easily simulate user agent strings, fixes #11 2016-01-21 12:53:12 +08:00
Jia Hao 14a4846c1a Merge branch 'master' of github.com:jiahaog/nativefier 2016-01-21 12:27:44 +08:00
Jia Hao f6488149f0 Fix bug in error when response is undefined 2016-01-21 12:27:20 +08:00
Jia Hao c097fe767c Add helper scripts to debug easily 2016-01-21 12:26:05 +08:00
Jia Hao 5bceb97775 Hide app instead of exiting on OSX to fix #14 2016-01-21 12:25:33 +08:00
Jia Hao 877ee060f6 Update deprecated electron loadUrl usage
Remove crash reporter
Remove commented code
2016-01-21 11:30:37 +08:00
Jia Hao 16dab25321 Merge pull request #20 from mattchue/master
Allow intranet URLs
2016-01-21 08:33:08 +07:00
Jia Hao add0c8a19a Merge pull request #25 from PoziWorld/patch-1
Minor copy fixes
2016-01-21 08:18:55 +07:00
Jia Hao 673851202f Merge pull request #24 from himynameisdave/master
Fixes the issue with "/"'s in the page title
2016-01-21 08:17:33 +07:00
Jia Hao 7bfedc823b Make app resource folder contain a short id string, fix #21 2016-01-21 09:06:04 +08:00
PoziWorld f9e9c16d58 Minor copy fixes
an native desktop app => a native desktop app
can built => can be built
2016-01-20 14:55:40 -08:00
dlunny 64b4f8b2a5 Fixes the issue with "/"'s in the page title
This commit resolves an bug where the page title would cause a filepaths
issue due to it having a forward slash.

The code committed here uses a simple regular expression to find all
instances of forward slashes in the page title and replace them with an
empty string.

Resolves #22

For more on this, please see:
https://github.com/jiahaog/nativefier/issues/22
2016-01-20 16:54:54 -05:00
Jia Hao 762de5f1c8 Update documentation, no longer need to add the full url with the protocol
Fixes #13
2016-01-21 01:22:22 +08:00
Matt Prestlien 9ceb46cb89 Fix wrong bool
Small correction
2016-01-20 08:57:51 -08:00
Matt Prestlien a65bf351bc Allow intranet URLs
This could be a great way to package intranet applications into electron apps. Added `require_tld` validator config setting to allow those intranet locations without a TLD to be packaged.
2016-01-20 08:54:05 -08:00
Jia Hao 0786ae5449 Update readme 2016-01-19 22:02:01 +08:00
Jia Hao 981c176894 Hide the webview until it finishes loading 2016-01-19 21:55:50 +08:00
Jia Hao 5c91cf231b Allow urls without protocols 2016-01-19 21:33:08 +08:00
Jia Hao 256064213b Allow targetUrls without protocol 2016-01-19 21:32:55 +08:00
Jia Hao 364993bfcd Make app `package.json` name to be kebab cased for robustness 2016-01-19 21:19:09 +08:00
Jia Hao 96c99b21a4 Change name within package.json 2016-01-19 21:07:06 +08:00
Jia Hao 0897432062 Change `name` within `package.json` so that temporary files will not be shared across different app instances 2016-01-19 21:06:42 +08:00
Jia Hao cfd212bd94 Update debug script to ensure that `src` is built before packaging the app 2016-01-19 21:05:41 +08:00
Jia Hao eb0a1b47bc Add spinner 2016-01-19 20:51:48 +08:00
Jia Hao ded8397dd3 Add loading spinner 2016-01-19 20:51:26 +08:00
Jia Hao 8a7219eba8 Add npm script to facilitate debug 2016-01-19 20:51:01 +08:00
Jia Hao c8e2c17fb5 Add application menus 2016-01-19 20:35:10 +08:00
Jia Hao b4be8e37a6 Fix bug where `shell` is not imported, and add links to github repo 2016-01-19 20:33:25 +08:00
Jia Hao 9c9fe59ed7 Remove flag to show dev tools 2016-01-19 20:28:58 +08:00
Jia Hao c7e0242852 Implement opening of web view dev tools from menu 2016-01-19 20:26:19 +08:00
Jia Hao 19bfce4191 Add template `nativefier.json` for debugging use 2016-01-19 19:53:59 +08:00
Jia Hao d01780a613 Implement basic application menu with shortcuts which supersedes mousetrap 2016-01-19 19:53:10 +08:00
Jia Hao de98c5e402 Fix user agent 2016-01-19 18:33:59 +08:00
Jia Hao b9d0f70580 Dev tools should show for the `webview` instead of the main browser window 2016-01-19 18:24:24 +08:00
Jia Hao b5ed2277bd Use event `dom-ready` just like example in docs http://electron.atom.io/docs/v0.36.4/api/web-view-tag/ 2016-01-19 18:21:14 +08:00
Jia Hao 53383ebae7 Only attempt to change user agent if it is provided as an argument 2016-01-19 18:18:43 +08:00
Jia Hao 5adf7b9297 Fix #8 user agent issue
The user agent needs to be set for the `<webview>` instead of for the `mainWindow`
2016-01-19 18:06:20 +08:00
Jia Hao 8ad805d820 Make package.json valid 2016-01-19 18:04:49 +08:00
Jia Hao b17c2af5e6 Add keywords 2016-01-19 18:04:36 +08:00
Jia Hao eb4a948506 Remove unnecessary statement to delete temporary files as they are already cleared if the `keep` parameter is not passed to `tmp.dirSync` 2016-01-19 11:58:26 +08:00
Jia Hao 1c87305d39 6.2.0 2016-01-19 11:49:50 +08:00
Jia Hao d91f5839d3 Use quotations in examples 2016-01-19 11:48:08 +08:00
Jia Hao d2733c4cb0 Change name for config file to `nativefier.json` 2016-01-19 11:45:17 +08:00
Jia Hao 1f31d06a85 Implement command line flag to show developer tools 2016-01-19 11:42:52 +08:00
Jia Hao 48345eceb5 Implement setting of user agent to fix #8 2016-01-19 11:30:42 +08:00
Jia Hao fb4a99a872 Implement electron API changes for requiring module as per http://blog.atom.io/2015/11/17/electron-api-changes.html 2016-01-19 11:28:04 +08:00
Jia Hao 63df4f0283 MIT License 2016-01-19 11:25:54 +08:00
Jia Hao 73f56e0ebb Add new line 2016-01-19 11:23:52 +08:00
Jia Hao 6baf6f277a Implemented intelligent app name retrieval 2016-01-19 01:18:15 +08:00
Jia Hao 0b43db6109 Update Readme 2016-01-19 01:17:27 +08:00
Jia Hao e12d10042c Make help text clearer, add parsing of width and height 2016-01-19 01:17:12 +08:00
Jia Hao 549427c8ac Fix bug in default argument for targetUrl 2016-01-19 00:44:15 +08:00
Jia Hao 3b02889435 Implement scraping of the web page name for the app 2016-01-19 00:40:24 +08:00
Jia Hao a57d00f701 Add MIT License 2016-01-19 00:15:29 +08:00
Jia Hao 86b2e0f0ef Add `.npmignore` to ignore `src` 2016-01-19 00:01:58 +08:00
Jia Hao 5411e9c71a Validate url before building the app 2016-01-18 23:56:59 +08:00
Jia Hao 10c1a9c97a Remove logging and make help clearer 2016-01-18 23:56:41 +08:00
Jia Hao 7d359a99fa Use arrow function and remove console.log statement 2016-01-18 23:56:17 +08:00
Jia Hao 0fe67234f2 Implement cli 2016-01-18 23:38:52 +08:00
Jia Hao 36b5597063 Add prepublish script 2016-01-18 23:36:05 +08:00
Jia Hao 0eb2e83dc2 Cleanup code 2016-01-18 22:07:22 +08:00
Jia Hao 5379740dda Initial commit for working module only with api change 2016-01-18 21:45:18 +08:00
Jia Hao 5f20dca5cc Merge pull request #6 from starquake/patch-2
Fix error in usage example
2015-11-17 16:08:04 +08:00
Jan Visser 7a7a04d56a Fix error in usage example
Removed "--target" in example and add quotes to make sure the shell interpreter does not try to interpret certain characters like ? or & in the url
2015-11-06 14:53:28 +01:00
Jia Hao e987b6425f Merge pull request #4 from gfpacheco/badge-not-osx
Fix #3 - Prevent call to setBadge when not on OSX
2015-08-07 14:24:50 +08:00
Guilherme Pacheco 5adf9d3a5d Prevent call to setBadge when not on OSX 2015-08-06 00:34:32 -03:00
Jia Hao 054c7f9ad5 5.1.1 2015-07-06 13:48:34 +08:00
Jia Hao aefea56f3f Updated README 2015-07-06 13:44:49 +08:00
Jia Hao 0bca6ffce1 Added screenshot 2015-07-06 13:36:42 +08:00
Jia Hao fdb0750e47 5.1.0 2015-07-06 11:48:09 +08:00
Jia Hao 4e7312ea03 1.1.0 2015-07-06 11:46:50 +08:00
Jia Hao 619e8c60b9 Quit app when all windows are closed 2015-07-06 11:46:08 +08:00
Jia Hao d7859160eb Only set the title listener after the webview finishes loading 2015-07-06 11:45:55 +08:00
Jia Hao 4ddf10a108 Updated to support setting of window dimensions using the cli, and updated documentation 2015-07-06 10:31:09 +08:00
Jia Hao 3b815dd1ba Updated CLI and documentation 2015-07-06 10:12:30 +08:00
Jia Hao 80f1863b8b Implemented setting of badge configuration in the command line 2015-07-06 09:46:27 +08:00
Jia Hao 199b1423e5 Implemented mac app badge through listening for webview title changes 2015-07-06 09:38:04 +08:00
Jia Hao c1509e30d9 1.0.9 2015-07-05 20:47:48 +08:00
Jia Hao 46bb14ac2a Implemented opening of links in new window 2015-07-05 20:44:45 +08:00
Jia Hao 4b34cf1ffb Updated documentation 2015-07-05 20:44:18 +08:00
Jia Hao 247345f203 Implemented selectAll, copy, cut, paste, undo keyboard shortcuts 2015-07-05 20:11:34 +08:00
Jia Hao e9bd05aa34 1.0.8 2015-07-05 17:02:25 +08:00
Jia Hao 397567a4e7 Corrected yet another bug in creating a temporary directory 2015-07-05 17:02:09 +08:00
Jia Hao 594607080e Updated version and readme 2015-07-05 16:43:44 +08:00
Jia Hao 4495b26c92 Fixed enormous bug in not passing the app configuration properly 2015-07-05 16:43:32 +08:00
Jia Hao 4a621b14a7 1.0.6 2015-07-05 16:02:40 +08:00
Jia Hao 9f74b690a1 Corrected bug in file directory 2015-07-05 16:02:31 +08:00
Jia Hao 7e43248e11 Added keywords 2015-07-05 15:49:47 +08:00
Jia Hao d48fb6b84c 1.0.4 2015-07-05 15:24:58 +08:00
Jia Hao 2282245fec Added back the readme 2015-07-05 15:24:36 +08:00
Jia Hao 0d2f85cb0f 1.0.3 2015-07-05 15:21:30 +08:00
Jia Hao 0f7df66d36 deleted readme 2015-07-05 15:21:25 +08:00
Jia Hao ffab8e5a6b 1.0.2 2015-07-05 15:18:31 +08:00
Jia Hao afe7f26558 1.0.1 2015-07-05 15:12:10 +08:00
Jia Hao 140c444a96 Added documentation 2015-07-05 15:11:49 +08:00
Jia Hao debc395599 Validation of url should trigger after the check for the required arguments 2015-07-05 15:10:36 +08:00
Jia Hao 0939715017 Modified package.json 2015-07-05 14:46:06 +08:00
Jia Hao ff5545435a Changed usage command 2015-07-05 14:45:48 +08:00
Jia Hao d40e605e98 Reformattted code with tabs and colons 2015-07-05 14:17:40 +08:00
Jia Hao 8ddeac3443 Implemented validation of url 2015-07-05 14:16:07 +08:00
Jia Hao 104821b759 Initial commit 2015-07-05 14:08:13 +08:00
Jia Hao d33c1e5fa2 Modified gitignore 2015-07-05 14:02:36 +08:00
rameshv 189fd022d7 Update readme and usage.txt with command-line version-string examples
Resolves #89; further documents #63.
2015-07-02 20:26:42 -04:00
Max Ogden dcf1da5d1a 5.0.0 2015-07-01 13:04:55 -07:00
Max Ogden 042228fb5c update collaborators 2015-07-01 13:03:14 -07:00
=^._.^= 8d26a51213 Merge pull request #88 from kfranqueiro/refactor
Implement --all, refactor code, add tests
2015-07-01 12:54:34 -07:00
Kenneth G. Franqueiro a360f6c2ee Add more comprehensive tests
This implements the following cases:

* Basic runs for a specific version/platform/arch with default options
  (testing for existence of expected output files/folders)
* Runs for each target platform with `out` set
* Runs for each target platform with `asar` set
* Runs for each target platform with `prune` set
* Runs for each target platform with `ignore` set
  * This includes testing `ignore` including a path separator
  * This also includes testing that `ignore` entries are applied
    with the app folder trimmed
* Runs for each target platform with `overwrite` set / not set
* Runs with `all` set, with `arch` or `platform` each set to `all`,
  and with `platform` set to multiple platforms (comma-delimited or
  array)
* Tests specifically for the darwin target platform, to test
  `icon` and `sign`

The test logic is organized such that the version of Electron being
tested can be configured once centrally (in config.json), and the tests
will not run until after downloading any necessary electron versions
(to avoid timing out due to long downloads).

Resolves #77.
2015-06-28 16:57:59 -04:00
Kenneth G. Franqueiro a157f716a5 Implement multi-target options and refactor code
This adds support for --all, --platform=all, and --arch=all.

In order to accommodate outputting multiple directories for multiple
platforms and architectures, this also implements a new directory
structure under the output folder (distinguished by both platform and
arch).  This structure is applied even to OS X distributions, which
formerly were output directly to an .app folder.  This could be considered
a backwards-incompatible change.

One other backwards-incompatible change is the value that the packager
function passes to the callback, which is now always an array of paths,
rather than just a single path.

The behavior of the icon option has also been modified to use its
basename and apply .ico or .icns depending on platform, to make it
usable with --all and --platform=all.  This attempts to maximize
backwards compatibility, by allowing a full filename to be specified,
but replacing the filename's extension with what is appropriate for
each target platform.  Alternatively, the extension can now be omitted.

In the process of implementing this, it became evident that some things
were being done in 3 different places, and weren't always being done
consistently, so I've deduplicated everything I could.

This also includes a few other changes to improve stability for
multi-target runs, and other fixes:

* Avoid targeting darwin if the environment doesn't support symlinks,
  to avoid the process bailing out on Windows
* Implement --overwrite centrally in index.js such that it explicitly
  skips if an output directory already exists, for consistency with
  all target platforms and to avoid any possible errors that would halt
  operation during one target of a multi-target run
* Use ncp instead of mv to move to finalPath, which avoids flakiness
  I noticed when testing on Windows 8 especially with multi-target runs
* Simplify temp directory logic by using a nested structure, so there
  is only one top-level directory to clean up
* Reinstate fix from #55 which seems to have been clobbered by a later
  merge
* linux.createApp now resolves to the final output directory;
  it was formerly resolving to the executable path
* mac.createApp now replaces space with underscore in bundle IDs
* Only the platform modules that are needed are loaded
* The win32 module only loads rcedit if needed

This also fixes a couple of missing updates to docs (readme/usage).

This commit addresses the following issues:

* Resolves #40
* Resolves #38
* Resolves #70
* Works around #71
* Resolves #84 by reinstating #55
2015-06-28 16:57:56 -04:00
Max Ogden cff6ab50ee update readme 2015-06-27 20:08:47 -05:00
Max Ogden 714c02cc00 4.2.0 2015-06-27 20:07:42 -05:00
Max Ogden 2d128d8e99 fix testapp 2015-06-27 20:07:33 -05:00
Max Ogden 86edff69d8 add basic tests, remove unused folder, make linux out dir behave like other OSes 2015-06-27 19:48:35 -05:00
=^._.^= 5cc8a4b688 Merge pull request #82 from kfranqueiro/win32-ignore
Fix path separator replacement for ignore on Windows
2015-06-27 19:27:07 -05:00
=^._.^= b80a060716 Merge pull request #76 from kfranqueiro/rename-helper
Fix #65: Rename Helper application on Mac OS X
2015-06-27 19:26:42 -05:00
Max Ogden e01849fbf3 Merge branch 'tengyifei-master' 2015-06-27 19:26:07 -05:00
Max Ogden ec321d2593 merge 2015-06-27 19:25:56 -05:00
=^._.^= 5e0edab6fe Merge pull request #74 from malept/rename-across-volumes
Fix renaming folders across volumes (when packaging for OS X)
2015-06-27 19:24:30 -05:00
=^._.^= 59228f2214 Merge pull request #63 from Rameshv/versionedit
Version info added to the final executable using rcedit
2015-06-27 19:24:11 -05:00
Max Ogden 065114b785 Merge branch 'chentsulin-master' 2015-06-27 19:23:40 -05:00
Max Ogden e81627f11e merge 2015-06-27 19:23:33 -05:00
Yifei Teng f39ba8c8f2 Support --overwrite 2015-06-23 10:54:45 -07:00
Max Ogden fc98b0c8cb license file 2015-06-23 10:15:19 -07:00
Max Ogden 9da60ff319 bsd-2-clause 2015-06-23 10:15:19 -07:00
Kenneth G. Franqueiro 63db115257 Fix path separator replacement for ignore on Windows
This needs to be done when Windows is the host OS regardless of
the target platform, not vice versa.

Fixes #79.
2015-06-22 21:32:15 -04:00
Mark Lee 178b3d4cd3 Fix regression with the asar code
OSX has a special cased resources path.

Fixes #81.
2015-06-22 07:52:44 -07:00
Mark Lee e4ad46b2a8 Fix renaming folders across volumes on OS X
Fixes #60.
2015-06-21 14:44:58 -07:00
Kenneth G. Franqueiro dd2a5d4b68 Fix #65: Rename Helper application on Mac OS X
This fixes the helper process showing up in Activity Monitor as
"Electron Helper"; it will now show up as "<appname> Helper".
2015-06-17 23:50:52 -04:00
rameshv 947c2084aa skipping the rcedit when both icon & version-string hash is not
available
2015-06-17 16:51:31 +05:30
jden 554f3b7354 4.1.3 2015-06-16 10:40:40 -07:00
jden 5b1c5477f6 Merge pull request #69 from mmuehlberger/master
Add quotes around signing identity to allow spaces
2015-06-16 10:37:24 -07:00
Markus Mühlberger c1269f62c8 Add quotes around signing identity to allow spaces 2015-06-16 18:49:44 +02:00
rameshv d2a2f05476 version-string hash added to update the windows executable resource
using rcedit
2015-06-12 21:02:47 +05:30
Mark Lee 81340093ee Merge pull request #61 from malept/deduplicate-code
Deduplicate code + cleanup
2015-06-11 08:29:10 -07:00
Mark Lee 9bcb1a81d9 Rename variable which mirrors the function name 2015-06-09 20:38:09 -07:00
Mark Lee bdc8c46362 Move prune to common module 2015-06-09 19:50:52 -07:00
Mark Lee 16b2feb39f Move user ignore filter to common module 2015-06-09 19:26:55 -07:00
Mark Lee f3d17bceb2 Fix comment 2015-06-09 19:17:57 -07:00
Mark Lee 83dd1dcb34 Move asar code to a common location 2015-06-09 19:17:21 -07:00
C.T. Lin f8c64fe755 get rid of sourcedir in path for ignore filter matching 2015-06-03 17:02:33 +08:00
jden 5f3704cdf5 Merge pull request #50 from jasonhinkle/patch-1
update readme with example for ignoring multiple directories
2015-06-02 15:23:47 -07:00
jasonhinkle df06c346e8 Add example to readme for ignoring multiple folders 2015-06-02 17:06:37 -05:00
jden db9301a971 Merge pull request #56 from jden/contributing-platform
Update CONTRIBUTING.md 📖 😉
2015-06-02 12:49:56 -07:00
jden f751c9a91d Update CONTRIBUTING.md 📖 😉
typo
2015-06-01 15:10:37 -07:00
Sindre Sorhus ac8aea014e 4.1.2 2015-05-27 22:12:55 +02:00
Sindre Sorhus f14911cb6b fix incorrect option name in readme 2015-05-27 19:12:22 +02:00
jden dbe467ba78 Merge pull request #48 from jenslind/docs-improvements
Docs improvements
2015-05-27 10:01:15 -07:00
Jens Lind aed7968aed Fixed typos 2015-05-27 18:50:47 +02:00
Jens Lind 199283f251 Added API section to readme 2015-05-27 18:46:42 +02:00
Jens Lind eeb91ef31e Changed --ignore part in readme 2015-05-27 18:46:17 +02:00
Max Ogden 41dafb927a 4.1.1 2015-05-21 21:06:43 -07:00
=^._.^= 37e319281c Merge pull request #46 from zaggino/zaggino/fix-win
convert slashes on windows so unix-format ignores work
2015-05-21 21:06:19 -07:00
=^._.^= 9b6237afca Merge pull request #45 from zaggino/zaggino/fix
fix defaultIgnores to be proper reg-exps
2015-05-21 21:05:48 -07:00
=^._.^= 5ad54e7c5e Merge pull request #43 from jden/dereference-symlinks
always dereference symlinks - per #42
2015-05-21 21:05:43 -07:00
Martin Zagora 13897ff303 convert slashes on windows so unix-format ignores work 2015-05-22 11:25:41 +10:00
Martin Zagora 5c82a97f05 fix defaultIgnores to be proper reg-exps 2015-05-22 10:28:04 +10:00
jden aa5c6187e3 always dereference symlinks - per #42 2015-05-20 19:05:47 -07:00
Max Ogden 92c75b0326 4.1.0 2015-05-18 16:42:20 -07:00
Max Ogden ada31862a7 Merge pull request #31 from jenslind/master
Added --sign to mac
2015-05-18 16:42:06 -07:00
Max Ogden 7e711b97ae 4.0.3 2015-05-18 14:10:18 -07:00
Max Ogden a2f3c1c707 style 2015-05-18 14:10:10 -07:00
Max Ogden 3c4bcbfc41 Merge pull request #34 from jenslind/feature/default-ignores
Ignore this and related modules
2015-05-18 14:09:15 -07:00
Jens Lind 892b15fc30 Ignore this and related modules 2015-05-18 19:51:33 +02:00
Jens Lind 7f25fab225 Added --sign to readme 2015-05-12 20:37:35 +02:00
Jens Lind d4f4c78769 Added codesign 2015-05-12 20:32:37 +02:00
Max Ogden 1127084ce7 add contributing 2015-05-11 17:05:22 -07:00
Max Ogden 42e56eb862 update readme 2015-05-11 13:09:02 -07:00
Max Ogden 1c3fb6adfe 4.0.2 2015-05-11 12:11:45 -07:00
Max Ogden fcc29035c8 cleaner error printing 2015-05-11 12:11:40 -07:00
Max Ogden 923996f6ca return when erroring early 2015-05-11 12:10:26 -07:00
Max Ogden d32cf1034f Merge pull request #26 from Ivshti/patch-2
Validate the --arch argument
2015-05-11 12:08:18 -07:00
Max Ogden 2d96a80635 update collabs 2015-05-11 12:03:44 -07:00
Max Ogden f748b00593 Merge pull request #28 from sindresorhus/patch-1
add `grunt-electron` to the readme
2015-05-11 12:02:25 -07:00
Sindre Sorhus fb9c4dceab add `grunt-electron` to the readme
grunt-electron is pretty dumb, but without it you have to do some useless boilerplate, so why not.
2015-05-11 19:47:32 +02:00
Ivo Georgiev ca6af870d3 Validate the --arch argument 2015-05-11 18:22:10 +03:00
Max Ogden e1d0264e2d 4.0.1 2015-05-10 16:46:04 -07:00
Max Ogden be46561151 Merge pull request #24 from malept/ensure-outdir-exists
Ensure the out dir specified exists when building OSX app
2015-05-10 16:45:29 -07:00
Mark Lee 1e1a479b23 Ensure the out dir specified exists when building OSX app 2015-05-10 15:44:38 -07:00
Max Ogden ab5da23a70 4.0.0 2015-05-10 13:57:47 -07:00
Max Ogden 260a16509b breaking change. allow building apps for other platforms 2015-05-10 13:57:42 -07:00
Max Ogden f9fdb45d09 update collabs 2015-05-10 12:23:39 -07:00
Max Ogden 6f58d1a478 3.4.0 2015-05-10 12:22:58 -07:00
Max Ogden 8d237097c5 Merge pull request #20 from jden/windows-support
add windows support - #3
2015-05-10 12:09:53 -07:00
Max Ogden 7adc5b0189 update collabs 2015-05-06 12:54:12 -07:00
Max Ogden 864b9094d1 Merge pull request #22 from shama/docs/asar
Add asar to readme
2015-05-06 12:54:06 -07:00
Kyle Robinson Young 26742e5758 Add asar to readme 2015-05-06 12:48:33 -07:00
jden 00440bc685 fix javascript standard style issues 2015-05-06 12:19:03 -07:00
jden b126237994 add windows support #3 2015-05-06 12:11:03 -07:00
Mathias Buus ae9db07bdf 3.3.0 2015-05-06 15:46:51 +02:00
Mathias Buus 6e89629644 set asar and prune as boolean options. /cc #18 2015-05-06 15:46:47 +02:00
Mathias Buus ac3e2903ae Merge pull request #18 from shama/asar
Add asar support. Closes GH-2
2015-05-06 15:44:15 +02:00
Kyle Robinson Young a2abf5ed6c Add asar support. Closes GH-2 2015-05-05 21:34:51 -07:00
Max Ogden a930bebaf3 update readme 2015-05-05 10:01:42 -07:00
Max Ogden 3798796639 3.2.0 2015-05-05 10:00:04 -07:00
Max Ogden 9142ada24d update collabs 2015-05-05 10:00:00 -07:00
Max Ogden 6bf877a195 Merge pull request #17 from malept/linux-support
Basic Linux support
2015-05-05 09:48:07 -07:00
Mark Lee 6634913bb2 Add basic Linux support
Copies both the electron-prebuilt distribution into the app's toplevel
dist directory (by default) and the user's app into the correct subdirectory.
2015-05-05 07:43:09 -07:00
Mark Lee 17803f01d1 Put Mac-specific code in its own file, raise error if platform unsupported 2015-05-05 07:43:09 -07:00
Max Ogden 9309786bfe add collabs 2015-04-27 12:14:16 -07:00
Max Ogden d0ff305170 3.1.0 2015-04-27 12:13:50 -07:00
Max Ogden 90fa9b1bd0 Merge branch 'stefanbuck-set-version-number' 2015-04-27 12:13:26 -07:00
Max Ogden 2f2618f7a0 merge 2015-04-27 12:13:21 -07:00
Max Ogden a4e3209567 Merge branch 'stefanbuck-custom-icon' 2015-04-27 12:12:23 -07:00
Max Ogden 290e5c1e35 merge 2015-04-27 12:12:17 -07:00
Max Ogden eddae849d4 3.0.0 2015-04-27 12:09:26 -07:00
Max Ogden 198f379d41 rename to electron-packager 2015-04-27 12:09:24 -07:00
Max Ogden c5a0fb00bf update collabs 2015-04-26 12:43:45 -07:00
Stefan Buck 40f881a718 Add set version support 2015-04-24 15:48:09 +02:00
Stefan Buck be1c139b5a add --icon support 2015-04-24 14:17:30 +02:00
Mathias Buus a6cf85d0f6 2.1.1 2015-04-05 09:04:27 -07:00
Mathias Buus ff12e9ada1 support globally installed atom-shell 2015-04-05 09:04:24 -07:00
Mathias Buus 07d5710a74 2.1.0 2015-04-04 21:28:25 -07:00
Mathias Buus 46b0596eb8 fix protocol cli 2015-04-04 21:28:06 -07:00
Mathias Buus 7464ba56a3 Merge pull request #10 from maxogden/patch-1
add custom protocol handler support
2015-04-04 21:26:36 -07:00
Mathias Buus 18871061d5 add custom protocol handler support 2015-04-04 21:26:03 -07:00
Max Ogden 1d44672807 add devdep 2015-04-04 21:19:53 -07:00
Max Ogden 5cd01f1358 add collaborator 2015-04-04 21:13:39 -07:00
Max Ogden 7c9b95fa1b update install instructions. closes #8 2015-04-04 09:50:33 -07:00
Max Ogden 8f4bd7fe89 2.0.0 2015-04-04 09:48:28 -07:00
Max Ogden 23b084f1b6 update readme 2015-04-04 09:48:24 -07:00
Max Ogden 330e376372 dont download atom-shell -- use local version instead 2015-04-04 09:47:01 -07:00
Max Ogden 95c236dd25 1.2.0 2015-03-23 16:59:53 -07:00
Max Ogden cbc585393b hoist + reorganize code from #7, and use fs.rename instead of spawn mv 2015-03-23 16:59:46 -07:00
Max Ogden 24f0476eeb Merge pull request #7 from maxogden/npm-prune
Add prune option
2015-03-23 16:54:51 -07:00
Zach Bruggeman 90b7cbb334 Add prune option
Fixes GH-4
2015-03-23 14:58:28 -07:00
Max Ogden da320cc264 add collaborator 2015-03-22 20:45:52 -07:00
Max Ogden d254289a15 update readme 2015-03-22 20:28:52 -07:00
Max Ogden 6a7634dadb 1.1.0 2015-03-22 20:14:45 -07:00
Max Ogden b1e2e80dbb add --ignore support 2015-03-22 20:14:42 -07:00
Max Ogden 4c08efb6a1 gitignore 2015-03-22 19:55:57 -07:00
Max Ogden 6244861b74 update name 2015-03-22 19:55:15 -07:00
Max Ogden e67e458de8 update repo urls 2015-03-22 19:54:01 -07:00
Max Ogden ace7354392 1.0.0 2015-03-22 19:53:30 -07:00
Max Ogden cf7fe77eb6 first working version 2015-03-22 19:51:19 -07:00
111 changed files with 23294 additions and 0 deletions

56
.dockerignore Normal file
View File

@ -0,0 +1,56 @@
# git
.git*
# OSX
.DS_Store
# Node.js
# ignore compiled lib files
lib/*
app/lib/*
built-tests
dist
app/dist
# Docs
docs
*.md
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
app/node_modules
# IntelliJ project files
.idea
*.iml
out
gen
.vscode

27
.editorconfig Normal file
View File

@ -0,0 +1,27 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8
indent_style = space
indent_size = 2
# 2 space indentation
[*.{html,css,less,scss,yml,json}]
indent_style = space
indent_size = 2
# Tab indentation (no size specified)
[Makefile]
indent_style = tab
trim_trailing_whitespace = true

1
.env Normal file
View File

@ -0,0 +1 @@
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1

30
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,30 @@
# Contributing to Nativefier
## Issues
Please include the following in your new issue:
- Version of Nativefier (run `$ nativefier --version`)
- Version of Node.js (run `$ node --version`)
- Command line parameters
- OS and architecture you are running Nativefier from
- Stack trace from the error message (if any)
- Instructions to reproduce the issue
## Pull Requests
See [here](https://github.com/nativefier/nativefier/blob/master/HACKING.md) for instructions on how to set up a development environment.
We follow the [Airbnb Style Guide](https://github.com/airbnb/javascript), please make sure tests and lints pass when you submit your pull request.
The following commands might be helpful:
```bash
# Run specs only
npm run test
# Run linter only
npm run lint
```
Thank you so much for your contribution!

93
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@ -0,0 +1,93 @@
name: Bug Report
description: File a bug report
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report 🙂! Help us help you, **fill this form thoroughly**. An incomplete bug report is a useless bug report.
- type: checkboxes
id: homework
attributes:
label: Homework
options:
- label: I took the time to write a good, descriptive issue title
required: true
- label: I read `nativefier --help` and [API.md](https://github.com/nativefier/nativefier/blob/master/API.md).
required: true
- label: I checked [CATALOG.md](https://github.com/nativefier/nativefier/blob/master/CATALOG.md) for community suggestions & workarounds.
required: true
- label: I searched [existing issues, open & closed](https://github.com/nativefier/nativefier/issues?q=is%3Aissue). Yes, my bug is new.
required: true
- label: I'm running the [latest version](https://github.com/nativefier/nativefier/releases).
required: true
- type: input
id: nativefier-command
attributes:
label: Nativefier command
description: "Your ***full*** nativefier command, on a ***public*** site."
placeholder: nativefier --verbose --some-option https://mysite.com
validations:
required: true
- type: textarea
id: steps-to-repro
attributes:
label: Steps to reproduce
placeholder: |
1. I did this...
2. And then that...
3. Finally, I clicked here.
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
placeholder: What you expected to happen.
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual behavior
placeholder: What happened instead.
validations:
required: true
- type: textarea
id: debug-info
attributes:
label: Debug info
placeholder: |
- Logs of your full build command, with the `--verbose` flag. Put them in a ```code block``` !
- If the bug happens at app run time, the in-app DevTools console logs (open it with F12)
- Error messages, screenshots, screencasts, anything relevant!
validations:
required: false
- type: input
id: nativefier-version
attributes:
label: Nativefier version
placeholder: "nativefier --version"
validations:
required: true
- type: input
id: node-version
attributes:
label: Node.js version
placeholder: "node --version"
validations:
required: true
- type: input
id: npm-version
attributes:
label: npm version
placeholder: "npm --version"
validations:
required: true
- type: input
id: os
attributes:
label: OS
placeholder: "For example: Windows 10 build 1809"
validations:
required: true

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: false

45
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Feature request
description: Suggest an idea for Nativefier
labels: ["feature-request"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request 🙂! Help us help you, **fill this form thoroughly**. An incomplete feature request is a useless feature request.
- type: checkboxes
id: homework
attributes:
label: Homework
options:
- label: I took the time to write a good, descriptive issue title
required: true
- label: I read `nativefier --help` and [API.md](https://github.com/nativefier/nativefier/blob/master/API.md), no existing option fits my needs.
required: true
- label: I checked [CATALOG.md](https://github.com/nativefier/nativefier/blob/master/CATALOG.md) for community suggestions & workarounds.
required: true
- label: I searched [existing issues, open & closed](https://github.com/nativefier/nativefier/issues?q=is%3Aissue). Yes, my feature request is new.
required: true
- label: I'm running the [latest version](https://github.com/nativefier/nativefier/releases). Yes, the feature I'm requesting isn't in it.
required: true
validations:
required: true
- type: textarea
id: problem-statement
attributes:
label: Problem statement
description: A clear and concise description of what your feature would be.
placeholder: |
For example:
Nativefier should XYZ, ... details details details...
Existing option --something is not what I want, because ...
validations:
required: true
- type: textarea
id: motivation-and-context
attributes:
label: Motivation & context
placeholder: |
What makes you want this feature?
Where does it come from?
validations:
required: true

78
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: Question
description: Ask for help
labels: ["question"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report 🙂! Help us help you, **fill this form thoroughly**. A cryptic question is a question unlikely to be answered.
- type: checkboxes
id: homework
attributes:
label: Homework
options:
- label: I took the time to write a good, descriptive issue title
required: true
- label: I read `nativefier --help` and [API.md](https://github.com/nativefier/nativefier/blob/master/API.md).
required: true
- label: I checked [CATALOG.md](https://github.com/nativefier/nativefier/blob/master/CATALOG.md) for community suggestions & workarounds.
required: true
- label: I searched [existing issues, open & closed](https://github.com/nativefier/nativefier/issues?q=is%3Aissue). Yes, my question is new.
required: true
- label: I'm running the [latest version](https://github.com/nativefier/nativefier/releases).
required: true
validations:
required: false
- type: textarea
id: question
attributes:
label: Your question
description: Your question, expressed clearly and concisely.
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: "If you already have a Nativefier command you're struggling with, paste ***full*** nativefier command and its logs, with the ***`--verbose` flag***, on a ***public*** site:"
value: |
```
nativefier --verbose --some-option https://mysite.com
<paste your verbose build logs, if relevant to your question>
```
validations:
required: false
- type: textarea
id: debug-info
attributes:
label: Debug info
placeholder: |
Error messages, screenshots, screencasts, anything relevant!
- type: input
id: nativefier-version
attributes:
label: Nativefier version
placeholder: "nativefier --version"
validations:
required: true
- type: input
id: node-version
attributes:
label: Node.js version
placeholder: "node --version"
validations:
required: true
- type: input
id: npm-version
attributes:
label: npm version
placeholder: "npm --version"
validations:
required: true
- type: input
id: os
attributes:
label: OS
placeholder: "For example: Windows 10 build 1809"
validations:
required: true

BIN
.github/dock-screenshot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

79
.github/generate-changelog vendored Executable file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Updates the changelog and version in the package.json
# Will also create a commit with these changes locally
#
# Usage:
# ./.github/generate-changelog -- "7.0.0"
#
# Prerequisites:
# - On master branch
# - No uncommitted changes
#
# Dependencies:
# - git-extras: https://github.com/tj/git-extras/blob/master/Installation.md
# - jq: https://stedolan.github.io/jq/download/
set -eo pipefail
echo 'HEY YOU. Before you release, here is a report of outdated dependencies.'
echo ' - Red upgrades fulfill semver and do *not* need any action'
echo ' - Yellow upgrades *do* need looking at changelogs for breaking changes, and updating package.json'
echo
echo 'CLI:'
npm out || true
echo
echo 'App:'
cd app; npm out || true; cd ..
echo
echo 'Okay with this, or care to do/plan a few upgrades?'
echo 'Press any key to continue, or Ctrl+C to abort'
read -r
echo 'HEY YOU, again. Did you run the quick pre-release smoke test? ( npm run test:manual )'
echo 'Press any key to continue, or Ctrl+C to abort'
read -r
# Checks if we are on the master branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [[ "$BRANCH" != 'master' ]]; then
echo 'ERROR: not on master branch' >&2
exit 1;
fi
# Checks if there are uncommitted changes
git diff-index --quiet HEAD -- || (echo 'ERROR: there are uncommitted changes' >&2 && exit 1)
VERSION="$1"
# Validates the $VERSION
SEMVER_REGEX='^([0-9]+\.){2}([0-9]+)$'
if ! [[ $VERSION =~ $SEMVER_REGEX ]]; then
echo "ERROR: Version '$VERSION' is invalid " >&2
exit 1
fi
# 1. Update the version in the package.json
cat package.json | jq ".version = \"$VERSION\"" > package.json.tmp
mv package.json.tmp package.json # workaround for in-place jq editing
# 2. Compile new commits from CHANGELOG.md, and open it in your EDITOR for cleanup
git changelog CHANGELOG.md --tag "$VERSION"
# 3. Commit the changes
git add CHANGELOG.md
git add package.json
git commit -m "Update changelog for \`v$VERSION\`"
# 4. Create an annotated tag
git tag -a "v$VERSION" -m "v$VERSION"
# 5. List remaining work
echo
echo 'Please verify commit & tag look fine in Git, then:'
echo ' 1. Push: git push --follow-tags origin master'
echo ' 2. Create a GitHub Release at https://github.com/nativefier/nativefier/releases ,'
echo " using created tag v$VERSION and with title \"Nativefier v$VERSION\" (yes, with a \"v\")."
echo
echo 'GitHub Action "publish" will react on the new release, and publish it to npm.'
echo 'The new version will be visible on npm within a few minutes/hours.'

125
.github/manual-test vendored Executable file
View File

@ -0,0 +1,125 @@
#!/usr/bin/env bash
# Manual test to validate some hard-to-programmatically-test features work.
set -eo pipefail
missingDeps=false
if ! command -v mktemp > /dev/null; then echo "Missing mktemp"; missingDeps=true; fi
if ! command -v uname > /dev/null; then echo "Missing uname"; missingDeps=true; fi
if ! command -v node > /dev/null; then echo "Missing node"; missingDeps=true; fi
if [ "$missingDeps" = true ]; then exit 1; fi
function launch_app() {
printf '\n*** Running app\n'
if [ "$(uname -s)" = "Darwin" ]; then
open -a "$1/$2-darwin-x64/$2.app"
elif [ "$(uname -o)" = "Msys" ]; then
"$1/$2-win32-x64/$2.exe"
else
"$1/$2-linux-x64/$2"
fi
}
function do_cleanup() {
if [ -n "$1" ]; then
printf '\n***** Deleting test dir %s *****\n' "$1"
rm -rf "$1"
printf '\n'
fi
}
function request_feedback() {
printf '\nDid everything work as expected? [yN] '
read -r response
do_cleanup "$1"
if [ "$response" != 'y' ]; then
echo "Back to fixing"
exit 1
fi
echo "Yayyyyyyyyyyy"
}
printf "\n***** SMOKE TEST 1: Setting up test and building app... *****\n"
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
nativefier_dir="$script_dir/.."
pushd "$nativefier_dir"
tmp_dir=$(mktemp -d -t nativefier-manual-test-XXXXX)
name="nativefier-smoke-test-1"
resources_dir="$tmp_dir/resources"
mkdir "$resources_dir"
injected_css="$resources_dir/inject.css"
injected_js="$resources_dir/inject.js"
echo '* { background-color: blue; }' > "$injected_css"
echo 'alert("hello world from inject");' > "$injected_js"
node ./lib/cli.js 'https://npmjs.com/' \
--inject "$injected_css" \
--inject "$injected_js" \
--name "$name" \
"$tmp_dir"
printf '\n***** SMOKE TEST 1: Test checklist *****
- Context menu -> Open Link In New Window works
- MAC ONLY: Context menu -> Open Link In New Tab works
- Keyboard shortcuts: {back, forward, zoom in/out/zero} work
- Console: no Electron runtime deprecation warnings/error logged'
launch_app "$tmp_dir" "$name"
request_feedback "$tmp_dir"
# ------------------------------------------------------------------------------
printf '\n***** SMOKE TEST 2: Setting up test and building app... *****\n'
tmp_dir=$(mktemp -d -t nativefier-manual-test-tray-XXXXX)
name='nativefier-smoke-test-2'
node ./lib/cli.js 'https://google.com/' \
--name "$name" \
--tray \
"$tmp_dir"
printf '\n***** SMOKE TEST 2: Test checklist *****
- Should have an app with a tray icon
- Console: no Electron runtime deprecation warnings/error logged'
launch_app "$tmp_dir" "$name"
request_feedback "$tmp_dir"
# ------------------------------------------------------------------------------
printf '\n***** SMOKE TEST 3: Setting up test and building app... *****\n'
tmp_dir=$(mktemp -d -t nativefier-manual-test-start-in-tray-XXXXX)
name='nativefier-smoke-test-3'
node ./lib/cli.js 'https://google.com/' \
--name "$name" \
--tray start-in-tray \
"$tmp_dir"
printf '\n***** SMOKE TEST 3: Test checklist *****
- Should have an app that does not show a window initially,
but will have a tray icon that will show the window.
- Console: no Electron runtime deprecation warnings/error logged'
launch_app "$tmp_dir" "$name"
request_feedback "$tmp_dir"
# ------------------------------------------------------------------------------
printf '\n***** SMOKE TEST 4: Setting up test and building app... *****\n'
tmp_dir=$(mktemp -d -t nativefier-manual-test-get-media-devices)
name='nativefier-smoke-test-4'
node ./lib/cli.js 'https://meet.jit.si/nativefier-test' \
--name "$name" \
"$tmp_dir"
printf '\n***** SMOKE TEST 4: Test checklist *****
- Join the Jitsi meeting and try to share your screen
(third button from the left in the bottom bar)
- An overlay should appear where you can select a screen/window to share
This presently does not work in MacOS as you would have to give the app
"Screen Recording" permissions, but you can''t for an app in the temp directory.
- After selecting a screen, a thumbnail of the shared screen should appear on
the top right
- Console: no Electron runtime deprecation warnings/error logged'
launch_app "$tmp_dir" "$name"
request_feedback "$tmp_dir"

BIN
.github/nativefier-walkthrough.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

81
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,81 @@
name: ci
on:
push:
branches:
- master
pull_request:
branches:
- master
# - Bumping the *minimum* required Node version? You must bump:
# 1. package.json -> engines.node
# 2. package.json -> devDependencies.@types/node
# 3. tsconfig.json -> {target, lib}
# 4. .github/workflows/ci.yml -> node-version
# - Bumping the *maximum* tested Node version? You must bump also: publish.yml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 20
uses: actions/setup-node@v2
with:
node-version: 20
cache: 'npm'
cache-dependency-path: |
npm-shrinkwrap.json
app/npm-shrinkwrap.json
package-lock.json
app/package-lock.json
- env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
run: npm ci --no-fund # Will also (via `prepare` hook): 1. install ./app, 2. build
- run: npm run lint
playwright:
runs-on: windows-latest # Doesn't work on headless ubuntu, and is slow on mac
steps:
- uses: actions/checkout@v2
- name: Use Node.js 20
uses: actions/setup-node@v2
with:
node-version: 20
cache: 'npm'
cache-dependency-path: |
npm-shrinkwrap.json
app/npm-shrinkwrap.json
package-lock.json
app/package-lock.json
- env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
run: npm ci --no-fund # Will also (via `prepare` hook): 1. install ./app, 2. build
- run: npm run test:playwright
timeout-minutes: 5
# Useful to debug PlayWright tests failing in CI
# env:
# DEBUG: pw:browser*
tests:
strategy:
matrix:
node-version:
- '20'
- '16' # the oldest we require in package.json -> engines.node, to check we run on this minimum
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: |
npm-shrinkwrap.json
app/npm-shrinkwrap.json
package-lock.json
app/package-lock.json
- env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
run: npm ci --no-fund # Will also (via `prepare` hook): 1. install ./app, 2. build
- run: npm run test:noplaywright

51
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: publish
on:
release:
types:
- created
jobs:
playwright:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2 # Setup .npmrc file to publish to npm
with:
node-version: '20' # Align the version of Node here with ci.yml.
registry-url: 'https://registry.npmjs.org'
- run: npm ci --no-fund # Will also (via `prepare` hook): 1. install ./app, 2. build
- run: npm run test:playwright
timeout-minutes: 5
build:
needs: playwright
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2 # Setup .npmrc file to publish to npm
with:
node-version: '20' # Align the version of Node here with ci.yml.
registry-url: 'https://registry.npmjs.org'
- run: npm ci --no-fund # Will also (via `prepare` hook): 1. install ./app, 2. build
- run: npm run test:noplaywright
- run: npm run lint
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
docker:
needs: [ playwright, build ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build . --file Dockerfile --tag "nativefier/nativefier:latest"
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Tag and push Docker image
run: |
docker tag "nativefier/nativefier:latest" "nativefier/nativefier:${GITHUB_REF_NAME}"
docker push "nativefier/nativefier:latest"
docker push "nativefier/nativefier:${GITHUB_REF_NAME}"

67
.gitignore vendored Normal file
View File

@ -0,0 +1,67 @@
# OSX
.DS_Store
# ignore compiled lib files
lib*
app/lib/*
app/dist/*
built-tests
# commit a placeholder to keep the app/lib directory
app/inject
!app/inject/_placeholder
!app/lib/.placeholder
dist
package-lock.json
app/package-lock.json
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
# Python virtual environment in case it's created for the Castlabs code signing tool
venv
# IntelliJ project files
.idea
*.iml
.run
out
gen
# Builds when testing npm pack
nativefier*.tgz
.vscode
# https://github.com/nektos/act
.actrc
tsconfig.tsbuildinfo
scripts

23
.npmignore Normal file
View File

@ -0,0 +1,23 @@
/*
!lib/
!icon-scripts
!npm-shrinkwrap.json
.DS_Store
src/
*eslintrc.js
*eslintrc.yml
*tsconfig.tsbuildinfo
*tsconfig.json
*jestSetupFiles*
*-test.js
*-test.js.map
*.test.d.ts
*.test.js
*.test.js.map
app/*
!app/lib/
!app/inject/
!app/nativefier.json
!app/package.json
!app/npm-shrinkwrap.json
.vscode/

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
16

1303
API.md Normal file

File diff suppressed because it is too large Load Diff

300
CATALOG.md Normal file
View File

@ -0,0 +1,300 @@
# Build Commands Catalog
Below you'll find a list of build commands contributed by the Nativefier community. They are here as examples, to help you nativefy "complicated" apps that need a bit of elbow grease to work. We need your help to enrich it, as long as you follow these two guidelines:
1. Only add sites that require something special! No need to document here that `simplesite.com` works with a simple `nativefier simplesite.com` 🙂.
2. Please add commands with the _strict necessary_ to make an app work. For example,
- Yes to mention that `--widevine` or some `--browserwindow-options` are necessary...
- ... but don't add other flags that are pure personal preference (e.g. `--disable-dev-tools` or `--disk-cache-size`).
---
## General recipes
### Videos dont play
Some sites like [HBO Max](https://github.com/nativefier/nativefier/issues/1153) and [Udemy](https://github.com/nativefier/nativefier/issues/1147) host videos using [DRM](https://en.wikipedia.org/wiki/Digital_rights_management).
For those, try passing the [`--widevine`](API.md#widevine) option.
### Settings cached between app rebuilds
You might be surprised to see settings persist after rebuilding your app.
This occurs because the app cache lives separately from the app.
Try deleting your app's cache, found at `<your_app_name_lower_case>-nativefier-<random_id>` in your OSs "App Data" directory (Linux: `$XDG_CONFIG_HOME` or `~/.config` , MacOS: `~/Library/Application Support/` , Windows: `%APPDATA%` or `C:\Users\yourprofile\AppData\Roaming`)
### Window size and position
This allows the last set window size and position to be remembered and applied
after your app is restarted. Note: PR welcome for a built-in fix for that :) .
```sh
nativefier 'https://open.google.com/'
--inject window.js
```
Note: [Inject](https://github.com/nativefier/nativefier/blob/master/API.md#inject)
the following javascript as `windows.js` to prevent the window size and position to reset.
```javascript
function storeWindowPos() {
window.localStorage.setItem('windowX', window.screenX);
window.localStorage.setItem('windowY', window.screenY);
}
window.moveTo(window.localStorage.getItem('windowX'), window.localStorage.getItem('windowY'));
setInterval(storeWindowPos, 250);
```
---
## Site-specific recipes
### Google apps
Lying about the User Agent is required, else Google Login will notice your
"Chrome" isn't a real Chrome, and will: 1. Refuse login, 2. Break notifications.
This example documents Google Sheets, but is applicable to other Google apps,
e.g. Google Calendar, GMail, etc. If `firefox` doesnt work, try `safari` .
```sh
nativefier 'https://docs.google.com/spreadsheets' \
--user-agent firefox
```
### Outlook
```sh
nativefier 'https://outlook.office.com/mail'
--internal-urls '.*?(outlook.live.com|outlook.office365.com).*?'
--file-download-options '{"saveAs": true}'
--browserwindow-options '{"webPreferences": { "webviewTag": true, "nodeIntegration": true, "nodeIntegrationInSubFrames": true } }'
```
Note: `--browserwindow-options` is needed to allow pop-outs when creating/editing an email.
### Udemy
```sh
nativefier 'https://www.udemy.com/'
--internal-urls '.*?udemy.*?'
--file-download-options '{"saveAs": true}'
--widevine
```
Note: most videos will work, but to play some DRMed videos you must pass `--widevine` AND [sign the app](https://github.com/nativefier/nativefier/issues/1147#issuecomment-828750362).
### HBO Max
```sh
nativefier 'https://play.hbomax.com/'
--widevine
--enable-es3-apis
&& python -m castlabs_evs.vmp sign-pkg 'name_of_the_generated_hbo_app'
```
Note: as for Udemy, `--widevine` + [app signing](https://github.com/nativefier/nativefier/issues/1147#issuecomment-828750362) is necessary.
### WhatsApp
```sh
nativefier 'https://web.whatsapp.com/'
--inject whatsapp.js
```
With this `--inject` in `whatsapp.js` (and maybe more, see [#1112](https://github.com/nativefier/nativefier/issues/1112)):
```javascript
if ('serviceWorker' in navigator) {
caches.keys().then(function (cacheNames) {
cacheNames.forEach(function (cacheName) {
caches.delete(cacheName);
});
});
}
```
Another option to see WhatsApp or WhatsApp Business more macOS-like (macos only):
```sh
nativefier https://web.whatsapp.com --name 'WhatsApp Business' --counter true --darwin-dark-mode-support true --title-bar-style hidden --inject whatsappmacos.css
```
with this `whatsappmacos.css` to make the window draggable, and move the user avatar to the right:
```css
header > div:first-child {
flex: 0 0 auto;
margin-right: 15px;
}
div#app > div.os-mac > span:first-child {
position: fixed;
top: 0;
z-index: 1000;
width: 100%;
height: 59px;
pointer-events: none;
-webkit-app-region: drag;
}
```
### Spotify
```sh
nativefier 'https://open.spotify.com/'
--widevine
--inject spotify.js
--inject spotify.css
```
Notes:
- You might have to pass `--user-agent firefox` to circumvent Spotify's detection that your browser isn't a real Chrome. But [maybe not](https://github.com/nativefier/nativefier/issues/1195#issuecomment-855003776).
- [Inject](https://github.com/nativefier/nativefier/blob/master/API.md#inject) the following javascript as `spotify.js` to prevent "Unsupported Browser" messages.
```javascript
function dontShowBrowserNoticePage() {
const browserNotice = document.getElementById('browser-support-notice');
console.log({ browserNotice });
if (browserNotice) {
// When Spotify displays the browser notice, it's not just the notice,
// but the entire page is focused on not allowing you to proceed.
// So in this case, we hide the body element (so nothing shows)
// until our JS deletes the service worker and reload (which will actually load the player)
document.getElementsByTagName('body')[0].style.display = 'none';
}
}
function reload() {
window.location.href = window.location.href;
}
function nukeWorkers() {
dontShowBrowserNoticePage();
if ('serviceWorker' in navigator) {
caches.keys().then(function (cacheNames) {
cacheNames.forEach(function (cacheName) {
console.debug('Deleting cache', cacheName);
caches.delete(cacheName);
});
});
navigator.serviceWorker.getRegistrations().then((registrations) => {
registrations.forEach((worker) =>
worker
.unregister()
.then((u) => {
console.debug('Unregistered worker', worker);
reload();
})
.catch((e) =>
console.error('Unable to unregister worker', error, { worker }),
),
);
});
}
}
document.addEventListener('DOMContentLoaded', () => {
nukeWorkers();
});
if (document.readyState === 'interactive') {
nukeWorkers();
}
```
- It is also required to [sign the app](https://github.com/nativefier/nativefier/blob/master/API.md#widevine), or many songs will not play.
- To hide all download links (as if you were in the actual app), [inject](https://github.com/nativefier/nativefier/blob/master/API.md#inject) the following CSS as `spotify.css`:
```css
a[href='/download'] {
display: none;
}
```
### Notion
You can use Notion pages with Nativefier without much hassle, but Notion itself does not present an easy way to use HTML buttons. As such, if you want to use Notion Pages as a quick way to make dashboards and interactive panels, you will be restricted to only plain links and standard components.
With Nativefier you can now extend Notion's functionality and possibilities by adding HTML buttons that can call other javascript functions, since it enables you to inject custom Javascript and CSS.
```sh
nativefier 'YOUR_NOTION_PAGE_SHARE_URL'
--inject notion.js
--inject notion.css
```
Notes:
- You can inject the notion.js and notion.css files by copying them to the resources/app/inject folder of your nativefier app.
- In your Notion page, use [notionbutton]BUTTON_TEXT|BUTTON_ACTION[/notionbutton], where BUTTON_TEXT is the text contained in your button and BUTTON_ACTION is the action which will be called in your JS function.
```javascript
/* notion.js */
// First, we replace all placeholders in our Notion page to add our interactive buttons to it.
window.onload =
setTimeout(function(){
let htmlCode = document.body.getElementsByTagName("*");
for (let i = 0; i <= htmlCode.length; i++) {
if(htmlCode[i] && htmlCode[i].innerHTML){
let match = htmlCode[i].innerHTML.match(/\[notionbutton\]([\s\S]*?)\[\/notionbutton\]/);
if (match && typeof match == 'object'){
let btnarray = match['1'].split("|");
let btn_text = btnarray[0];
let btn_action = btnarray[1];
htmlCode[i].innerHTML = htmlCode[i].innerHTML.replace(match['0'], "<button class=\"btn-notion\" btnaction=\"" + btn_action + "\" >"+btn_text+"</button>");
}
}
}
let buttons = document.querySelectorAll(".btn-notion");
for (let j=0; j <= buttons.length; j++){
if(buttons[j].hasAttribute("btnaction")){
buttons[j].onclick = function () { runAction(buttons[j].getAttribute("btnaction")) };
}
}
}, 3000);
// And then we define your action below, according to our needs
function runAction(action) {
switch(action){
case '1':
alert('Nice One!');
break;
default:
alert('Hello World!');
}
}
```
After that, set your css file as follows:
```css
.notion-topbar{ /* hiding notion's default navigation bar for a more "app" feeling */
display:none;
}
.btn-notion{ /* defining some style for our buttons */
background-color:#FFC300;
color: #333333;
}
.notion-selectable.notion-page-block.notion-collection-item span{
pointer-events: auto !important; /* notion prevents clicks on items inside databases. Use this to remove that. */
}
```
### Microsoft Teams
You can get an almost macOS look-alike using this:
```sh
nativefier https://teams.microsoft.com --name 'Microsoft Teams' --counter true --darwin-dark-mode-support true --title-bar-style hidden --internal-urls "(.*)" --inject teamsapp.css
```
Note that the `--internal-urls` argument is necessary to login.
Inject the following `teamsapp.css` file to hide the download button at the bottom left and the Office 365 apps waffle button at the top left:
```css
get-app-button.ts-sym.app-bar-link {
display: none;
}
button#ts-waffle-button {
display: none;
}
```

1205
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

55
Dockerfile Normal file
View File

@ -0,0 +1,55 @@
FROM --platform=linux/amd64 node:lts-alpine
LABEL description="Alpine image to build Nativefier apps"
# Install dependencies and cleanup extraneous files
RUN apk update \
&& apk add bash wine imagemagick dos2unix \
&& rm -rf /var/cache/apk/* \
&& mkdir /nativefier && chown node:node /nativefier
# Use node (1000) as default user not root
USER node
ENV NPM_PACKAGES="/home/node/npm-packages"
ENV PATH="$PATH:$NPM_PACKAGES/bin"
ENV MANPATH="$MANPATH:$NPM_PACKAGES/share/man"
# Setup a global packages location for "node" user so we can npm link
RUN mkdir $NPM_PACKAGES \
&& npm config set prefix $NPM_PACKAGES
WORKDIR /nativefier
# Add sources with node as the owner so that it has the power it needs to build in /nativefier
COPY --chown=node:node . .
# Fix line endings that may have gotten mangled in Windows
RUN find ./icon-scripts ./src ./app -type f -print0 | xargs -0 dos2unix
# Link (which will install and build)
# Run tests (to ensure we don't Docker build & publish broken stuff)
# Cleanup leftover files in this step to not waste Docker layer space
# Make sure nativefier is executable
RUN npm i \
&& npm link \
&& npm run test:noplaywright \
&& rm -rf /tmp/nativefier* ~/.npm/_cacache ~/.cache/electron \
&& chmod +x $NPM_PACKAGES/bin/nativefier
# Run a {lin,mac,win} build
# 1. to check installation was sucessful
# 2. to cache electron distributables and avoid downloads at runtime
# Also delete generated apps so they don't get added to the Docker layer
# !Important! The `rm -rf` command must be in the same `RUN` command (using an `&&`), to not waste Docker layer space
RUN nativefier https://github.com/nativefier/nativefier /tmp/nativefier \
&& nativefier -p osx https://github.com/nativefier/nativefier /tmp/nativefier \
&& nativefier -p windows https://github.com/nativefier/nativefier /tmp/nativefier \
&& rm -rf /tmp/nativefier
RUN echo Generated Electron cache size: $(du -sh ~/.cache/electron) \
&& echo Final image size: $(du -sh / 2>/dev/null)
ENTRYPOINT ["nativefier"]
CMD ["--help"]

255
HACKING.md Normal file
View File

@ -0,0 +1,255 @@
# Development Guide
Welcome, soon-to-be contributor 🙂! This document sums up
what you need to know to get started hacking on Nativefier.
## Guidelines
1. **Before starting work on a huge change, gauge the interest**
of community & maintainers through a GitHub issue. For big changes,
create a **[RFC](https://en.wikipedia.org/wiki/Request_for_Comments)**
issue to enable a good peer review.
2. Do your best to **avoid adding new Nativefier command-line options**.
If a new option is inevitable for what you want to do, sure,
but as much as possible try to see if you change works without.
Nativefier already has a ton of them, making it hard to use.
3. Do your best to **limit breaking changes**.
Only introduce breaking changes when necessary, when required by deps, or when
not breaking would be unreasonable. When you can, support the old thing forever.
For example, keep maintaining old flags; to "replace" an flag you want to replace
with a better version, you should keep honoring the old flag, and massage it
to pass parameters to the new flag, maybe using a wrapper/adapter.
Yes, our code will get a tiny bit uglier than it could have been with a hard
breaking change, but that would be to ignore our users.
Introducing breaking changes willy nilly is a comfort to us developers, but is
disrespectful to end users who must constantly bend to the flow of breaking changes
pushed by _all their software_ who think it's "just one breaking change".
See [Rich Hickey - Spec-ulation](https://www.youtube.com/watch?v=oyLBGkS5ICk).
4. **Avoid adding npm dependencies**. Each new dep is a complexity & security liability.
You might be thinking your extra dep is _"just a little extra dep"_, and maybe
you found one that is high-quality & dependency-less. Still, it's an extra dep,
and over the life of Nativefier we requested changes to _dozens_ of PRs to avoid
"just a little extra dep". Without this constant attention, Nativefier would be
more bloated, less stable for users, more annoying to maintainers. Now, don't go
rewriting zlib if you need a zlib dep, for sure use a dep. But if you can write a
little helper function saving us a dep for a mundane task, go for the helper :) .
Also, an in-tree helper will always be less complex than a dep, as inherently
more tailored to our use case, and less complexity is good.
5. Use **types**, avoid `any`, write **tests**.
6. **Document for users** in `API.md`
7. **Document for other devs** in comments, jsdoc, commits, PRs.
Say _why_ more than _what_, the _what_ is your code!
## Setup
First, clone the project:
```bash
git clone https://github.com/nativefier/nativefier.git
cd nativefier
```
Install dependencies (for both the CLI and the Electron app):
```bash
npm ci
```
The above `npm ci` will build automatically (through the `prepare` hook).
When you need to re-build Nativefier,
```bash
npm run build
```
Set up a symbolic link so that running `nativefier` calls your dev version with your changes:
```bash
npm link
which nativefier
# -> Should return a path, e.g. /home/youruser/.node_modules/lib/node_modules/nativefier
# If not, be sure your `npm_config_prefix` env var is set and in your `PATH`
```
After doing so, you can run Nativefier with your test parameters:
```bash
nativefier --your-awesome-new-flag 'https://your-test-site.com'
```
Then run your nativefier app _through the command line too_ (to see logs & errors):
```bash
# Under Linux
./your-test-site-linux-x64/your-test-site
# Under Windows
your-test-site-win32-x64/your-test-site.exe
# Under macOS
./YourTestSite-darwin-x64/YourTestSite.app/Contents/MacOS/YourTestSite --verbose
```
## Linting & formatting
Nativefier uses [Prettier](https://prettier.io/), which will shout at you for
not formatting code exactly like it expects. This guarantees a homogenous style,
but is painful to do manually. Do yourself a favor and install a
[Prettier plugin for your editor](https://prettier.io/docs/en/editors.html).
## Tests
- To run all tests, `npm t`
- To run only unit tests, `npm run test:unit`
- To run only integration tests, `npm run test:integration`
- Logging is suppressed by default in tests, to avoid polluting Jest output.
To get debug logs, `npm run test:withlog` or set the `LOGLEVEL` env. var.
- For a good live experience, open two terminal panes/tabs running code/tests watchers:
1. Run a TSC watcher: `npm run build:watch`
2. Run a Jest unit tests watcher: `npm run test:watch`
3. Here is [a screencast of how the live-reload experience should look like](https://user-images.githubusercontent.com/522085/120407694-abdf3f00-c31b-11eb-9ab5-a531a929adb9.mp4)
- Alternatively, you can run both test processes in the same terminal by running: `npm run watch`
## Maintainers corner
### Deps: major-upgrading Electron
When a new major [Electron release](https://github.com/electron/electron/releases) occurs,
1. Wait a few weeks to let it stabilize. Never upgrade Nativefier to a `.0.0`.
2. Thoroughly digest the new version's [breaking changes](https://www.electronjs.org/docs/breaking-changes)
(also via the [Releases page](https://github.com/electron/electron/releases) and [the blog](https://www.electronjs.org/blog/), the content is different),
grepping our codebase for every changed API.
- If called for by the breaking changes, perform the necessary API changes
3. Bump
- `src/constants.ts` / `DEFAULT_ELECTRON_VERSION` & `DEFAULT_CHROME_VERSION`
- `package.json / devDeps / electron`
- `app / package.json / devDeps / electron`
4. On Windows, macOS, Linux, test for regression and crashes:
1. With `npm test` and `npm run test:manual`
2. With extra manual testing
5. When confident enough, release it in a regression-spelunking-friendly way:
1. If `master` has unreleased commits, make a patch/minor release with them, but without the major Electron bump.
2. Commit your Electron major bump and release it as a major new Nativefier version. Help users identify the breaking change by using a bold **[BREAKING]** marker in `CHANGELOG.md` and in the GitHub release.
### Deps updates
It is important to stay afloat of dependencies upgrades.
In packages ecosystems like npm, there's only one way: forward.
The best time to do package upgrades is now / progressively, because:
1. Slacking on doing these upgrades means you stay behind, and it becomes
risky to do them. Upgrading a woefully out-of-date dep from 3.x to 9.x is
scarier than 3.x to 4.x, release, then 4.x to 5.x, release, etc... to 9.x.
2. Also, dependencies applying security patches to old major versions are rare
in npm. So, by slacking on upgrades, it becomes more and more probable that
we get impacted by a vulnerability. And when this happens, it then becomes
urgent & stressful to A. fix the vulnerability, B. do the required major upgrades.
So: do upgrade CLI & App deps regularly! Our release script will remind you about it.
### Deps lockfile / shrinkwrap
We do use lockfiles (`npm-shrinkwrap.json` & `app/npm-shrinkwrap.json`), for:
1. Security (avoiding supply chain attacks)
2. Reproducibility
3. Performance
It means you might have to update these lockfiles when adding a dependency.
`npm run relock` will help you with that.
Note: we do use `npm-shrinkwrap.json` rather than `package-lock.json` because
the latter is tailored to libraries, and is not publishable.
As [documented](https://docs.npmjs.com/cli/v6/configuring-npm/shrinkwrap-json),
CLI tools like Nativefier should use shrinkwrap.
### Release
While on `master`, with no uncommitted changes, run:
```bash
npm run changelog -- $VERSION
# With no 'v'. For example: npm run changelog -- '42.5.0'
```
Do follow semantic versioning, and give visibility to breaking changes
in release notes by prefixing their line with **[BREAKING]**.
### Triage
These are the guidelines we (try to) follow when triaging [issues](https://github.com/nativefier/nativefier/issues):
1. Do your best to conciliate **empathy & efficiency, and keep your cool**.
Its not always easy 😄😬😭🤬. Get away from triaging if you feel grouchy.
2. **Rename** issues. Most issues are badly named, with titles ranging from
unclear to flat out wrong. A good backlog is a backlog of issues with clear
concise titles, understandable with only the title after you read them once.
Rename and clarify.
3. **Ask for clarification & details** when needed, and add a `need-info` label.
1. In particular, if the issue isnt reproducible (e.g. a non-trivial bug
happening on an internal site), express that we cant work without a
repro scenario, and flag as `need-info`.
4. **Label** issues with _category/sorting_ labels (e.g. `mac` / `linux` / `windows`,
`bug` / `feature-request` ...) and _status_ labels (e.g. `upstream`, `wontfix`,
`need-info`, `cannot-reproduce`).
5. **Close if needed, but not too much**. We _do_ want to close what deserves it,
but closing _too_ ruthlessly frustrates and disappoints users, and does us a
disservice of not having a clear honest backlog available to us & users. So,
1. When in doubt, leave issues open and triaged as `bug` / `feature-request`.
Its okay, reaching 0 open issues is _not_ an objective. Or if it is,
it deserves to be a development objective, not a triage one.
2. That being said, do close whats `upstream`, with a kind message.
3. Also do close bugs that have been `need-info` or `cannot-reproduce` for
too long (weeks / months), with a kind message explaining were okay to
re-open if the requested info / scenario is provided.
4. Finally, carefully close issues we do not want to address, e.g. requests
going against project goals, or bugs & feature requests that are so niche
or far-fetched that theres zero chance of ever seeing them addressed.
But if in doubt, remain at point 1. above: leave open, renamed, labelled.
6. **Close duplicates issues** and link to the original issue.
1. To be able to notice dups implies you must know the backlog (one more
reason to keep it tidy and palatable). Once in a blue moon, do a
"full pass" of the whole backlog from beginning to end, youll often
find lots of now-irrelevant bugs, and duplicates.
7. **Use [GitHub saved replies](https://github.com/settings/replies)** to
automate asking for info and being nice on closing as noanswer / stale-needinfo.
8. **Transform findings stemming from issues discussion** into documentation
(chiefly, [CATALOG.md](CATALOG.md) & [API.md](API.md)), or into code comments.
9. **Dont scold authors of lame "+1" comments**, this only adds to the noise
youre trying to avoid. Instead, hide useless comments as `Off-topic`.
From personal experience, users do understand this signal, and such hidden
comments do avoid an avalanche of extra "+1" comments.
1. There are shades of lame. A literal `"+1"` comment is frankly useless and
is worth hiding. But a comment like `"same for me on Windows"` at least
brings an extra bit of information, so can remain visible.
2. In a perfect world, GitHub would let us add a note when hiding comments to
express _"Please use a 👍 reaction on the issue to vote for it instead of_
_posting a +1 comment"_. In a perfecter world, GitHub would use their AI
skillz to automatically detect such comments, discourage them and nudge
towards a 👍 reaction. Were not there yet, so “hidden as off-topic” will do.
10. **Dont let yourself be abused** by abrasive / entitled users. There are
plenty of articles documenting open-source burnout and trolls-induced misery.
Find an article that speaks to you, and point problematic users to it.
I like [Brett Cannon - The social contract of open source](https://snarky.ca/the-social-contract-of-open-source/).

10
LICENSE.md Normal file
View File

@ -0,0 +1,10 @@
The MIT License (MIT)
=====================
Copyright © `2016` `Goh Jia Hao`
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

101
README.md Normal file
View File

@ -0,0 +1,101 @@
Note: Nativefier is unmaintained, please see https://github.com/nativefier/nativefier/issues/1577.
# Nativefier
![Example of Nativefier app in the macOS dock](.github/dock-screenshot.png)
You want to make a native-looking wrapper for WhatsApp Web (or any web page).
```bash
nativefier 'web.whatsapp.com'
```
![Walkthrough animation](.github/nativefier-walkthrough.gif)
You're done.
## Introduction
Nativefier is a command-line tool to easily create a “desktop app” for any web site
with minimal fuss. Apps are wrapped by [Electron](https://www.electronjs.org/)
(which uses Chromium under the hood) in an OS executable (`.app`, `.exe`, etc)
usable on Windows, macOS and Linux.
I built this because I grew tired of having to Alt-Tab to my browser and then search
through numerous open tabs when using Messenger or
Whatsapp Web ([HN thread](https://news.ycombinator.com/item?id=10930718)). Nativefier features:
- Automatically retrieval of app icon / name
- Injection of custom JS & CSS
- Many more, see the [API docs](API.md) or `nativefier --help`
## Installation
Install Nativefier globally with `npm install -g nativefier` . Requirements:
- macOS 10.13+ / Windows / Linux
- [Node.js](https://nodejs.org/) ≥ 16.9 and npm ≥ 7.10
Optional dependencies:
- [ImageMagick](http://www.imagemagick.org/) or [GraphicsMagick](http://www.graphicsmagick.org/) to convert icons.
Be sure `convert` + `identify` or `gm` are in your `$PATH`.
- [Wine](https://www.winehq.org/) to build Windows apps from non-Windows platforms.
Be sure `wine` is in your `$PATH`.
<details>
<summary>Or install with Docker (click to expand)</summary>
- Pull the image from [Docker Hub](https://hub.docker.com/r/nativefier/nativefier): `docker pull nativefier/nativefier`
- ... or build it yourself: `docker build -t local/nativefier .`
(in this case, replace `nativefier/` in the below examples with `local/`)
By default, `nativefier --help` will be executed.
To build e.g. a Gmail app into `~/nativefier-apps`,
```bash
docker run --rm -v ~/nativefier-apps:/target/ nativefier/nativefier https://mail.google.com/ /target/
```
You can pass Nativefier flags, and mount volumes to pass local files. E.g. to use an icon,
```bash
docker run --rm -v ~/my-icons-folder/:/src -v $TARGET-PATH:/target nativefier/nativefier --icon /src/icon.png --name whatsApp -p linux -a x64 https://web.whatsapp.com/ /target/
```
</details>
<details>
<summary>Or install with Snap & AUR (click to expand)</summary>
These repos are *not* managed by Nativefier maintainers; use at your own risk.
If using them, for your security, please inspect the build script.
- [Snap](https://snapcraft.io/nativefier)
- [AUR](https://aur.archlinux.org/packages/nodejs-nativefier)
</details>
## Usage
To create an app for medium.com, simply `nativefier 'medium.com'`
Nativefier will try to determine the app name, and well as other options that you
can override. For example, to override the name, `nativefier --name 'My Medium App' 'medium.com'`
**Read the [API docs](API.md) or run `nativefier --help`**
to learn about command-line flags and configure your app.
## Troubleshooting
**See [CATALOG.md](CATALOG.md) for site-specific ideas & workarounds contributed by the community**.
If this doesnt help, go look at our [issue tracker](https://github.com/nativefier/nativefier/issues).
## Development
Help welcome on [bugs](https://github.com/nativefier/nativefier/issues?q=is%3Aopen+is%3Aissue+label%3Abug) and
[feature requests](https://github.com/nativefier/nativefier/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request)!
Docs: [Developer / build / hacking](HACKING.md), [API / flags](API.md),
[Changelog](CHANGELOG.md).
License: [MIT](LICENSE.md).

21
app/.eslintrc.js Normal file
View File

@ -0,0 +1,21 @@
const base = require('../base-eslintrc');
// # https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md
module.exports = {
parser: base.parser,
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
plugins: base.plugins,
extends: base.extends,
rules: base.rules,
// https://eslint.org/docs/user-guide/configuring/ignoring-code#ignorepatterns-in-config-files
ignorePatterns: [
'node_modules/**',
'lib/**',
'dist/**',
'built-tests/**',
'coverage/**',
],
};

0
app/inject/_placeholder Normal file
View File

8
app/nativefier.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "Google",
"targetUrl": "http://google.com",
"badge": false,
"width": 1280,
"height": 800,
"showMenuBar": false
}

1170
app/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

25
app/package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "nativefier-placeholder",
"version": "1.0.0",
"description": "Placeholder for the nativefier cli to override with a target url",
"main": "lib/main.js",
"author": "Jia Hao",
"license": "MIT",
"keywords": [
"desktop",
"electron",
"placeholder"
],
"scripts": {},
"dependencies": {
"electron-context-menu": "^3.6.1",
"electron-dl": "^3.5.0",
"electron-squirrel-startup": "^1.0.0",
"electron-window-state": "^5.0.3",
"loglevel": "^1.8.1",
"source-map-support": "^0.5.21"
},
"devDependencies": {
"electron": "^25.7.0"
}
}

View File

@ -0,0 +1,84 @@
import {
BrowserWindow,
ContextMenuParams,
Event as ElectronEvent,
} from 'electron';
import contextMenu from 'electron-context-menu';
import { nativeTabsSupported, openExternal } from '../helpers/helpers';
import * as log from '../helpers/loggingHelper';
import { setupNativefierWindow } from '../helpers/windowEvents';
import { createNewWindow } from '../helpers/windowHelpers';
import {
OutputOptions,
outputOptionsToWindowOptions,
} from '../../../shared/src/options/model';
export function initContextMenu(
options: OutputOptions,
window?: BrowserWindow,
): void {
log.debug('initContextMenu');
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
contextMenu({
prepend: (actions: contextMenu.Actions, params: ContextMenuParams) => {
log.debug('contextMenu.prepend', { actions, params });
const items = [];
if (params.linkURL && window) {
items.push({
label: 'Open Link in Default Browser',
click: () => {
openExternal(params.linkURL).catch((err) =>
log.error('contextMenu Open Link in Default Browser ERROR', err),
);
},
});
items.push({
label: 'Open Link in New Window',
click: () =>
createNewWindow(
outputOptionsToWindowOptions(options, nativeTabsSupported()),
setupNativefierWindow,
params.linkURL,
// window,
),
});
if (nativeTabsSupported()) {
items.push({
label: 'Open Link in New Tab',
click: () =>
// // Fire a new window event for a foreground tab
// // Previously we called createNewTab directly, but it had incosistent and buggy behavior
// // as it was mostly designed for running off of events. So this will create a new event
// // for a foreground-tab for the event handler to grab and take care of instead.
// (window as BrowserWindow).webContents.emit(
// // event name
// 'new-window',
// // event object
// {
// // Leave to the default for a NewWindowWebContentsEvent
// newGuest: undefined,
// ...new Event('new-window'),
// }, // as NewWindowWebContentsEvent,
// // url
// params.linkURL,
// // frameName
// window?.webContents.mainFrame.name ?? '',
// // disposition
// 'foreground-tab',
// ),
window.emit('new-window-for-tab', {
...new Event('new-window-for-tab'),
url: params.linkURL,
} as ElectronEvent<{ url: string }>),
});
}
}
return items;
},
showCopyImage: true,
showCopyImageAddress: true,
showSaveImage: true,
});
}

View File

@ -0,0 +1,39 @@
import * as path from 'path';
import { BrowserWindow, ipcMain } from 'electron';
import * as log from '../helpers/loggingHelper';
import { nativeTabsSupported } from '../helpers/helpers';
export async function createLoginWindow(
loginCallback: (username?: string, password?: string) => void,
parent?: BrowserWindow,
): Promise<BrowserWindow> {
log.debug('createLoginWindow', {
loginCallback,
parent,
});
const loginWindow = new BrowserWindow({
parent: nativeTabsSupported() ? undefined : parent,
width: 300,
height: 400,
frame: false,
resizable: false,
webPreferences: {
nodeIntegration: true, // TODO work around this; insecure
contextIsolation: false, // https://github.com/electron/electron/issues/28017
sandbox: false, // https://www.electronjs.org/blog/electron-20-0#default-changed-renderers-without-nodeintegration-true-are-sandboxed-by-default
},
});
await loginWindow.loadURL(
`file://${path.join(__dirname, 'static/login.html')}`,
);
ipcMain.once('login-message', (event, usernameAndPassword: string[]) => {
log.debug('login-message', { event, username: usernameAndPassword[0] });
loginCallback(usernameAndPassword[0], usernameAndPassword[1]);
loginWindow.close();
});
return loginWindow;
}

View File

@ -0,0 +1,336 @@
import * as fs from 'fs';
import * as path from 'path';
import {
desktopCapturer,
ipcMain,
BrowserWindow,
Event,
HandlerDetails,
} from 'electron';
import windowStateKeeper from 'electron-window-state';
import { initContextMenu } from './contextMenu';
import { createMenu } from './menu';
import {
getAppIcon,
getCounterValue,
isOSX,
nativeTabsSupported,
} from '../helpers/helpers';
import * as log from '../helpers/loggingHelper';
import { IS_PLAYWRIGHT } from '../helpers/playwrightHelpers';
import { onNewWindow, setupNativefierWindow } from '../helpers/windowEvents';
import {
clearCache,
createNewTab,
getDefaultWindowOptions,
hideWindow,
} from '../helpers/windowHelpers';
import {
OutputOptions,
outputOptionsToWindowOptions,
} from '../../../shared/src/options/model';
export const APP_ARGS_FILE_PATH = path.join(__dirname, '..', 'nativefier.json');
type SessionInteractionRequest = {
id?: string;
func?: string;
funcArgs?: unknown[];
property?: string;
propertyValue?: unknown;
};
type SessionInteractionResult<T = unknown> = {
id?: string;
value?: T | Promise<T>;
error?: Error;
};
/**
* @param {{}} nativefierOptions AppArgs from nativefier.json
* @param {function} setDockBadge
*/
export async function createMainWindow(
nativefierOptions: OutputOptions,
setDockBadge: (value: number | string, bounce?: boolean) => void,
): Promise<BrowserWindow> {
const options = { ...nativefierOptions };
const mainWindowState = windowStateKeeper({
defaultWidth: options.width || 1280,
defaultHeight: options.height || 800,
});
const mainWindow = new BrowserWindow({
frame: !options.hideWindowFrame,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: options.minWidth,
minHeight: options.minHeight,
maxWidth: options.maxWidth,
maxHeight: options.maxHeight,
x: options.x,
y: options.y,
autoHideMenuBar: !options.showMenuBar,
icon: getAppIcon(),
fullscreen: options.fullScreen,
// Whether the window should always stay on top of other windows. Default is false.
alwaysOnTop: options.alwaysOnTop,
titleBarStyle: options.titleBarStyle ?? 'default',
// Maximize window visual glitch on Windows fix
// We want a consistent behavior on all OSes, but Windows needs help to not glitch.
// So, we manually mainWindow.show() later, see a few lines below
show: options.tray !== 'start-in-tray' && process.platform !== 'win32',
backgroundColor: options.backgroundColor,
...getDefaultWindowOptions(
outputOptionsToWindowOptions(options, nativeTabsSupported()),
),
});
// Just load about:blank to start, gives playwright something to latch onto initially for testing.
if (IS_PLAYWRIGHT) {
await mainWindow.loadURL('about:blank');
}
mainWindowState.manage(mainWindow);
// after first run, no longer force maximize to be true
if (options.maximize) {
mainWindow.maximize();
options.maximize = undefined;
saveAppArgs(options);
}
if (options.tray === 'start-in-tray') {
mainWindow.hide();
} else if (process.platform === 'win32') {
// See other "Maximize window visual glitch on Windows fix" comment above.
mainWindow.show();
}
const windowOptions = outputOptionsToWindowOptions(
options,
nativeTabsSupported(),
);
createMenu(options, mainWindow);
createContextMenu(options, mainWindow);
setupNativefierWindow(windowOptions, mainWindow);
// Note it is important to add these handlers only to the *main* window,
// else we run into weird behavior like opening tabs twice
mainWindow.webContents.setWindowOpenHandler((details: HandlerDetails) => {
return onNewWindow(
windowOptions,
setupNativefierWindow,
details,
mainWindow,
);
});
mainWindow.on('new-window-for-tab', (event?: Event<{ url?: string }>) => {
log.debug('mainWindow.new-window-for-tab', { event });
createNewTab(
windowOptions,
setupNativefierWindow,
event?.url ?? options.targetUrl,
true,
// mainWindow,
);
});
if (options.counter) {
setupCounter(options, mainWindow, setDockBadge);
} else {
setupNotificationBadge(options, mainWindow, setDockBadge);
}
ipcMain.on('notification-click', () => {
log.debug('ipcMain.notification-click');
mainWindow.show();
});
setupSessionInteraction(mainWindow);
setupSessionPermissionHandler(mainWindow);
if (options.clearCache) {
await clearCache(mainWindow);
}
setupCloseEvent(options, mainWindow);
return mainWindow;
}
function createContextMenu(
options: OutputOptions,
window: BrowserWindow,
): void {
if (!options.disableContextMenu) {
initContextMenu(options, window);
}
}
export function saveAppArgs(newAppArgs: OutputOptions): void {
try {
fs.writeFileSync(APP_ARGS_FILE_PATH, JSON.stringify(newAppArgs, null, 2));
} catch (err: unknown) {
log.warn(
`WARNING: Ignored nativefier.json rewrital (${(err as Error).message})`,
);
}
}
function setupCloseEvent(options: OutputOptions, window: BrowserWindow): void {
window.on('close', (event: Event) => {
log.debug('mainWindow.close', event);
if (window.isFullScreen()) {
if (nativeTabsSupported()) {
window.moveTabToNewWindow();
}
window.setFullScreen(false);
window.once('leave-full-screen', (event: Event) =>
hideWindow(
window,
event,
options.fastQuit ?? false,
options.tray ?? 'false',
),
);
}
hideWindow(
window,
event,
options.fastQuit ?? false,
options.tray ?? 'false',
);
if (options.clearCache) {
clearCache(window).catch((err) => log.error('clearCache ERROR', err));
}
});
}
function setupCounter(
options: OutputOptions,
window: BrowserWindow,
setDockBadge: (value: number | string, bounce?: boolean) => void,
): void {
window.on('page-title-updated', (event, title) => {
log.debug('mainWindow.page-title-updated', { event, title });
const counterValue = getCounterValue(title);
if (counterValue) {
setDockBadge(counterValue, options.bounce);
} else {
setDockBadge('');
}
});
}
function setupSessionPermissionHandler(window: BrowserWindow): void {
window.webContents.session.setPermissionCheckHandler(() => {
return true;
});
window.webContents.session.setPermissionRequestHandler(
(_webContents, _permission, callback) => {
callback(true);
},
);
ipcMain.handle('desktop-capturer-get-sources', () => {
return desktopCapturer.getSources({
types: ['screen', 'window'],
});
});
}
function setupNotificationBadge(
options: OutputOptions,
window: BrowserWindow,
setDockBadge: (value: number | string, bounce?: boolean) => void,
): void {
ipcMain.on('notification', () => {
log.debug('ipcMain.notification');
if (!isOSX() || window.isFocused()) {
return;
}
setDockBadge('•', options.bounce);
});
window.on('focus', () => {
log.debug('mainWindow.focus');
setDockBadge('');
});
}
function setupSessionInteraction(window: BrowserWindow): void {
// See API.md / "Accessing The Electron Session"
ipcMain.on(
'session-interaction',
(event, request: SessionInteractionRequest) => {
log.debug('ipcMain.session-interaction', { event, request });
const result: SessionInteractionResult = { id: request.id };
let awaitingPromise = false;
try {
if (request.func !== undefined) {
// If no funcArgs provided, we'll just use an empty array
if (request.funcArgs === undefined || request.funcArgs === null) {
request.funcArgs = [];
}
// If funcArgs isn't an array, we'll be nice and make it a single item array
if (typeof request.funcArgs[Symbol.iterator] !== 'function') {
request.funcArgs = [request.funcArgs];
}
// Call func with funcArgs
// @ts-expect-error accessing a func by string name
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
result.value = window.webContents.session[request.func](
...request.funcArgs,
);
if (result.value !== undefined && result.value instanceof Promise) {
// This is a promise. We'll resolve it here otherwise it will blow up trying to serialize it in the reply
(result.value as Promise<unknown>)
.then((trueResultValue) => {
result.value = trueResultValue;
log.debug('ipcMain.session-interaction:result', result);
event.reply('session-interaction-reply', result);
})
.catch((err) =>
log.error('session-interaction ERROR', request, err),
);
awaitingPromise = true;
}
} else if (request.property !== undefined) {
if (request.propertyValue !== undefined) {
// Set the property
// @ts-expect-error setting a property by string name
window.webContents.session[request.property] =
request.propertyValue;
}
// Get the property value
// @ts-expect-error accessing a property by string name
result.value = window.webContents.session[request.property];
} else {
// Why even send the event if you're going to do this? You're just wasting time! ;)
throw new Error(
'Received neither a func nor a property in the request. Unable to process.',
);
}
// If we are awaiting a promise, that will return the reply instead, else
if (!awaitingPromise) {
log.debug('session-interaction:result', result);
event.reply('session-interaction-reply', result);
}
} catch (err: unknown) {
log.error('session-interaction:error', err, event, request);
result.error = err as Error;
result.value = undefined; // Clear out the value in case serializing the value is what got us into this mess in the first place
event.reply('session-interaction-reply', result);
}
},
);
}

View File

@ -0,0 +1,167 @@
import { BrowserWindow, MenuItemConstructorOptions } from 'electron';
jest.mock('../helpers/helpers');
import { isOSX } from '../helpers/helpers';
import { generateMenu } from './menu';
describe('generateMenu', () => {
let window: BrowserWindow;
const mockIsOSX: jest.SpyInstance = isOSX as jest.Mock;
let mockIsFullScreen: jest.SpyInstance;
let mockIsFullScreenable: jest.SpyInstance;
let mockIsSimpleFullScreen: jest.SpyInstance;
let mockSetFullScreen: jest.SpyInstance;
let mockSetSimpleFullScreen: jest.SpyInstance;
beforeEach(() => {
window = new BrowserWindow();
mockIsOSX.mockReset();
mockIsFullScreen = jest
.spyOn(window, 'isFullScreen')
.mockReturnValue(false);
mockIsFullScreenable = jest
.spyOn(window, 'isFullScreenable')
.mockReturnValue(true);
mockIsSimpleFullScreen = jest
.spyOn(window, 'isSimpleFullScreen')
.mockReturnValue(false);
mockSetFullScreen = jest.spyOn(window, 'setFullScreen');
mockSetSimpleFullScreen = jest.spyOn(window, 'setSimpleFullScreen');
});
afterAll(() => {
mockIsFullScreen.mockRestore();
mockIsFullScreenable.mockRestore();
mockIsSimpleFullScreen.mockRestore();
mockSetFullScreen.mockRestore();
mockSetSimpleFullScreen.mockRestore();
});
test('does not have fullscreen if not supported', () => {
mockIsOSX.mockReturnValue(false);
mockIsFullScreenable.mockReturnValue(false);
const menu = generateMenu(
{
nativefierVersion: '1.0.0',
zoom: 1.0,
disableDevTools: false,
},
window,
);
const editMenu = menu.filter((item) => item.label === '&View');
const fullscreen = (
editMenu[0].submenu as MenuItemConstructorOptions[]
).filter((item) => item.label === 'Toggle Full Screen');
expect(fullscreen).toHaveLength(1);
expect(fullscreen[0].enabled).toBe(false);
expect(fullscreen[0].visible).toBe(false);
expect(mockIsOSX).toHaveBeenCalled();
expect(mockIsFullScreenable).toHaveBeenCalled();
});
test('has fullscreen no matter what on mac', () => {
mockIsOSX.mockReturnValue(true);
mockIsFullScreenable.mockReturnValue(false);
const menu = generateMenu(
{
nativefierVersion: '1.0.0',
zoom: 1.0,
disableDevTools: false,
},
window,
);
const editMenu = menu.filter((item) => item.label === '&View');
const fullscreen = (
editMenu[0].submenu as MenuItemConstructorOptions[]
).filter((item) => item.label === 'Toggle Full Screen');
expect(fullscreen).toHaveLength(1);
expect(fullscreen[0].enabled).toBe(true);
expect(fullscreen[0].visible).toBe(true);
expect(mockIsOSX).toHaveBeenCalled();
expect(mockIsFullScreenable).toHaveBeenCalled();
});
test.each([true, false])(
'has a fullscreen menu item that toggles fullscreen',
(isFullScreen) => {
mockIsOSX.mockReturnValue(false);
mockIsFullScreenable.mockReturnValue(true);
mockIsFullScreen.mockReturnValue(isFullScreen);
const menu = generateMenu(
{
nativefierVersion: '1.0.0',
zoom: 1.0,
disableDevTools: false,
},
window,
);
const editMenu = menu.filter((item) => item.label === '&View');
const fullscreen = (
editMenu[0].submenu as MenuItemConstructorOptions[]
).filter((item) => item.label === 'Toggle Full Screen');
expect(fullscreen).toHaveLength(1);
expect(fullscreen[0].enabled).toBe(true);
expect(fullscreen[0].visible).toBe(true);
expect(mockIsOSX).toHaveBeenCalled();
expect(mockIsFullScreenable).toHaveBeenCalled();
// @ts-expect-error click is here TypeScript...
fullscreen[0].click(null, window);
expect(mockSetFullScreen).toHaveBeenCalledWith(!isFullScreen);
expect(mockSetSimpleFullScreen).not.toHaveBeenCalled();
},
);
test.each([true, false])(
'has a fullscreen menu item that toggles simplefullscreen as a fallback on mac',
(isFullScreen) => {
mockIsOSX.mockReturnValue(true);
mockIsFullScreenable.mockReturnValue(false);
mockIsSimpleFullScreen.mockReturnValue(isFullScreen);
const menu = generateMenu(
{
nativefierVersion: '1.0.0',
zoom: 1.0,
disableDevTools: false,
},
window,
);
const editMenu = menu.filter((item) => item.label === '&View');
const fullscreen = (
editMenu[0].submenu as MenuItemConstructorOptions[]
).filter((item) => item.label === 'Toggle Full Screen');
expect(fullscreen).toHaveLength(1);
expect(fullscreen[0].enabled).toBe(true);
expect(fullscreen[0].visible).toBe(true);
expect(mockIsOSX).toHaveBeenCalled();
expect(mockIsFullScreenable).toHaveBeenCalled();
// @ts-expect-error click is here TypeScript...
fullscreen[0].click(null, window);
expect(mockSetSimpleFullScreen).toHaveBeenCalledWith(!isFullScreen);
expect(mockSetFullScreen).not.toHaveBeenCalled();
},
);
});

424
app/src/components/menu.ts Normal file
View File

@ -0,0 +1,424 @@
import * as fs from 'fs';
import path from 'path';
import {
BrowserWindow,
clipboard,
Menu,
MenuItem,
MenuItemConstructorOptions,
} from 'electron';
import { cleanupPlainText, isOSX, openExternal } from '../helpers/helpers';
import * as log from '../helpers/loggingHelper';
import {
clearAppData,
getCurrentURL,
goBack,
goForward,
goToURL,
zoomIn,
zoomOut,
zoomReset,
} from '../helpers/windowHelpers';
import { OutputOptions } from '../../../shared/src/options/model';
type BookmarksLink = {
type: 'link';
title: string;
url: string;
shortcut?: string;
};
type BookmarksSeparator = {
type: 'separator';
};
type BookmarkConfig = BookmarksLink | BookmarksSeparator;
type BookmarksMenuConfig = {
menuLabel: string;
bookmarks: BookmarkConfig[];
};
export function createMenu(
options: OutputOptions,
mainWindow: BrowserWindow,
): void {
log.debug('createMenu', { options });
const menuTemplate = generateMenu(options, mainWindow);
injectBookmarks(menuTemplate);
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
}
export function generateMenu(
options: {
disableDevTools: boolean;
nativefierVersion: string;
zoom?: number;
},
mainWindow: BrowserWindow,
): MenuItemConstructorOptions[] {
const { nativefierVersion, zoom, disableDevTools } = options;
const zoomResetLabel =
!zoom || zoom === 1.0
? 'Reset Zoom'
: `Reset Zoom (to ${(zoom * 100).toFixed(1)}%, set at build time)`;
const editMenu: MenuItemConstructorOptions = {
label: '&Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
role: 'undo',
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo',
},
{
type: 'separator',
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut',
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy',
},
{
label: 'Copy as Plain Text',
accelerator: 'CmdOrCtrl+Shift+C',
click: (): void => {
// We use clipboard.readText to strip down formatting
const text = clipboard.readText('selection');
clipboard.writeText(cleanupPlainText(text), 'clipboard');
},
},
{
label: 'Copy Current URL',
accelerator: 'CmdOrCtrl+L',
click: (): void => clipboard.writeText(getCurrentURL()),
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste',
},
{
label: 'Paste and Match Style',
// https://github.com/nativefier/nativefier/issues/404
// Apple's HIG lists this shortcut for paste and match style
// https://support.apple.com/en-us/HT209651
accelerator: isOSX() ? 'Option+Shift+Cmd+V' : 'Ctrl+Shift+V',
role: 'pasteAndMatchStyle',
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectAll',
},
{
label: 'Clear App Data',
click: (
item: MenuItem,
focusedWindow: BrowserWindow | undefined,
): void => {
log.debug('Clear App Data.click', {
item,
focusedWindow,
mainWindow,
});
if (!focusedWindow) {
focusedWindow = mainWindow;
}
clearAppData(focusedWindow).catch((err) =>
log.error('clearAppData ERROR', err),
);
},
},
],
};
const viewMenu: MenuItemConstructorOptions = {
label: '&View',
submenu: [
{
label: 'Back',
accelerator: isOSX() ? 'Cmd+Left' : 'Alt+Left',
click: goBack,
},
{
label: 'BackAdditionalShortcut',
visible: false,
acceleratorWorksWhenHidden: true,
accelerator: 'CmdOrCtrl+[', // What old versions of Nativefier used, kept for backwards compat
click: goBack,
},
{
label: 'Forward',
accelerator: isOSX() ? 'Cmd+Right' : 'Alt+Right',
click: goForward,
},
{
label: 'ForwardAdditionalShortcut',
visible: false,
acceleratorWorksWhenHidden: true,
accelerator: 'CmdOrCtrl+]', // What old versions of Nativefier used, kept for backwards compat
click: goForward,
},
{
label: 'Reload',
role: 'reload',
},
{
type: 'separator',
},
{
label: 'Toggle Full Screen',
accelerator: isOSX() ? 'Ctrl+Cmd+F' : 'F11',
enabled: mainWindow.isFullScreenable() || isOSX(),
visible: mainWindow.isFullScreenable() || isOSX(),
click: (
item: MenuItem,
focusedWindow: BrowserWindow | undefined,
): void => {
log.debug('Toggle Full Screen.click()', {
item,
focusedWindow,
isFullScreen: focusedWindow?.isFullScreen(),
isFullScreenable: focusedWindow?.isFullScreenable(),
});
if (!focusedWindow) {
focusedWindow = mainWindow;
}
if (focusedWindow.isFullScreenable()) {
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
} else if (isOSX()) {
focusedWindow.setSimpleFullScreen(
!focusedWindow.isSimpleFullScreen(),
);
}
},
},
{
label: 'Zoom In',
accelerator: 'CmdOrCtrl+=',
click: zoomIn,
},
{
label: 'ZoomInAdditionalShortcut',
visible: false,
acceleratorWorksWhenHidden: true,
accelerator: 'CmdOrCtrl+numadd',
click: zoomIn,
},
{
label: 'Zoom Out',
accelerator: 'CmdOrCtrl+-',
click: zoomOut,
},
{
label: 'ZoomOutAdditionalShortcut',
visible: false,
acceleratorWorksWhenHidden: true,
accelerator: 'CmdOrCtrl+numsub',
click: zoomOut,
},
{
label: zoomResetLabel,
accelerator: 'CmdOrCtrl+0',
click: (): void => zoomReset(options),
},
{
label: 'ZoomResetAdditionalShortcut',
visible: false,
acceleratorWorksWhenHidden: true,
accelerator: 'CmdOrCtrl+num0',
click: (): void => zoomReset(options),
},
],
};
if (!disableDevTools) {
(viewMenu.submenu as MenuItemConstructorOptions[]).push(
{
type: 'separator',
},
{
label: 'Toggle Developer Tools',
accelerator: isOSX() ? 'Alt+Cmd+I' : 'Ctrl+Shift+I',
click: (item: MenuItem, focusedWindow: BrowserWindow | undefined) => {
log.debug('Toggle Developer Tools.click()', { item, focusedWindow });
if (!focusedWindow) {
focusedWindow = mainWindow;
}
focusedWindow.webContents.toggleDevTools();
},
},
);
}
const windowMenu: MenuItemConstructorOptions = {
label: '&Window',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize',
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close',
},
],
};
const helpMenu: MenuItemConstructorOptions = {
label: '&Help',
role: 'help',
submenu: [
{
label: `Built with Nativefier v${nativefierVersion}`,
click: (): void => {
openExternal('https://github.com/nativefier/nativefier').catch(
(err: unknown): void =>
log.error(
'Built with Nativefier v${nativefierVersion}.click ERROR',
err,
),
);
},
},
{
label: 'Report an Issue',
click: (): void => {
openExternal('https://github.com/nativefier/nativefier/issues').catch(
(err: unknown): void =>
log.error('Report an Issue.click ERROR', err),
);
},
},
],
};
let menuTemplate: MenuItemConstructorOptions[];
if (isOSX()) {
const electronMenu: MenuItemConstructorOptions = {
label: 'E&lectron',
submenu: [
{
label: 'Services',
role: 'services',
submenu: [],
},
{
type: 'separator',
},
{
label: 'Hide App',
accelerator: 'Cmd+H',
role: 'hide',
},
{
label: 'Hide Others',
accelerator: 'Cmd+Shift+H',
role: 'hideOthers',
},
{
label: 'Show All',
role: 'unhide',
},
{
type: 'separator',
},
{
label: 'Quit',
accelerator: 'Cmd+Q',
role: 'quit',
},
],
};
(windowMenu.submenu as MenuItemConstructorOptions[]).push(
{
type: 'separator',
},
{
label: 'Bring All to Front',
role: 'front',
},
);
menuTemplate = [electronMenu, editMenu, viewMenu, windowMenu, helpMenu];
} else {
menuTemplate = [editMenu, viewMenu, windowMenu, helpMenu];
}
return menuTemplate;
}
function injectBookmarks(menuTemplate: MenuItemConstructorOptions[]): void {
const bookmarkConfigPath = path.join(__dirname, '..', 'bookmarks.json');
if (!fs.existsSync(bookmarkConfigPath)) {
return;
}
try {
const bookmarksMenuConfig = JSON.parse(
fs.readFileSync(bookmarkConfigPath, 'utf-8'),
) as BookmarksMenuConfig;
const submenu: MenuItemConstructorOptions[] =
bookmarksMenuConfig.bookmarks.map((bookmark) => {
switch (bookmark.type) {
case 'link':
if (!('title' in bookmark && 'url' in bookmark)) {
throw new Error(
'All links in the bookmarks menu must have a title and url.',
);
}
try {
new URL(bookmark.url);
} catch {
throw new Error('Bookmark URL "' + bookmark.url + '"is invalid.');
}
return {
label: bookmark.title,
click: (): void => {
goToURL(bookmark.url)?.catch((err: unknown): void =>
log.error(`${bookmark.title}.click ERROR`, err),
);
},
accelerator:
'shortcut' in bookmark ? bookmark.shortcut : undefined,
};
case 'separator':
return {
type: 'separator',
};
default:
throw new Error(
'A bookmarks menu entry has an invalid type; type must be one of "link", "separator".',
);
}
});
const bookmarksMenu: MenuItemConstructorOptions = {
label: bookmarksMenuConfig.menuLabel,
submenu,
};
// Insert custom bookmarks menu between menus "View" and "Window"
menuTemplate.splice(menuTemplate.length - 2, 0, bookmarksMenu);
} catch (err: unknown) {
log.error('Failed to load & parse bookmarks configuration JSON file.', err);
}
}

View File

@ -0,0 +1,88 @@
import { app, Tray, Menu, ipcMain, nativeImage, BrowserWindow } from 'electron';
import { getAppIcon, getCounterValue, isOSX } from '../helpers/helpers';
import * as log from '../helpers/loggingHelper';
import { OutputOptions } from '../../../shared/src/options/model';
export function createTrayIcon(
nativefierOptions: OutputOptions,
mainWindow: BrowserWindow,
): Tray | undefined {
const options = { ...nativefierOptions };
if (options.tray && options.tray !== 'false') {
const iconPath = getAppIcon();
if (!iconPath) {
throw new Error('Icon path not found found to use with tray option.');
}
const nimage = nativeImage.createFromPath(iconPath);
const appIcon = new Tray(nativeImage.createEmpty());
if (isOSX()) {
//sets the icon to the height of the tray.
appIcon.setImage(
nimage.resize({ height: appIcon.getBounds().height - 2 }),
);
} else {
appIcon.setImage(nimage);
}
const onClick = (): void => {
log.debug('onClick');
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
};
const contextMenu = Menu.buildFromTemplate([
{
label: options.name,
click: onClick,
},
{
label: 'Quit',
click: (): void => app.exit(0),
},
]);
appIcon.on('click', onClick);
if (options.counter) {
mainWindow.on('page-title-updated', (event, title) => {
log.debug('mainWindow.page-title-updated', { event, title });
const counterValue = getCounterValue(title);
if (counterValue) {
appIcon.setToolTip(
`(${counterValue}) ${options.name ?? 'Nativefier'}`,
);
} else {
appIcon.setToolTip(options.name ?? '');
}
});
} else {
ipcMain.on('notification', () => {
log.debug('ipcMain.notification');
if (mainWindow.isFocused()) {
return;
}
if (options.name) {
appIcon.setToolTip(`${options.name}`);
}
});
mainWindow.on('focus', () => {
log.debug('mainWindow.focus');
appIcon.setToolTip(options.name ?? '');
});
}
appIcon.setToolTip(options.name ?? '');
appIcon.setContextMenu(contextMenu);
return appIcon;
}
return undefined;
}

View File

@ -0,0 +1,348 @@
import { shell } from 'electron';
jest.mock('./windowHelpers');
import {
cleanupPlainText,
getCounterValue,
linkIsInternal,
openExternal,
removeUserAgentSpecifics,
} from './helpers';
import { showNavigationBlockedMessage } from './windowHelpers';
const internalUrl = 'https://medium.com/';
const internalUrlWww = 'https://www.medium.com/';
const internalUrlSubPathRegex = /https:\/\/www.medium.com\/.*/;
const sameBaseDomainUrl = 'https://app.medium.com/';
const internalUrlCoUk = 'https://medium.co.uk/';
const differentBaseDomainUrlCoUk = 'https://other.domain.co.uk/';
const sameBaseDomainUrlCoUk = 'https://app.medium.co.uk/';
const sameBaseDomainUrlTidalListen = 'https://listen.tidal.com/';
const sameBaseDomainUrlTidalLogin = 'https://login.tidal.com/';
const sameBaseDomainUrlTidalRegex = /https:\/\/(login|listen).tidal.com\/.*/;
const internalUrlSubPath = 'topic/technology';
const externalUrl = 'https://www.wikipedia.org/wiki/Electron';
const wildcardRegex = /.*/;
test('the original url should be internal without --strict-internal-urls', () => {
expect(
linkIsInternal(internalUrl, internalUrl, undefined, undefined),
).toEqual(true);
});
test('the original url should be internal with --strict-internal-urls off', () => {
expect(linkIsInternal(internalUrl, internalUrl, undefined, false)).toEqual(
true,
);
});
test('the original url should be internal with --strict-internal-urls on', () => {
expect(linkIsInternal(internalUrl, internalUrl, undefined, true)).toEqual(
true,
);
});
test('sub-paths of the original url should be internal with --strict-internal-urls off', () => {
expect(
linkIsInternal(
internalUrl,
internalUrl + internalUrlSubPath,
undefined,
false,
),
).toEqual(true);
});
test('sub-paths of the original url should not be internal with --strict-internal-urls on', () => {
expect(
linkIsInternal(
internalUrl,
internalUrl + internalUrlSubPath,
undefined,
true,
),
).toEqual(false);
});
test('sub-paths of the original url should be internal with using a regex and --strict-internal-urls on', () => {
expect(
linkIsInternal(
internalUrl,
internalUrl + internalUrlSubPath,
internalUrlSubPathRegex,
true,
),
).toEqual(false);
});
test("'about:blank' should always be internal", () => {
expect(linkIsInternal(internalUrl, 'about:blank', undefined, true)).toEqual(
true,
);
});
test('urls from different sites should not be internal', () => {
expect(linkIsInternal(internalUrl, externalUrl, undefined, false)).toEqual(
false,
);
});
test('all urls should be internal with wildcard regex', () => {
expect(linkIsInternal(internalUrl, externalUrl, wildcardRegex, true)).toEqual(
true,
);
});
test('a "www." of a domain should be considered internal', () => {
expect(linkIsInternal(internalUrl, internalUrlWww, undefined, false)).toEqual(
true,
);
});
test('urls on the same "base domain" should be considered internal', () => {
expect(
linkIsInternal(internalUrl, sameBaseDomainUrl, undefined, false),
).toEqual(true);
});
test('urls on the same "base domain" should NOT be considered internal using --strict-internal-urls', () => {
expect(
linkIsInternal(internalUrl, sameBaseDomainUrl, undefined, true),
).toEqual(false);
});
test('urls on the same "base domain" should be considered internal, even with a www', () => {
expect(
linkIsInternal(internalUrlWww, sameBaseDomainUrl, undefined, false),
).toEqual(true);
});
test('urls on the same "base domain" should be considered internal, even with different sub domains', () => {
expect(
linkIsInternal(
sameBaseDomainUrlTidalListen,
sameBaseDomainUrlTidalLogin,
undefined,
false,
),
).toEqual(true);
});
test('urls should support sub domain matching with a regex', () => {
expect(
linkIsInternal(
sameBaseDomainUrlTidalListen,
sameBaseDomainUrlTidalLogin,
sameBaseDomainUrlTidalRegex,
false,
),
).toEqual(true);
});
test('urls on the same "base domain" should NOT be considered internal with different sub domains when using --strict-internal-urls', () => {
expect(
linkIsInternal(
sameBaseDomainUrlTidalListen,
sameBaseDomainUrlTidalLogin,
undefined,
true,
),
).toEqual(false);
});
test('urls on the same "base domain" should be considered internal, long SLD', () => {
expect(
linkIsInternal(internalUrlCoUk, sameBaseDomainUrlCoUk, undefined, false),
).toEqual(true);
});
test('urls on the a different "base domain" are considered NOT internal, long SLD', () => {
expect(
linkIsInternal(
internalUrlCoUk,
differentBaseDomainUrlCoUk,
undefined,
false,
),
).toEqual(false);
});
const testLoginPages = [
'https://amazon.co.uk/signin',
'https://amazon.com/signin',
'https://amazon.de/signin',
'https://amazon.com/ap/signin',
'https://facebook.co.uk/login',
'https://facebook.com/login',
'https://facebook.de/login',
'https://github.co.uk/login',
'https://github.com/login',
'https://github.de/login',
// GitHub 2FA flow with FIDO token
'https://github.com/session',
'https://github.com/sessions/two-factor/webauth',
'https://accounts.google.co.uk',
'https://accounts.google.com',
'https://mail.google.com/accounts/SetOSID',
'https://mail.google.co.uk/accounts/SetOSID',
'https://accounts.google.de',
'https://linkedin.co.uk/uas/login',
'https://linkedin.com/uas/login',
'https://linkedin.de/uas/login',
'https://login.live.co.uk',
'https://login.live.com',
'https://login.live.de',
'https://login.microsoftonline.com/common/oauth2/authorize',
'https://login.microsoftonline.co.uk/common/oauth2/authorize',
'https://login.microsoftonline.de/common/oauth2/authorize',
'https://okta.co.uk',
'https://okta.com',
'https://subdomain.okta.com',
'https://okta.de',
'https://twitter.co.uk/oauth/authenticate',
'https://twitter.com/oauth/authenticate',
'https://twitter.de/oauth/authenticate',
'https://appleid.apple.com/auth/authorize',
'https://id.atlassian.com',
'https://auth.atlassian.com',
'https://vmware.workspaceair.com',
'https://vmware.auth.securid.com',
];
test.each(testLoginPages)(
'%s login page should be internal',
(loginUrl: string) => {
expect(linkIsInternal(internalUrl, loginUrl, undefined, false)).toEqual(
true,
);
},
);
// Ensure that we don't over-match service pages
const testNonLoginPages = [
'https://www.amazon.com/Node-Cookbook-techniques-server-side-development-ebook',
'https://github.com/nativefier/nativefier',
'https://github.com/org/nativefier',
'https://microsoft.com',
'https://office.microsoftonline.com',
'https://twitter.com/marcoroth_/status/1325938620906287104',
'https://appleid.apple.com/account',
'https://mail.google.com/',
'https://atlassian.com',
];
test.each(testNonLoginPages)(
'%s page should not be internal',
(url: string) => {
expect(linkIsInternal(internalUrl, url, undefined, false)).toEqual(false);
},
);
const smallCounterTitle = 'Inbox (11) - nobody@example.com - Gmail';
const largeCounterTitle = 'Inbox (8,756) - nobody@example.com - Gmail';
const hourCounterTitle = 'Today (1:23) - nobody@example.com - TimeTracker';
const noCounterTitle = 'Inbox - nobody@example.com - Gmail';
test('getCounterValue should return undefined for titles without counter numbers', () => {
expect(getCounterValue(noCounterTitle)).toEqual(undefined);
});
test('getCounterValue should return a string for small counter numbers in the title', () => {
expect(getCounterValue(smallCounterTitle)).toEqual('11');
});
test('getCounterValue should return a string for large counter numbers in the title', () => {
expect(getCounterValue(largeCounterTitle)).toEqual('8,756');
});
test('getCounterValue should return a string for hour counter numbers in the title', () => {
expect(getCounterValue(hourCounterTitle)).toEqual('1:23');
});
describe('removeUserAgentSpecifics', () => {
const userAgentFallback =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) app-nativefier-804458/1.0.0 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36';
test('removes Electron and App specific info', () => {
expect(
removeUserAgentSpecifics(
userAgentFallback,
'app-nativefier-804458',
'1.0.0',
),
).not.toBe(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36',
);
});
test('should not have multiple spaces in a row', () => {
expect(
removeUserAgentSpecifics(
userAgentFallback,
'app-nativefier-804458',
'1.0.0',
),
).toEqual(expect.not.stringMatching(/\s{2,}/));
});
});
describe('cleanupPlainText', () => {
test('removes extra spaces from text', () => {
expect(cleanupPlainText(' this is a test ')).toBe('this is a test');
});
});
describe('openExternal', () => {
const mockShellOpenExternal: jest.SpyInstance = jest.spyOn(
shell,
'openExternal',
);
const mockShowNavigationBlockedMessage: jest.SpyInstance =
showNavigationBlockedMessage as jest.Mock;
beforeEach(() => {
mockShellOpenExternal.mockReset();
mockShowNavigationBlockedMessage
.mockReset()
.mockReturnValue(Promise.resolve(undefined));
});
afterAll(() => {
mockShellOpenExternal.mockRestore();
mockShowNavigationBlockedMessage.mockRestore();
});
test('https urls scheme should *not* be blocked', async () => {
await openExternal('https://whatever.foo');
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockShellOpenExternal).toHaveBeenCalled();
});
test('urls with whitelisted scheme should *not* be blocked', async () => {
await openExternal('ircs://irc.libera.chat/whatever');
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockShellOpenExternal).toHaveBeenCalled();
});
test('urls with non-allowlisted scheme *should* be blocked', async () => {
await openExternal('barf://whatever.foo');
expect(mockShowNavigationBlockedMessage).toHaveBeenCalledTimes(1);
expect(mockShellOpenExternal).not.toHaveBeenCalled();
});
test('potentially-malicious urls *should* be blocked', async () => {
await openExternal('https://hello.com/wor%00ld');
expect(mockShowNavigationBlockedMessage).toHaveBeenCalledTimes(1);
expect(mockShellOpenExternal).not.toHaveBeenCalled();
});
test('malformed urls *should* be blocked', async () => {
await openExternal('zombocom');
expect(mockShowNavigationBlockedMessage).toHaveBeenCalledTimes(1);
expect(mockShellOpenExternal).not.toHaveBeenCalled();
});
});

315
app/src/helpers/helpers.ts Normal file
View File

@ -0,0 +1,315 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { BrowserWindow, OpenExternalOptions, shell } from 'electron';
import * as log from '../helpers/loggingHelper';
import { showNavigationBlockedMessage } from './windowHelpers';
export const INJECT_DIR = path.join(__dirname, '..', 'inject');
/**
* Firefox's list of protocols for which opening an external handler is allowed without confirmation.
* Taken from Firefox's. Location might vary in codebase, search for one of them, e.g.
* https://searchfox.org/mozilla-central/search?q=%22xmpp%22&path=&case=false&regexp=false
*/
const URL_PROTOCOLS_NOCONFIRMATION_FIREFOX = [
'bitcoin:',
'ftp:',
'ftps:',
'geo:',
'im:',
'irc:',
'ircs:',
'magnet:',
'mailto:',
'matrix:',
'mms:',
'news:',
'nntp:',
'openpgp4fpr:',
'sftp:',
'sip:',
'sms:',
'smsto:',
'ssh:',
'tel:',
'urn:',
'webcal:',
'wtai:',
'xmpp:',
];
/**
* Our extension to Firefox's list. If extending this list too much, we should
* really add a confirmation modal (for now we just block), like browsers do.
* But for now, since nobody shouts at us for bluntly blocking anything else,
* let's keep rolling with it.
*/
const URL_PROTOCOLS_NOCONFIRMATION_EXTRA = ['zoommtg:'];
/**
* List of protocols for which opening an external handler is allowed without confirmation.
* Note: "without confirmation" is currently a lie. It was implemented this way
* as a way to know from user feedback what protocols would cause users to shout,
* but there wasn't much shouting happening, so we currently don't have a confirmation
* mechanism, we just bluntly block. That might need to change at some point.
*/
const URL_PROTOCOLS_NOCONFIRMATION = [
'http:',
'https:',
...URL_PROTOCOLS_NOCONFIRMATION_FIREFOX,
...URL_PROTOCOLS_NOCONFIRMATION_EXTRA,
];
const SHELL_SAFETY_FEEDBACK_STR =
'If you believe this URL should open, you might be right, and our validation might be excessive.' +
'Please share this error & URL at https://github.com/nativefier/nativefier/issues/1459';
export function isUrlShellSafe(
urlToGo: string,
): { blocked: false } | { blocked: true; reason: string } {
let url: URL;
try {
url = new URL(urlToGo.toLowerCase());
} catch (err: unknown) {
return {
blocked: true,
reason: `URL appears malformed. ${SHELL_SAFETY_FEEDBACK_STR}`,
};
}
if (!URL_PROTOCOLS_NOCONFIRMATION.includes(url.protocol)) {
return {
blocked: true,
reason: `URL protocol is disallowed. ${SHELL_SAFETY_FEEDBACK_STR}`,
};
}
// https://cwe.mitre.org/data/definitions/177.html
if (
urlToGo.includes('%00') ||
urlToGo.includes('%0a') ||
urlToGo.includes('%2e') ||
urlToGo.includes('%2f') ||
urlToGo.includes('%5c')
) {
return {
blocked: true,
reason: `URL might be malicious. ${SHELL_SAFETY_FEEDBACK_STR}`,
};
}
return { blocked: false };
}
/**
* Helper to print debug messages from the main process in the browser window
*/
export function debugLog(browserWindow: BrowserWindow, message: string): void {
// Need a delay, as it takes time for the preloaded js to be loaded by the window
setTimeout(() => {
browserWindow.webContents.send('debug', message);
}, 3000);
log.debug(message);
}
/**
* Helper to determine domain-ish equality for many cases, the trivial ones
* and the trickier ones, e.g. `blog.foo.com` and `shop.foo.com`,
* in a way that is "good enough", and doesn't need a list of SLDs.
* See chat at https://github.com/nativefier/nativefier/pull/1171#pullrequestreview-649132523
*/
function domainify(url: string): string {
// So here's what we're doing here:
// Get the hostname from the url
const hostname = new URL(url).hostname;
// Drop the first section if the domain
const domain = hostname.split('.').slice(1).join('.');
// Check the length, if it's too short, the hostname was probably the domain
// Or if the domain doesn't have a . in it we went too far
if (domain.length < 6 || domain.split('.').length === 0) {
return hostname;
}
// This SHOULD be the domain, but nothing is 100% guaranteed
return domain;
}
export function getAppIcon(): string | undefined {
// Prefer ICO under Windows, see
// https://www.electronjs.org/docs/api/browser-window#new-browserwindowoptions
// https://www.electronjs.org/docs/api/native-image#supported-formats
if (isWindows()) {
const ico = path.join(__dirname, '..', 'icon.ico');
if (fs.existsSync(ico)) {
return ico;
}
}
const png = path.join(__dirname, '..', 'icon.png');
if (fs.existsSync(png)) {
return png;
}
}
export function getCounterValue(title: string): string | undefined {
const itemCountRegex = /[([{]([\d.,:]*)\+?[}\])]/;
const match = itemCountRegex.exec(title);
return match ? match[1] : undefined;
}
export function getCSSToInject(): string {
let cssToInject = '';
const cssFiles = fs
.readdirSync(INJECT_DIR, { withFileTypes: true })
.filter(
(injectFile) => injectFile.isFile() && injectFile.name.endsWith('.css'),
)
.map((cssFileStat) =>
path.resolve(path.join(INJECT_DIR, cssFileStat.name)),
);
for (const cssFile of cssFiles) {
log.debug('Injecting CSS file', cssFile);
const cssFileData = fs.readFileSync(cssFile);
cssToInject += `/* ${cssFile} */\n\n ${cssFileData.toString()}\n\n`;
}
return cssToInject;
}
export function isOSX(): boolean {
return os.platform() === 'darwin';
}
export function isLinux(): boolean {
return os.platform() === 'linux';
}
export function isWindows(): boolean {
return os.platform() === 'win32';
}
function isInternalLoginPage(url: string): boolean {
// Making changes? Remember to update the tests in helpers.test.ts and in API.md
const internalLoginPagesArray = [
'amazon\\.[a-zA-Z\\.]*/[a-zA-Z\\/]*signin', // Amazon
`facebook\\.[a-zA-Z\\.]*\\/login`, // Facebook
'github\\.[a-zA-Z\\.]*\\/(?:login|session)', // GitHub
'accounts\\.google\\.[a-zA-Z\\.]*', // Google
'mail\\.google\\.[a-zA-Z\\.]*\\/accounts/SetOSID', // Google
'linkedin\\.[a-zA-Z\\.]*/uas/login', // LinkedIn
'login\\.live\\.[a-zA-Z\\.]*', // Microsoft
'login\\.microsoftonline\\.[a-zA-Z\\.]*', // Microsoft
'okta\\.[a-zA-Z\\.]*', // Okta
'twitter\\.[a-zA-Z\\.]*/oauth/authenticate', // Twitter
'appleid\\.apple\\.com/auth/authorize', // Apple
'(?:id|auth)\\.atlassian\\.[a-zA-Z]+', // Atlassian
'.*\\.workspaceair\\.com', // VMWare Workspace One SSO
'.*\\.securid\\.com', // SecurID for VMWare Workspace One SSO
];
// Making changes? Remember to update the tests in helpers.test.ts and in API.md
const regex = RegExp(internalLoginPagesArray.join('|'));
return regex.test(url);
}
export function linkIsInternal(
currentUrl: string,
newUrl: string,
internalUrlRegex: string | RegExp | undefined,
isStrictInternalUrlsEnabled: boolean | undefined,
): boolean {
log.debug('linkIsInternal', { currentUrl, newUrl, internalUrlRegex });
if (newUrl.split('#')[0] === 'about:blank') {
return true;
}
if (isInternalLoginPage(newUrl)) {
return true;
}
if (internalUrlRegex) {
const regex = RegExp(internalUrlRegex);
if (regex.test(newUrl)) {
return true;
}
}
if (isStrictInternalUrlsEnabled) {
return currentUrl == newUrl;
}
try {
// Consider as "same domain-ish", without TLD/SLD list:
// 1. app.foo.com and foo.com
// 2. www.foo.com and foo.com
// 3. www.foo.com and app.foo.com
// Only use the tld and the main domain for domain-ish test
// Enables domain-ish equality for blog.foo.com and shop.foo.com
return domainify(currentUrl) === domainify(newUrl);
} catch (err: unknown) {
log.error(
'Failed to parse domains as determining if link is internal. From:',
currentUrl,
'To:',
newUrl,
err,
);
return false;
}
}
export function nativeTabsSupported(): boolean {
return isOSX();
}
/**
* Open the given external protocol URL in the desktop's default manner
* (e.g. `mailto:` URLs in the user's default mail agent), with extra validation.
*/
export function openExternal(
url: string,
options?: OpenExternalOptions,
): Promise<void> {
const urlShellSafety = isUrlShellSafe(url);
log.debug('openExternal', { url, options, urlShellSafety });
if (urlShellSafety.blocked) {
return new Promise((resolve) => {
showNavigationBlockedMessage(
`Navigation blocked to ${url}\n\n${urlShellSafety.reason}`,
)
.then(() => resolve())
.catch((err: unknown) => {
throw err;
});
});
}
return shell.openExternal(url, options);
}
// Copy-pastaed as unable to get imports to work in preload.
// If modifying, update also app/src/preload.ts
export function isWayland(): boolean {
return (
isLinux() &&
(Boolean(process.env.WAYLAND_DISPLAY) ||
process.env.XDG_SESSION_TYPE === 'wayland')
);
}
export function removeUserAgentSpecifics(
userAgentFallback: string,
appName: string,
appVersion: string,
): string {
// Electron userAgentFallback is the user agent used if none is specified when creating a window.
// For our purposes, it's useful because its format is similar enough to a real Chrome's user agent to not need
// to infer the userAgent. userAgentFallback normally looks like this:
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) app-nativefier-804458/1.0.0 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36
// We just need to strip out the appName/1.0.0 and Electron/electronVersion
return userAgentFallback
.replace(`Electron/${process.versions.electron} `, '')
.replace(`${appName}/${appVersion} `, '');
}
/** Removes extra spaces from a text */
export function cleanupPlainText(text: string): string {
return text.trim().replace(/\s+/g, ' ');
}

View File

@ -0,0 +1,90 @@
import * as fs from 'fs';
import * as path from 'path';
import { isOSX, isWindows, isLinux } from './helpers';
import * as log from './loggingHelper';
type fsError = Error & { code: string };
/**
* Find a file or directory
*/
function findSync(
pattern: RegExp,
basePath: string,
limitSearchToDirectories = false,
): string[] {
const matches: string[] = [];
(function findSyncRecurse(base): void {
let children: string[];
try {
children = fs.readdirSync(base);
} catch (err: unknown) {
if ((err as fsError).code === 'ENOENT') {
return;
}
throw err;
}
for (const child of children) {
const childPath = path.join(base, child);
const childIsDirectory = fs.lstatSync(childPath).isDirectory();
const patternMatches = pattern.test(childPath);
if (!patternMatches) {
if (!childIsDirectory) {
return;
}
findSyncRecurse(childPath);
return;
}
if (!limitSearchToDirectories) {
matches.push(childPath);
return;
}
if (childIsDirectory) {
matches.push(childPath);
}
}
})(basePath);
return matches;
}
function findFlashOnLinux(): string {
return findSync(/libpepflashplayer\.so/, '/opt/google/chrome')[0];
}
function findFlashOnWindows(): string {
return findSync(
/pepflashplayer\.dll/,
'C:\\Program Files (x86)\\Google\\Chrome',
)[0];
}
function findFlashOnMac(): string {
return findSync(
/PepperFlashPlayer.plugin/,
'/Applications/Google Chrome.app/',
true,
)[0];
}
export function inferFlashPath(): string | undefined {
if (isOSX()) {
return findFlashOnMac();
}
if (isWindows()) {
return findFlashOnWindows();
}
if (isLinux()) {
return findFlashOnLinux();
}
log.warn('Unable to determine OS to infer flash player');
return undefined;
}

View File

@ -0,0 +1,82 @@
// This helper allows logs to either be printed to the console as they would normally or if
// the USE_LOG_FILE environment variable is set (such as through our playwright tests), then
// the logs can be diverted from the command line to a log file, so that they can be displayed
// later (such as at the end of a playwright test run to help diagnose potential failures).
// Use this instead of loglevel whenever logging messages inside the app.
import * as fs from 'fs';
import * as path from 'path';
import loglevel from 'loglevel';
import { safeGetEnv } from './playwrightHelpers';
const USE_LOG_FILE = safeGetEnv('USE_LOG_FILE') === '1';
const LOG_FILE_DIR = safeGetEnv('LOG_FILE_DIR') ?? process.cwd();
const LOG_FILENAME = path.join(LOG_FILE_DIR, `${new Date().getTime()}.log`);
const logLevelNames = ['TRACE', 'DEBUG', 'INFO ', 'WARN ', 'ERROR'];
function _logger(
logFunc: (...args: unknown[]) => void,
level: loglevel.LogLevelNumbers,
...args: unknown[]
): void {
if (USE_LOG_FILE && loglevel.getLevel() >= level) {
for (const arg of args) {
try {
const lines =
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
JSON.stringify(arg, null, 2)?.split('\n') ?? `${arg}`.split('\n');
for (const line of lines) {
fs.appendFileSync(
LOG_FILENAME,
`${new Date().getTime()} ${logLevelNames[level]} ${line}\n`,
);
}
} catch {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
fs.appendFileSync(LOG_FILENAME, `${logLevelNames[level]} ${arg}\n`);
}
}
} else {
logFunc(...args);
}
}
export function debug(...args: unknown[]): void {
// eslint-disable-next-line @typescript-eslint/unbound-method
_logger(loglevel.debug, loglevel.levels.DEBUG, ...args);
}
export function error(...args: unknown[]): void {
// eslint-disable-next-line @typescript-eslint/unbound-method
_logger(loglevel.error, loglevel.levels.ERROR, ...args);
}
export function info(...args: unknown[]): void {
// eslint-disable-next-line @typescript-eslint/unbound-method
_logger(loglevel.info, loglevel.levels.INFO, ...args);
}
export function log(...args: unknown[]): void {
// eslint-disable-next-line @typescript-eslint/unbound-method
_logger(loglevel.info, loglevel.levels.INFO, ...args);
}
export function setLevel(
level: loglevel.LogLevelDesc,
persist?: boolean,
): void {
loglevel.setLevel(level, persist);
}
export function trace(...args: unknown[]): void {
// eslint-disable-next-line @typescript-eslint/unbound-method
_logger(loglevel.trace, loglevel.levels.TRACE, ...args);
}
export function warn(...args: unknown[]): void {
// eslint-disable-next-line @typescript-eslint/unbound-method
_logger(loglevel.warn, loglevel.levels.WARN, ...args);
}

View File

@ -0,0 +1,6 @@
export const IS_PLAYWRIGHT = safeGetEnv('PLAYWRIGHT_TEST') === '1';
export const PLAYWRIGHT_CONFIG = safeGetEnv('PLAYWRIGHT_CONFIG');
export function safeGetEnv(key: string): string | undefined {
return key in process.env ? process.env[key] : undefined;
}

View File

@ -0,0 +1,353 @@
jest.mock('./helpers');
jest.mock('./windowEvents');
jest.mock('./windowHelpers');
import { dialog, BrowserWindow, HandlerDetails, WebContents } from 'electron';
import { WindowOptions } from '../../../shared/src/options/model';
import { linkIsInternal, openExternal, nativeTabsSupported } from './helpers';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const {
onNewWindowHelper,
onWillNavigate,
onWillPreventUnload,
}: {
onNewWindowHelper: (
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
details: Partial<HandlerDetails>,
parent?: BrowserWindow,
) => ReturnType<Parameters<WebContents['setWindowOpenHandler']>[0]>;
onWillNavigate: (
options: {
blockExternalUrls: boolean;
internalUrls?: string | RegExp;
targetUrl: string;
},
event: unknown,
urlToGo: string,
) => Promise<void>;
onWillPreventUnload: (event: unknown) => void;
} = jest.requireActual('./windowEvents');
import {
showNavigationBlockedMessage,
createAboutBlankWindow,
createNewTab,
} from './windowHelpers';
describe('onNewWindowHelper', () => {
const originalURL = 'https://medium.com/';
const internalURL = 'https://medium.com/topics/technology';
const externalURL = 'https://www.wikipedia.org/wiki/Electron';
const foregroundDisposition = 'foreground-tab';
const backgroundDisposition = 'background-tab';
const baseOptions = {
autoHideMenuBar: true,
blockExternalUrls: false,
insecure: false,
name: 'TEST_APP',
targetUrl: originalURL,
zoom: 1.0,
} as WindowOptions;
const mockShowNavigationBlockedMessage: jest.SpyInstance =
showNavigationBlockedMessage as jest.Mock;
const mockCreateAboutBlank: jest.SpyInstance =
createAboutBlankWindow as jest.Mock;
const mockCreateNewTab: jest.SpyInstance = createNewTab as jest.Mock;
const mockLinkIsInternal: jest.SpyInstance = (
linkIsInternal as jest.Mock
).mockImplementation(() => true);
const mockNativeTabsSupported: jest.SpyInstance =
nativeTabsSupported as jest.Mock;
const mockOpenExternal: jest.SpyInstance = openExternal as jest.Mock;
const setupWindow = jest.fn();
beforeEach(() => {
mockShowNavigationBlockedMessage
.mockReset()
.mockReturnValue(Promise.resolve(undefined));
mockCreateAboutBlank.mockReset();
mockCreateNewTab.mockReset();
mockLinkIsInternal.mockReset().mockReturnValue(true);
mockNativeTabsSupported.mockReset().mockReturnValue(false);
mockOpenExternal.mockReset();
setupWindow.mockReset();
});
afterAll(() => {
mockShowNavigationBlockedMessage.mockRestore();
mockCreateAboutBlank.mockRestore();
mockCreateNewTab.mockRestore();
mockLinkIsInternal.mockRestore();
mockNativeTabsSupported.mockRestore();
mockOpenExternal.mockRestore();
});
test('internal urls should not be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
});
test('external urls should be opened externally', () => {
mockLinkIsInternal.mockReturnValue(false);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: externalURL,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('external urls should be ignored if blockExternalUrls is true', () => {
mockLinkIsInternal.mockReturnValue(false);
const options = {
...baseOptions,
blockExternalUrls: true,
};
const result = onNewWindowHelper(options, setupWindow, {
url: externalURL,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).toHaveBeenCalledTimes(1);
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
});
test('tab disposition should be ignored if tabs are not enabled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: foregroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
});
test('tab disposition should be ignored if url is external', () => {
mockLinkIsInternal.mockReturnValue(false);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: externalURL,
disposition: foregroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).toHaveBeenCalledTimes(1);
expect(result.action).toEqual('deny');
});
test('foreground tabs with internal urls should be opened in the foreground', () => {
mockNativeTabsSupported.mockReturnValue(true);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: foregroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).toHaveBeenCalledWith(
baseOptions,
setupWindow,
internalURL,
true,
);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
});
test('background tabs with internal urls should be opened in background tabs', () => {
mockNativeTabsSupported.mockReturnValue(true);
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: internalURL,
disposition: backgroundDisposition,
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).toHaveBeenCalledWith(
baseOptions,
setupWindow,
internalURL,
false,
);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
});
test('about:blank urls should be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank',
});
expect(mockCreateAboutBlank).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
});
test('about:blank#blocked urls should be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank#blocked',
});
expect(mockCreateAboutBlank).toHaveBeenCalledTimes(1);
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('deny');
});
test('about:blank#other urls should not be handled', () => {
const result = onNewWindowHelper(baseOptions, setupWindow, {
url: 'about:blank#other',
});
expect(mockCreateAboutBlank).not.toHaveBeenCalled();
expect(mockCreateNewTab).not.toHaveBeenCalled();
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(result.action).toEqual('allow');
});
});
describe('onWillNavigate', () => {
const originalURL = 'https://medium.com/';
const internalURL = 'https://medium.com/topics/technology';
const externalURL = 'https://www.wikipedia.org/wiki/Electron';
const mockShowNavigationBlockedMessage: jest.SpyInstance =
showNavigationBlockedMessage as jest.Mock;
const mockLinkIsInternal: jest.SpyInstance = linkIsInternal as jest.Mock;
const mockOpenExternal: jest.SpyInstance = openExternal as jest.Mock;
const preventDefault = jest.fn();
beforeEach(() => {
mockShowNavigationBlockedMessage
.mockReset()
.mockReturnValue(Promise.resolve(undefined));
mockLinkIsInternal.mockReset().mockReturnValue(false);
mockOpenExternal.mockReset();
preventDefault.mockReset();
});
afterAll(() => {
mockShowNavigationBlockedMessage.mockRestore();
mockLinkIsInternal.mockRestore();
mockOpenExternal.mockRestore();
});
test('internal urls should not be handled', async () => {
mockLinkIsInternal.mockReturnValue(true);
const options = {
blockExternalUrls: false,
targetUrl: originalURL,
};
const event = { preventDefault };
await onWillNavigate(options, event, internalURL);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).not.toHaveBeenCalled();
});
test('external urls should be opened externally', async () => {
const options = {
blockExternalUrls: false,
targetUrl: originalURL,
};
const event = { preventDefault };
await onWillNavigate(options, event, externalURL);
expect(mockShowNavigationBlockedMessage).not.toHaveBeenCalled();
expect(mockOpenExternal).toHaveBeenCalledTimes(1);
expect(preventDefault).toHaveBeenCalledTimes(1);
});
test('external urls should be blocked if blockExternalUrls is true', async () => {
const options = {
blockExternalUrls: true,
targetUrl: originalURL,
};
const event = { preventDefault };
await onWillNavigate(options, event, externalURL);
expect(mockShowNavigationBlockedMessage).toHaveBeenCalledTimes(1);
expect(mockOpenExternal).not.toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalledTimes(1);
});
});
describe('onWillPreventUnload', () => {
const mockFromWebContents: jest.SpyInstance = jest
.spyOn(BrowserWindow, 'fromWebContents')
.mockImplementation(() => new BrowserWindow());
const mockShowDialog: jest.SpyInstance = jest.spyOn(
dialog,
'showMessageBoxSync',
);
const preventDefault: jest.SpyInstance = jest.fn();
beforeEach(() => {
mockFromWebContents.mockReset();
mockShowDialog.mockReset().mockReturnValue(undefined);
preventDefault.mockReset();
});
afterAll(() => {
mockFromWebContents.mockRestore();
mockShowDialog.mockRestore();
});
test('with no sender', () => {
const event = {};
onWillPreventUnload(event);
expect(mockFromWebContents).not.toHaveBeenCalled();
expect(mockShowDialog).not.toHaveBeenCalled();
expect(preventDefault).not.toHaveBeenCalled();
});
test('shows dialog and calls preventDefault on ok', () => {
mockShowDialog.mockReturnValue(0);
const event = { preventDefault, sender: {} };
onWillPreventUnload(event);
expect(mockFromWebContents).toHaveBeenCalledWith(event.sender);
expect(mockShowDialog).toHaveBeenCalled();
expect(preventDefault).toHaveBeenCalledWith();
});
test('shows dialog and does not call preventDefault on cancel', () => {
mockShowDialog.mockReturnValue(1);
const event = { preventDefault, sender: {} };
onWillPreventUnload(event);
expect(mockFromWebContents).toHaveBeenCalledWith(event.sender);
expect(mockShowDialog).toHaveBeenCalled();
expect(preventDefault).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,188 @@
import {
dialog,
BrowserWindow,
Event,
WebContents,
HandlerDetails,
} from 'electron';
import { linkIsInternal, nativeTabsSupported, openExternal } from './helpers';
import * as log from './loggingHelper';
import {
createAboutBlankWindow,
createNewTab,
injectCSS,
sendParamsOnDidFinishLoad,
setProxyRules,
showNavigationBlockedMessage,
} from './windowHelpers';
import { WindowOptions } from '../../../shared/src/options/model';
type NewWindowHandlerResult = ReturnType<
Parameters<WebContents['setWindowOpenHandler']>[0]
>;
export function onNewWindow(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
details: HandlerDetails,
parent?: BrowserWindow,
): NewWindowHandlerResult {
log.debug('onNewWindow', {
details,
});
return onNewWindowHelper(
options,
setupWindow,
details,
nativeTabsSupported() ? undefined : parent,
);
}
export function onNewWindowHelper(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
details: HandlerDetails,
parent?: BrowserWindow,
): NewWindowHandlerResult {
log.debug('onNewWindowHelper', {
options,
details,
});
try {
if (
!linkIsInternal(
options.targetUrl,
details.url,
options.internalUrls,
options.strictInternalUrls,
)
) {
if (options.blockExternalUrls) {
showNavigationBlockedMessage(
`Navigation to external URL blocked by options: ${details.url}`,
)
.then(() => {
// blockExternalURL(details.url).then(resolve).catch((err: unknown) => {
// log.error('blockExternalURL', err);
// });
})
.catch((err: unknown) => {
throw err;
});
return { action: 'deny' };
} else {
openExternal(details.url).catch((err: unknown) => {
log.error('openExternal', err);
});
return { action: 'deny' };
}
}
// Normally the following would be:
// if (urlToGo.startsWith('about:blank'))...
// But due to a bug we resolved in https://github.com/nativefier/nativefier/issues/1197
// Some sites use about:blank#something to use as placeholder windows to fill
// with content via JavaScript. So we'll stay specific for now...
else if (['about:blank', 'about:blank#blocked'].includes(details.url)) {
createAboutBlankWindow(
options,
setupWindow,
nativeTabsSupported() ? undefined : parent,
);
return { action: 'deny' };
} else if (nativeTabsSupported()) {
createNewTab(
options,
setupWindow,
details.url,
details.disposition === 'foreground-tab',
);
return { action: 'deny' };
}
return { action: 'allow' };
} catch (err: unknown) {
return { action: 'deny' };
}
}
export function onWillNavigate(
options: WindowOptions,
event: Event,
urlToGo: string,
): Promise<void> {
log.debug('onWillNavigate', urlToGo);
if (
!linkIsInternal(
options.targetUrl,
urlToGo,
options.internalUrls,
options.strictInternalUrls,
)
) {
event.preventDefault();
if (options.blockExternalUrls) {
return new Promise((resolve) => {
showNavigationBlockedMessage(
`Navigation to external URL blocked by options: ${urlToGo}`,
)
.then(() => resolve())
.catch((err: unknown) => {
throw err;
});
});
} else {
return openExternal(urlToGo);
}
}
return Promise.resolve(undefined);
}
export function onWillPreventUnload(
event: Event & { sender?: WebContents },
): void {
log.debug('onWillPreventUnload', event);
const webContents = event.sender;
if (!webContents) {
return;
}
const browserWindow =
BrowserWindow.fromWebContents(webContents) ??
BrowserWindow.getFocusedWindow();
if (browserWindow) {
const choice = dialog.showMessageBoxSync(browserWindow, {
type: 'question',
buttons: ['Proceed', 'Stay'],
message:
'You may have unsaved changes, are you sure you want to proceed?',
title: 'Changes you made may not be saved.',
defaultId: 0,
cancelId: 1,
});
if (choice === 0) {
event.preventDefault();
}
}
}
export function setupNativefierWindow(
options: WindowOptions,
window: BrowserWindow,
): void {
if (options.proxyRules) {
setProxyRules(window, options.proxyRules);
}
injectCSS(window);
window.webContents.on('will-navigate', (event: Event, url: string) => {
onWillNavigate(options, event, url).catch((err) => {
log.error('window.webContents.on.will-navigate ERROR', err);
event.preventDefault();
});
});
window.webContents.on('will-prevent-unload', onWillPreventUnload);
sendParamsOnDidFinishLoad(options, window);
}

View File

@ -0,0 +1,292 @@
import { dialog, BrowserWindow } from 'electron';
jest.mock('loglevel');
import { error } from 'loglevel';
import { WindowOptions } from '../../../shared/src/options/model';
jest.mock('./helpers');
import { getCSSToInject } from './helpers';
jest.mock('./windowEvents');
import { clearAppData, createNewTab, injectCSS } from './windowHelpers';
describe('clearAppData', () => {
let window: BrowserWindow;
let mockClearCache: jest.SpyInstance;
let mockClearStorageData: jest.SpyInstance;
const mockShowDialog: jest.SpyInstance = jest.spyOn(dialog, 'showMessageBox');
beforeEach(() => {
window = new BrowserWindow();
mockClearCache = jest.spyOn(window.webContents.session, 'clearCache');
mockClearStorageData = jest.spyOn(
window.webContents.session,
'clearStorageData',
);
mockShowDialog.mockReset().mockResolvedValue(undefined);
});
afterAll(() => {
mockClearCache.mockRestore();
mockClearStorageData.mockRestore();
mockShowDialog.mockRestore();
});
test('will not clear app data if dialog canceled', async () => {
mockShowDialog.mockResolvedValue(1);
await clearAppData(window);
expect(mockShowDialog).toHaveBeenCalledTimes(1);
expect(mockClearCache).not.toHaveBeenCalled();
expect(mockClearStorageData).not.toHaveBeenCalled();
});
test('will clear app data if ok is clicked', async () => {
mockShowDialog.mockResolvedValue(0);
await clearAppData(window);
expect(mockShowDialog).toHaveBeenCalledTimes(1);
expect(mockClearCache).not.toHaveBeenCalledTimes(1);
expect(mockClearStorageData).not.toHaveBeenCalledTimes(1);
});
});
describe('createNewTab', () => {
// const window = new BrowserWindow();
const options: WindowOptions = {
autoHideMenuBar: true,
blockExternalUrls: false,
insecure: false,
name: 'Test App',
targetUrl: 'https://github.com/nativefier/natifefier',
zoom: 1.0,
} as WindowOptions;
const setupWindow = jest.fn();
const url = 'https://github.com/nativefier/nativefier';
const mockAddTabbedWindow: jest.SpyInstance = jest.spyOn(
BrowserWindow.prototype,
'addTabbedWindow',
);
const mockFocus: jest.SpyInstance = jest.spyOn(
BrowserWindow.prototype,
'focus',
);
const mockLoadURL: jest.SpyInstance = jest.spyOn(
BrowserWindow.prototype,
'loadURL',
);
test('creates new foreground tab', () => {
const foreground = true;
const tab = createNewTab(options, setupWindow, url, foreground);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);
expect(mockLoadURL).toHaveBeenCalledWith(url);
expect(mockFocus).not.toHaveBeenCalled();
});
test('creates new background tab', () => {
const foreground = false;
const tab = createNewTab(
options,
setupWindow,
url,
foreground,
// window
);
expect(mockAddTabbedWindow).toHaveBeenCalledWith(tab);
expect(setupWindow).toHaveBeenCalledWith(options, tab);
expect(mockLoadURL).toHaveBeenCalledWith(url);
expect(mockFocus).toHaveBeenCalledTimes(1);
});
});
describe('injectCSS', () => {
jest.setTimeout(10000);
const mockGetCSSToInject: jest.SpyInstance = getCSSToInject as jest.Mock;
const mockLogError: jest.SpyInstance = error as jest.Mock;
const css = 'body { color: white; }';
let responseHeaders: Record<string, string[]>;
beforeEach(() => {
mockGetCSSToInject.mockReset().mockReturnValue('');
mockLogError.mockReset();
responseHeaders = { 'x-header': ['value'], 'content-type': ['test/other'] };
});
afterAll(() => {
mockGetCSSToInject.mockRestore();
mockLogError.mockRestore();
});
test('will not inject if getCSSToInject is empty', () => {
const window = new BrowserWindow();
const mockWebContentsInsertCSS: jest.SpyInstance = jest
.spyOn(window.webContents, 'insertCSS')
.mockResolvedValue('');
jest
.spyOn(window.webContents, 'getURL')
.mockReturnValue('https://example.com');
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
expect(mockWebContentsInsertCSS).not.toHaveBeenCalled();
});
test('will inject on did-navigate + onResponseStarted', () => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
const mockWebContentsInsertCSS: jest.SpyInstance = jest
.spyOn(window.webContents, 'insertCSS')
.mockResolvedValue('');
jest
.spyOn(window.webContents, 'getURL')
.mockReturnValue('https://example.com');
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
window.webContents.session.webRequest.send('onResponseStarted', {
responseHeaders,
webContents: window.webContents,
});
expect(mockWebContentsInsertCSS).toHaveBeenCalledWith(css);
});
test.each<string>(['application/json', 'font/woff2', 'image/png'])(
'will not inject for content-type %s',
(contentType: string) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
const mockWebContentsInsertCSS: jest.SpyInstance = jest
.spyOn(window.webContents, 'insertCSS')
.mockResolvedValue('');
jest
.spyOn(window.webContents, 'getURL')
.mockReturnValue('https://example.com');
responseHeaders['content-type'] = [contentType];
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
expect(window.webContents.emit('did-navigate')).toBe(true);
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
window.webContents.session.webRequest.send('onResponseStarted', {
responseHeaders,
webContents: window.webContents,
url: `test-${contentType}`,
});
// insertCSS will still run once for the did-navigate
expect(mockWebContentsInsertCSS).not.toHaveBeenCalled();
},
);
test.each<string>(['text/html'])(
'will inject for content-type %s',
(contentType: string) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
const mockWebContentsInsertCSS: jest.SpyInstance = jest
.spyOn(window.webContents, 'insertCSS')
.mockResolvedValue('');
jest
.spyOn(window.webContents, 'getURL')
.mockReturnValue('https://example.com');
responseHeaders['content-type'] = [contentType];
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
window.webContents.session.webRequest.send('onResponseStarted', {
responseHeaders,
webContents: window.webContents,
url: `test-${contentType}`,
});
expect(mockWebContentsInsertCSS).toHaveBeenCalledTimes(1);
},
);
test.each<string>(['image', 'script', 'stylesheet', 'xhr'])(
'will not inject for resource type %s',
(resourceType: string) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
const mockWebContentsInsertCSS: jest.SpyInstance = jest
.spyOn(window.webContents, 'insertCSS')
.mockResolvedValue('');
jest
.spyOn(window.webContents, 'getURL')
.mockReturnValue('https://example.com');
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
window.webContents.session.webRequest.send('onResponseStarted', {
responseHeaders,
webContents: window.webContents,
resourceType,
url: `test-${resourceType}`,
});
// insertCSS will still run once for the did-navigate
expect(mockWebContentsInsertCSS).not.toHaveBeenCalled();
},
);
test.each<string>(['html', 'other'])(
'will inject for resource type %s',
(resourceType: string) => {
mockGetCSSToInject.mockReturnValue(css);
const window = new BrowserWindow();
const mockWebContentsInsertCSS: jest.SpyInstance = jest
.spyOn(window.webContents, 'insertCSS')
.mockResolvedValue('');
jest
.spyOn(window.webContents, 'getURL')
.mockReturnValue('https://example.com');
injectCSS(window);
expect(mockGetCSSToInject).toHaveBeenCalled();
window.webContents.emit('did-navigate');
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
window.webContents.session.webRequest.send('onResponseStarted', {
responseHeaders,
webContents: window.webContents,
resourceType,
url: `test-${resourceType}`,
});
expect(mockWebContentsInsertCSS).toHaveBeenCalledTimes(1);
},
);
});

View File

@ -0,0 +1,365 @@
import path from 'path';
import {
dialog,
BrowserWindow,
BrowserWindowConstructorOptions,
Event,
MessageBoxReturnValue,
WebPreferences,
OnResponseStartedListenerDetails,
} from 'electron';
import { getCSSToInject, isOSX, nativeTabsSupported } from './helpers';
import * as log from './loggingHelper';
import { TrayValue, WindowOptions } from '../../../shared/src/options/model';
import { randomUUID } from 'crypto';
const ZOOM_INTERVAL = 0.1;
export function adjustWindowZoom(adjustment: number): void {
withFocusedWindow((focusedWindow: BrowserWindow) => {
focusedWindow.webContents.zoomFactor =
focusedWindow.webContents.zoomFactor + adjustment;
});
}
export function showNavigationBlockedMessage(
message: string,
): Promise<MessageBoxReturnValue> {
return new Promise((resolve, reject) => {
withFocusedWindow((focusedWindow) => {
dialog
.showMessageBox(focusedWindow, {
message,
type: 'error',
title: 'Navigation blocked',
})
.then((result) => resolve(result))
.catch((err) => {
reject(err);
});
});
});
}
export async function clearAppData(window: BrowserWindow): Promise<void> {
const response = await dialog.showMessageBox(window, {
type: 'warning',
buttons: ['Yes', 'Cancel'],
defaultId: 1,
title: 'Clear cache confirmation',
message:
'This will clear all data (cookies, local storage etc) from this app. Are you sure you wish to proceed?',
});
if (response.response !== 0) {
return;
}
await clearCache(window);
}
export async function clearCache(window: BrowserWindow): Promise<void> {
const { session } = window.webContents;
await session.clearStorageData();
await session.clearCache();
}
export function createAboutBlankWindow(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
parent?: BrowserWindow,
): BrowserWindow {
const window = createNewWindow(
{ ...options, show: false },
setupWindow,
'about:blank',
nativeTabsSupported() ? undefined : parent,
);
window.webContents.once('did-stop-loading', () => {
if (window.webContents.getURL() === 'about:blank') {
window.close();
} else {
window.show();
}
});
return window;
}
export function createNewTab(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
url: string,
foreground: boolean,
): BrowserWindow | undefined {
const focusedWindow = BrowserWindow.getFocusedWindow();
log.debug('createNewTab', {
url,
foreground,
focusedWindow,
});
return withFocusedWindow((focusedWindow) => {
const newTab = createNewWindow(options, setupWindow, url);
log.debug('createNewTab.withFocusedWindow', { focusedWindow, newTab });
focusedWindow.addTabbedWindow(newTab);
if (!foreground) {
focusedWindow.focus();
}
return newTab;
});
}
export function createNewWindow(
options: WindowOptions,
setupWindow: (options: WindowOptions, window: BrowserWindow) => void,
url: string,
parent?: BrowserWindow,
): BrowserWindow {
log.debug('createNewWindow', {
url,
parent,
});
const window = new BrowserWindow({
parent: nativeTabsSupported() ? undefined : parent,
...getDefaultWindowOptions(options),
});
setupWindow(options, window);
window.loadURL(url).catch((err) => log.error('window.loadURL ERROR', err));
return window;
}
export function getCurrentURL(): string {
return withFocusedWindow((focusedWindow) =>
focusedWindow.webContents.getURL(),
) as unknown as string;
}
export function getDefaultWindowOptions(
options: WindowOptions,
): BrowserWindowConstructorOptions {
const browserwindowOptions: BrowserWindowConstructorOptions = {
...options.browserwindowOptions,
};
// We're going to remove this and merge it separately into DEFAULT_WINDOW_OPTIONS.webPreferences
// Otherwise the browserwindowOptions.webPreferences object will completely replace the
// webPreferences specified in the DEFAULT_WINDOW_OPTIONS with itself
delete browserwindowOptions.webPreferences;
const webPreferences: WebPreferences = {
...(options.browserwindowOptions?.webPreferences ?? {}),
};
const defaultOptions: BrowserWindowConstructorOptions = {
autoHideMenuBar: options.autoHideMenuBar,
fullscreenable: true,
tabbingIdentifier: nativeTabsSupported()
? options.tabbingIdentifier ?? randomUUID()
: undefined,
title: options.name,
webPreferences: {
javascript: true,
nodeIntegration: false, // `true` is *insecure*, and cause trouble with messenger.com
preload: path.join(__dirname, 'preload.js'),
plugins: true,
sandbox: false, // https://www.electronjs.org/blog/electron-20-0#default-changed-renderers-without-nodeintegration-true-are-sandboxed-by-default
webSecurity: !options.insecure,
zoomFactor: options.zoom,
// `contextIsolation` was switched to true in Electron 12, which:
// 1. Breaks access to global variables in `--inject`-ed scripts:
// https://github.com/nativefier/nativefier/issues/1269
// 2. Might break notifications under Windows, although this was refuted:
// https://github.com/nativefier/nativefier/issues/1292
// So, it was flipped to false in https://github.com/nativefier/nativefier/pull/1308
//
// If attempting to set it back to `true` (for security),
// do test exhaustively these two areas, and more.
contextIsolation: false,
...webPreferences,
},
...browserwindowOptions,
};
log.debug('getDefaultWindowOptions', {
options,
webPreferences,
defaultOptions,
});
return defaultOptions;
}
export function goBack(): void {
log.debug('onGoBack');
withFocusedWindow((focusedWindow) => {
focusedWindow.webContents.goBack();
});
}
export function goForward(): void {
log.debug('onGoForward');
withFocusedWindow((focusedWindow) => {
focusedWindow.webContents.goForward();
});
}
export function goToURL(url: string): Promise<void> | undefined {
return withFocusedWindow((focusedWindow) => focusedWindow.loadURL(url));
}
export function hideWindow(
window: BrowserWindow,
event: Event,
fastQuit: boolean,
tray: TrayValue,
): void {
if (isOSX() && !fastQuit) {
// this is called when exiting from clicking the cross button on the window
event.preventDefault();
window.hide();
} else if (!fastQuit && tray !== 'false') {
event.preventDefault();
window.hide();
}
// will close the window on other platforms
}
export function injectCSS(browserWindow: BrowserWindow): void {
const cssToInject = getCSSToInject();
if (!cssToInject) {
return;
}
browserWindow.webContents.on('did-navigate', () => {
log.debug(
'browserWindow.webContents.did-navigate',
browserWindow.webContents.getURL(),
);
browserWindow.webContents
.insertCSS(cssToInject)
.catch((err: unknown) =>
log.error('browserWindow.webContents.insertCSS', err),
);
// We must inject css early enough; so onResponseStarted is a good place.
browserWindow.webContents.session.webRequest.onResponseStarted(
{ urls: [] }, // Pass an empty filter list; null will not match _any_ urls
(details: OnResponseStartedListenerDetails): void => {
log.debug('onResponseStarted', {
resourceType: details.resourceType,
url: details.url,
});
injectCSSIntoResponse(details, cssToInject).catch((err: unknown) => {
log.error('injectCSSIntoResponse ERROR', err);
});
},
);
});
}
function injectCSSIntoResponse(
details: OnResponseStartedListenerDetails,
cssToInject: string,
): Promise<string | undefined> {
const contentType =
details.responseHeaders && 'content-type' in details.responseHeaders
? details.responseHeaders['content-type'][0]
: undefined;
log.debug('injectCSSIntoResponse', { details, cssToInject, contentType });
// We go with a denylist rather than a whitelist (e.g. only text/html)
// to avoid "whoops I didn't think this should have been CSS-injected" cases
const nonInjectableContentTypes = [
/application\/.*/,
/font\/.*/,
/image\/.*/,
];
const nonInjectableResourceTypes = ['image', 'script', 'stylesheet', 'xhr'];
if (
(contentType &&
nonInjectableContentTypes.filter((x) => {
const matches = x.exec(contentType);
return matches && matches?.length > 0;
})?.length > 0) ||
nonInjectableResourceTypes.includes(details.resourceType) ||
!details.webContents
) {
log.debug(
`Skipping CSS injection for:\n${details.url}\nwith resourceType ${
details.resourceType
} and content-type ${contentType as string}`,
);
return Promise.resolve(undefined);
}
log.debug(
`Injecting CSS for:\n${details.url}\nwith resourceType ${
details.resourceType
} and content-type ${contentType as string}`,
);
return details.webContents.insertCSS(cssToInject);
}
export function sendParamsOnDidFinishLoad(
options: WindowOptions,
window: BrowserWindow,
): void {
window.webContents.on('did-finish-load', () => {
log.debug(
'sendParamsOnDidFinishLoad.window.webContents.did-finish-load',
window.webContents.getURL(),
);
// In children windows too: Restore pinch-to-zoom, disabled by default in recent Electron.
// See https://github.com/nativefier/nativefier/issues/379#issuecomment-598612128
// and https://github.com/electron/electron/pull/12679
window.webContents
.setVisualZoomLevelLimits(1, 3)
.catch((err) => log.error('webContents.setVisualZoomLevelLimits', err));
window.webContents.send('params', JSON.stringify(options));
});
}
export function setProxyRules(
window: BrowserWindow,
proxyRules?: string,
): void {
window.webContents.session
.setProxy({
proxyRules,
pacScript: '',
proxyBypassRules: '',
})
.catch((err) => log.error('session.setProxy ERROR', err));
}
export function withFocusedWindow<T>(
block: (window: BrowserWindow) => T,
): T | undefined {
const focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow) {
return block(focusedWindow);
}
return undefined;
}
export function zoomOut(): void {
log.debug('zoomOut');
adjustWindowZoom(-ZOOM_INTERVAL);
}
export function zoomReset(options: { zoom?: number }): void {
log.debug('zoomReset');
withFocusedWindow((focusedWindow) => {
focusedWindow.webContents.zoomFactor = options.zoom ?? 1.0;
});
}
export function zoomIn(): void {
log.debug('zoomIn');
adjustWindowZoom(ZOOM_INTERVAL);
}

458
app/src/main.ts Normal file
View File

@ -0,0 +1,458 @@
import 'source-map-support/register';
import fs from 'fs';
import * as path from 'path';
import electron, {
app,
dialog,
globalShortcut,
systemPreferences,
BrowserWindow,
Event,
} from 'electron';
import electronDownload from 'electron-dl';
import { createLoginWindow } from './components/loginWindow';
import {
createMainWindow,
saveAppArgs,
APP_ARGS_FILE_PATH,
} from './components/mainWindow';
import { createTrayIcon } from './components/trayIcon';
import {
isOSX,
isWayland,
isWindows,
removeUserAgentSpecifics,
} from './helpers/helpers';
import { inferFlashPath } from './helpers/inferFlash';
import * as log from './helpers/loggingHelper';
import {
IS_PLAYWRIGHT,
PLAYWRIGHT_CONFIG,
safeGetEnv,
} from './helpers/playwrightHelpers';
import { OutputOptions } from '../../shared/src/options/model';
// Entrypoint for Squirrel, a windows update framework. See https://github.com/nativefier/nativefier/pull/744
if (require('electron-squirrel-startup')) {
app.exit();
}
if (process.argv.indexOf('--verbose') > -1 || safeGetEnv('VERBOSE') === '1') {
log.setLevel('DEBUG');
process.traceDeprecation = true;
process.traceProcessWarnings = true;
process.argv.slice(1);
}
let mainWindow: BrowserWindow;
const appArgs =
IS_PLAYWRIGHT && PLAYWRIGHT_CONFIG
? (JSON.parse(PLAYWRIGHT_CONFIG) as OutputOptions)
: (JSON.parse(
fs.readFileSync(APP_ARGS_FILE_PATH, 'utf8'),
) as OutputOptions);
log.debug('appArgs', appArgs);
// Do this relatively early so that we can start storing appData with the app
if (appArgs.portable) {
log.debug(
'App was built as portable; setting appData and userData to the app folder: ',
path.resolve(path.join(__dirname, '..', 'appData')),
);
app.setPath('appData', path.join(__dirname, '..', 'appData'));
app.setPath('userData', path.join(__dirname, '..', 'appData'));
}
if (!appArgs.userAgentHonest) {
if (appArgs.userAgent) {
app.userAgentFallback = appArgs.userAgent;
} else {
app.userAgentFallback = removeUserAgentSpecifics(
app.userAgentFallback,
app.getName(),
app.getVersion(),
);
}
}
// this step is required to allow app names to be displayed correctly in notifications on windows
// https://www.electronjs.org/docs/latest/api/app#appsetappusermodelidid-windows
// https://www.electronjs.org/docs/latest/tutorial/notifications#windows
if (isWindows()) {
app.setAppUserModelId(app.getName());
}
const urlArgv = process.argv.filter((a) => a.startsWith('http'));
// Take in a URL on the command line as an override
if (urlArgv.length > 0) {
const maybeUrl = urlArgv[0];
try {
new URL(maybeUrl);
appArgs.targetUrl = maybeUrl;
log.info('Loading override URL passed as argument:', maybeUrl);
} catch (err: unknown) {
log.error(
'Not loading override URL passed as argument, because failed to parse:',
maybeUrl,
err,
);
}
}
// Nativefier is a browser, and an old browser is an insecure / badly-performant one.
// Given our builder/app design, we currently don't have an easy way to offer
// upgrades from the app themselves (like browsers do).
// As a workaround, we ask for a manual upgrade & re-build if the build is old.
// The period in days is chosen to be not too small to be exceedingly annoying,
// but not too large to be exceedingly insecure.
const OLD_BUILD_WARNING_THRESHOLD_DAYS = 90;
const OLD_BUILD_WARNING_THRESHOLD_MS =
OLD_BUILD_WARNING_THRESHOLD_DAYS * 24 * 60 * 60 * 1000;
const fileDownloadOptions = { ...appArgs.fileDownloadOptions };
electronDownload(fileDownloadOptions);
if (appArgs.processEnvs) {
let processEnvs: Record<string, string> =
appArgs.processEnvs as unknown as Record<string, string>;
// This is compatibility if just a string was passed.
if (typeof appArgs.processEnvs === 'string') {
try {
processEnvs = JSON.parse(appArgs.processEnvs) as Record<string, string>;
} catch {
// This wasn't JSON. Fall back to the old code
processEnvs = {};
process.env.processEnvs = appArgs.processEnvs;
}
}
Object.keys(processEnvs)
.filter((key) => key !== undefined)
.forEach((key) => {
process.env[key] = processEnvs[key];
});
}
if (typeof appArgs.flashPluginDir === 'string') {
app.commandLine.appendSwitch('ppapi-flash-path', appArgs.flashPluginDir);
} else if (appArgs.flashPluginDir) {
const flashPath = inferFlashPath();
app.commandLine.appendSwitch('ppapi-flash-path', flashPath);
}
if (appArgs.ignoreCertificate) {
app.commandLine.appendSwitch('ignore-certificate-errors');
}
if (appArgs.disableGpu) {
app.disableHardwareAcceleration();
}
if (appArgs.ignoreGpuBlacklist) {
app.commandLine.appendSwitch('ignore-gpu-blacklist');
}
if (appArgs.enableEs3Apis) {
app.commandLine.appendSwitch('enable-es3-apis');
}
if (appArgs.diskCacheSize) {
app.commandLine.appendSwitch(
'disk-cache-size',
appArgs.diskCacheSize.toString(),
);
}
if (appArgs.basicAuthUsername) {
app.commandLine.appendSwitch(
'basic-auth-username',
appArgs.basicAuthUsername,
);
}
if (appArgs.basicAuthPassword) {
app.commandLine.appendSwitch(
'basic-auth-password',
appArgs.basicAuthPassword,
);
}
if (isWayland()) {
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
}
if (appArgs.lang) {
const langParts = appArgs.lang.split(',');
// Convert locales to languages, because for some reason locales don't work. Stupid Chromium
const langPartsParsed = Array.from(
// Convert to set to dedupe in case something like "en-GB,en-US" was passed
new Set(langParts.map((l) => l.split('-')[0])),
);
const langFlag = langPartsParsed.join(',');
log.debug('Setting --lang flag to', langFlag);
app.commandLine.appendSwitch('--lang', langFlag);
}
let currentBadgeCount = 0;
const setDockBadge = isOSX()
? (count?: number | string, bounce = false): void => {
if (count !== undefined) {
app.dock.setBadge(count.toString());
if (bounce && typeof count === 'number' && count > currentBadgeCount)
app.dock.bounce();
currentBadgeCount = typeof count === 'number' ? count : 0;
}
}
: (): void => undefined;
app.on('window-all-closed', () => {
log.debug('app.window-all-closed');
if (!isOSX() || appArgs.fastQuit || IS_PLAYWRIGHT) {
app.quit();
}
});
app.on('before-quit', () => {
log.debug('app.before-quit');
// not fired when the close button on the window is clicked
if (isOSX()) {
// need to force a quit as a workaround here to simulate the osx app hiding behaviour
// Somehow sokution at https://github.com/atom/electron/issues/444#issuecomment-76492576 does not work,
// e.prevent default appears to persist
// might cause issues in the future as before-quit and will-quit events are not called
app.exit(0);
}
});
app.on('will-quit', (event) => {
log.debug('app.will-quit', event);
});
app.on('quit', (event, exitCode) => {
log.debug('app.quit', { event, exitCode });
});
app.on('will-finish-launching', () => {
log.debug('app.will-finish-launching');
});
app.on('open-url', (event, url) => {
log.debug('app.open-url', { event, url });
event.preventDefault();
if (mainWindow) {
mainWindow.webContents.send('open-url', url);
}
});
if (appArgs.widevine) {
// @ts-expect-error This event only appears on the widevine version of electron, which we'd see at runtime
app.on('widevine-ready', (version: string, lastVersion: string) => {
log.debug('app.widevine-ready', { version, lastVersion });
onReady().catch((err) => log.error('onReady ERROR', err));
});
app.on(
// @ts-expect-error This event only appears on the widevine version of electron, which we'd see at runtime
'widevine-update-pending',
(currentVersion: string, pendingVersion: string) => {
log.debug('app.widevine-update-pending', {
currentVersion,
pendingVersion,
});
},
);
// @ts-expect-error This event only appears on the widevine version of electron, which we'd see at runtime
app.on('widevine-error', (error: Error) => {
log.error('app.widevine-error', error);
});
} else {
app.on('ready', () => {
log.debug('ready');
onReady().catch((err) => log.error('onReady ERROR', err));
});
}
app.on('activate', (event: electron.Event, hasVisibleWindows: boolean) => {
log.debug('app.activate', { event, hasVisibleWindows });
if (isOSX() && !IS_PLAYWRIGHT) {
// this is called when the dock is clicked
if (!hasVisibleWindows) {
mainWindow.show();
}
}
});
// quit if singleInstance mode and there's already another instance running
const shouldQuit = appArgs.singleInstance && !app.requestSingleInstanceLock();
if (shouldQuit) {
app.quit();
} else {
app.on('second-instance', () => {
log.debug('app.second-instance');
if (mainWindow) {
if (!mainWindow.isVisible()) {
// try
mainWindow.show();
}
if (mainWindow.isMinimized()) {
// minimized
mainWindow.restore();
}
mainWindow.focus();
}
});
}
app.on('new-window-for-tab', (event: Event) => {
log.debug('app.new-window-for-tab', { event });
if (mainWindow) {
mainWindow.emit('new-window-for-tab', event);
}
});
app.on(
'login',
(
event,
webContents,
request,
authInfo,
callback: (username?: string, password?: string) => void,
) => {
log.debug('app.login', { event, request });
// for http authentication
event.preventDefault();
if (appArgs.basicAuthUsername && appArgs.basicAuthPassword) {
callback(appArgs.basicAuthUsername, appArgs.basicAuthPassword);
} else {
createLoginWindow(
callback,
// mainWindow
).catch((err) => log.error('createLoginWindow ERROR', err));
}
},
);
async function onReady(): Promise<void> {
// Warning: `mainWindow` below is the *global* unique `mainWindow`, created at init time
mainWindow = await createMainWindow(appArgs, setDockBadge);
createTrayIcon(appArgs, mainWindow);
// Register global shortcuts
if (appArgs.globalShortcuts) {
appArgs.globalShortcuts.forEach((shortcut) => {
globalShortcut.register(shortcut.key, () => {
shortcut.inputEvents.forEach((inputEvent) => {
// @ts-expect-error without including electron in our models, these will never match
mainWindow.webContents.sendInputEvent(inputEvent);
});
});
});
if (isOSX() && appArgs.accessibilityPrompt) {
const mediaKeys = [
'MediaPlayPause',
'MediaNextTrack',
'MediaPreviousTrack',
'MediaStop',
];
const globalShortcutsKeys = appArgs.globalShortcuts.map((g) => g.key);
const mediaKeyWasSet = globalShortcutsKeys.find((g) =>
mediaKeys.includes(g),
);
if (
mediaKeyWasSet &&
!systemPreferences.isTrustedAccessibilityClient(false)
) {
// Since we're trying to set global keyboard shortcuts for media keys, we need to prompt
// the user for permission on Mac.
// For reference:
// https://www.electronjs.org/docs/api/global-shortcut?q=MediaPlayPause#globalshortcutregisteraccelerator-callback
const accessibilityPromptResult = dialog.showMessageBoxSync(
mainWindow,
{
type: 'question',
message: 'Accessibility Permissions Needed',
buttons: ['Yes', 'No', 'No and never ask again'],
defaultId: 0,
detail:
`${appArgs.name} would like to use one or more of your keyboard's media keys (start, stop, next track, or previous track) to control it.\n\n` +
`Would you like Mac OS to ask for your permission to do so?\n\n` +
`If so, you will need to restart ${appArgs.name} after granting permissions for these keyboard shortcuts to begin working.`,
},
);
switch (accessibilityPromptResult) {
// User clicked Yes, prompt for accessibility
case 0:
systemPreferences.isTrustedAccessibilityClient(true);
break;
// User cliecked Never Ask Me Again, save that info
case 2:
appArgs.accessibilityPrompt = false;
saveAppArgs(appArgs);
break;
// User clicked No
default:
break;
}
}
}
}
if (
!appArgs.disableOldBuildWarning &&
new Date().getTime() - appArgs.buildDate > OLD_BUILD_WARNING_THRESHOLD_MS
) {
const oldBuildWarningText =
appArgs.oldBuildWarningText ||
'This app was built a long time ago. Nativefier uses the Chrome browser (through Electron), and it is insecure to keep using an old version of it. Please upgrade Nativefier and rebuild this app.';
dialog
.showMessageBox(mainWindow, {
type: 'warning',
message: 'Old build detected',
detail: oldBuildWarningText,
})
.catch((err) => log.error('dialog.showMessageBox ERROR', err));
}
if (appArgs.targetUrl) {
await mainWindow.loadURL(appArgs.targetUrl);
}
}
app.on(
'accessibility-support-changed',
(event: Event, accessibilitySupportEnabled: boolean) => {
log.debug('app.accessibility-support-changed', {
event,
accessibilitySupportEnabled,
});
},
);
app.on(
'activity-was-continued',
(event: Event, type: string, userInfo: unknown) => {
log.debug('app.activity-was-continued', { event, type, userInfo });
},
);
app.on('browser-window-blur', () => {
log.debug('app.browser-window-blur');
});
app.on('browser-window-created', () => {
log.debug('app.browser-window-created');
});
app.on('browser-window-focus', () => {
log.debug('app.browser-window-focus');
});

160
app/src/mocks/electron.ts Normal file
View File

@ -0,0 +1,160 @@
/* eslint-disable @typescript-eslint/no-extraneous-class */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { EventEmitter } from 'events';
/*
These mocks are PURPOSEFULLY minimal. A few reasons as to why:
1. I'm l̶a̶z̶y̶ a busy person :)
2. The less we have in here, the less we'll need to fix if an electron API changes
3. Only mocking what we need as we need it helps reveal areas under test where electron
is being accessed in previously unaccounted for ways
4. These mocks will get fleshed out as more unit tests are added, so if you need
something here as you are adding unit tests, then feel free to add exactly what you
need (and no more than that please).
As well, please resist the urge to turn this into a reimplimentation of electron.
When adding functions/classes, keep your implementation to only the minimal amount of code
it takes for TypeScript to recognize what you are doing. For anything more complex (including
implementation code and return values) please do that within your tests via jest with
mockImplementation or mockReturnValue.
*/
class MockBrowserWindow extends EventEmitter {
webContents: MockWebContents;
constructor(options?: unknown) {
// @ts-expect-error options is really EventEmitterOptions, but events.d.ts doesn't expose it...
super(options);
this.webContents = new MockWebContents();
}
addTabbedWindow(tab: MockBrowserWindow): void {
return;
}
focus(): void {
return;
}
static fromWebContents(webContents: MockWebContents): MockBrowserWindow {
return new MockBrowserWindow();
}
static getFocusedWindow(window: MockBrowserWindow): MockBrowserWindow {
return window ?? new MockBrowserWindow();
}
isSimpleFullScreen(): boolean {
throw new Error('Not implemented');
}
isFullScreen(): boolean {
throw new Error('Not implemented');
}
isFullScreenable(): boolean {
throw new Error('Not implemented');
}
loadURL(url: string, options?: unknown): Promise<void> {
return Promise.resolve(undefined);
}
setFullScreen(flag: boolean): void {
return;
}
setSimpleFullScreen(flag: boolean): void {
return;
}
}
class MockDialog {
static showMessageBox(
browserWindow: MockBrowserWindow,
options: unknown,
): Promise<number> {
throw new Error('Not implemented');
}
static showMessageBoxSync(
browserWindow: MockBrowserWindow,
options: unknown,
): number {
throw new Error('Not implemented');
}
}
class MockSession extends EventEmitter {
webRequest: MockWebRequest;
constructor() {
super();
this.webRequest = new MockWebRequest();
}
clearCache(): Promise<void> {
return Promise.resolve();
}
clearStorageData(): Promise<void> {
return Promise.resolve();
}
}
class MockWebContents extends EventEmitter {
session: MockSession;
constructor() {
super();
this.session = new MockSession();
}
getURL(): string {
throw new Error('Not implemented');
}
insertCSS(css: string, options?: unknown): Promise<string> {
throw new Error('Not implemented');
}
}
class MockWebRequest {
emitter: InternalEmitter;
constructor() {
this.emitter = new InternalEmitter();
}
onResponseStarted(
filter: unknown,
listener: ((details: unknown) => void) | null,
): void {
if (listener) {
this.emitter.addListener('onResponseStarted', (details: unknown) =>
listener(details),
);
}
}
send(event: string, ...args: unknown[]): void {
this.emitter.emit(event, ...args);
}
}
class InternalEmitter extends EventEmitter {}
const mockShell = {
openExternal(url: string, options?: unknown): Promise<void> {
return new Promise((resolve) => resolve());
},
};
export {
MockDialog as dialog,
MockBrowserWindow as BrowserWindow,
MockSession as Session,
MockWebContents as WebContents,
MockWebRequest as WebRequest,
mockShell as shell,
};

352
app/src/preload.ts Normal file
View File

@ -0,0 +1,352 @@
/**
* Preload file that will be executed in the renderer process.
* Note: This needs to be attached **prior to imports**, as imports
* would delay the attachment till after the event has been raised.
*/
document.addEventListener('DOMContentLoaded', () => {
injectScripts(); // eslint-disable-line @typescript-eslint/no-use-before-define
});
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { ipcRenderer } from 'electron';
import { OutputOptions } from '../../shared/src/options/model';
// Do *NOT* add 3rd-party imports here in preload (except for webpack `externals` like electron).
// They will work during development, but break in the prod build :-/ .
// Electron doc isn't explicit about that, so maybe *we*'re doing something wrong.
// At any rate, that's what we have now. If you want an import here, go ahead, but
// verify that apps built with a non-devbuild nativefier (installed from tarball) work.
// Recipe to monkey around this, assuming you git-cloned nativefier in /opt/nativefier/ :
// cd /opt/nativefier/ && rm -f nativefier-43.1.0.tgz && npm run build && npm pack && mkdir -p ~/n4310/ && cd ~/n4310/ \
// && rm -rf ./* && npm i /opt/nativefier/nativefier-43.1.0.tgz && ./node_modules/.bin/nativefier 'google.com'
// See https://github.com/nativefier/nativefier/issues/1175
// and https://www.electronjs.org/docs/api/browser-window#new-browserwindowoptions / preload
const log = console; // since we can't have `loglevel` here in preload
export const INJECT_DIR = path.join(__dirname, '..', 'inject');
/**
* Patches window.Notification to:
* - set a callback on a new Notification
* - set a callback for clicks on notifications
* @param createCallback
* @param clickCallback
*/
function setNotificationCallback(
createCallback: {
(title: string, opt: NotificationOptions): void;
(...args: unknown[]): void;
},
clickCallback: { (): void; (this: Notification, ev: Event): unknown },
): void {
const OldNotify = window.Notification;
const newNotify = function (
title: string,
opt: NotificationOptions,
): Notification {
createCallback(title, opt);
const instance = new OldNotify(title, opt);
instance.addEventListener('click', clickCallback);
return instance;
};
newNotify.requestPermission = OldNotify.requestPermission.bind(OldNotify);
Object.defineProperty(newNotify, 'permission', {
get: () => OldNotify.permission,
});
// @ts-expect-error TypeScript says its not compatible, but it works?
window.Notification = newNotify;
}
async function getDisplayMedia(
sourceId: number | string,
): Promise<MediaStream> {
type OriginalVideoPropertyType = boolean | MediaTrackConstraints | undefined;
if (!window?.navigator?.mediaDevices) {
throw Error('window.navigator.mediaDevices is not present');
}
// Electron supports an outdated specification for mediaDevices,
// see https://www.electronjs.org/docs/latest/api/desktop-capturer/
const stream = await window.navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId,
},
} as unknown as OriginalVideoPropertyType,
});
return stream;
}
function setupScreenSharePickerStyles(id: string): void {
const screenShareStyles = document.createElement('style');
screenShareStyles.id = id;
screenShareStyles.innerHTML = `
.desktop-capturer-selection {
--overlay-color: hsla(0, 0%, 11.8%, 0.75);
--highlight-color: highlight;
--text-content-color: #fff;
--selection-button-color: hsl(180, 1.3%, 14.7%);
}
.desktop-capturer-selection {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background: var(--overlay-color);
color: var(--text-content-color);
z-index: 10000000;
display: flex;
align-items: center;
justify-content: center;
}
.desktop-capturer-selection__close {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
padding: 1rem;
color: inherit;
position: absolute;
left: 1rem;
top: 1rem;
cursor: pointer;
}
.desktop-capturer-selection__scroller {
width: 100%;
max-height: 100vh;
overflow-y: auto;
}
.desktop-capturer-selection__list {
max-width: calc(100% - 100px);
margin: 50px;
padding: 0;
display: flex;
flex-wrap: wrap;
list-style: none;
overflow: hidden;
justify-content: center;
}
.desktop-capturer-selection__item {
display: flex;
margin: 4px;
}
.desktop-capturer-selection__btn {
display: flex;
flex-direction: column;
align-items: stretch;
width: 145px;
margin: 0;
border: 0;
border-radius: 3px;
padding: 4px;
background: var(--selection-button-color);
text-align: left;
transition: background-color .15s, box-shadow .15s;
}
.desktop-capturer-selection__btn:hover,
.desktop-capturer-selection__btn:focus {
background: var(--highlight-color);
}
.desktop-capturer-selection__thumbnail {
width: 100%;
height: 81px;
object-fit: cover;
}
.desktop-capturer-selection__name {
margin: 6px 0 6px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
@media (prefers-color-scheme: light) {
.desktop-capturer-selection {
--overlay-color: hsla(0, 0%, 90.2%, 0.75);
--text-content-color: hsl(0, 0%, 12.9%);
--selection-button-color: hsl(180, 1.3%, 85.3%);
}
}`;
document.head.appendChild(screenShareStyles);
}
function setupScreenSharePickerElement(
id: string,
sources: Electron.DesktopCapturerSource[],
): void {
const selectionElem = document.createElement('div');
selectionElem.classList.add('desktop-capturer-selection');
selectionElem.id = id;
selectionElem.innerHTML = `
<button class="desktop-capturer-selection__close" id="${id}-close" aria-label="Close screen share picker" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="32" height="32">
<path fill="currentColor" d="m12 10.586 4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/>
</svg>
</button>
<div class="desktop-capturer-selection__scroller">
<ul class="desktop-capturer-selection__list">
${sources
.map(
({ id, name, thumbnail }) => `
<li class="desktop-capturer-selection__item">
<button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">
<img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" />
<span class="desktop-capturer-selection__name">${name}</span>
</button>
</li>
`,
)
.join('')}
</ul>
</div>
`;
document.body.appendChild(selectionElem);
}
function setupScreenSharePicker(
resolve: (value: MediaStream | PromiseLike<MediaStream>) => void,
reject: (reason?: unknown) => void,
sources: Electron.DesktopCapturerSource[],
): void {
const baseElementsId = 'native-screen-share-picker';
const pickerStylesElementId = baseElementsId + '-styles';
setupScreenSharePickerElement(baseElementsId, sources);
setupScreenSharePickerStyles(pickerStylesElementId);
const clearElements = (): void => {
document.getElementById(pickerStylesElementId)?.remove();
document.getElementById(baseElementsId)?.remove();
};
document
.getElementById(`${baseElementsId}-close`)
?.addEventListener('click', () => {
clearElements();
reject('Screen share was cancelled by the user.');
});
document
.querySelectorAll('.desktop-capturer-selection__btn')
.forEach((button) => {
button.addEventListener('click', () => {
const id = button.getAttribute('data-id');
if (!id) {
log.error("Couldn't find `data-id` of element");
clearElements();
return;
}
const source = sources.find((source) => source.id === id);
if (!source) {
log.error(`Source with id "${id}" does not exist`);
clearElements();
return;
}
getDisplayMedia(source.id)
.then((stream) => {
resolve(stream);
})
.catch((err) => {
log.error('Error selecting desktop capture source:', err);
reject(err);
})
.finally(() => {
clearElements();
});
});
});
}
function setDisplayMediaPromise(): void {
// Since no implementation for `getDisplayMedia` exists in Electron we write our own.
if (!window?.navigator?.mediaDevices) {
return;
}
window.navigator.mediaDevices.getDisplayMedia = (): Promise<MediaStream> => {
return new Promise((resolve, reject) => {
const sources = ipcRenderer.invoke(
'desktop-capturer-get-sources',
) as Promise<Electron.DesktopCapturerSource[]>;
sources
.then(async (sources) => {
if (isWayland()) {
// No documentation is provided wether the first element is always PipeWire-picked or not
// i.e. maybe it's not deterministic, we are only taking a guess here.
const stream = await getDisplayMedia(sources[0].id);
resolve(stream);
} else {
setupScreenSharePicker(resolve, reject, sources);
}
})
.catch((err) => {
reject(err);
});
});
};
}
function injectScripts(): void {
const needToInject = fs.existsSync(INJECT_DIR);
if (!needToInject) {
return;
}
// Dynamically require scripts
try {
const jsFiles = fs
.readdirSync(INJECT_DIR, { withFileTypes: true })
.filter(
(injectFile) => injectFile.isFile() && injectFile.name.endsWith('.js'),
)
.map((jsFileStat) => path.join('..', 'inject', jsFileStat.name));
for (const jsFile of jsFiles) {
log.debug('Injecting JS file', jsFile);
require(jsFile);
}
} catch (err: unknown) {
log.error('Error encoutered injecting JS files', err);
}
}
function notifyNotificationCreate(
title: string,
opt: NotificationOptions,
): void {
ipcRenderer.send('notification', title, opt);
}
function notifyNotificationClick(): void {
ipcRenderer.send('notification-click');
}
// @ts-expect-error TypeScript thinks these are incompatible but they aren't
setNotificationCallback(notifyNotificationCreate, notifyNotificationClick);
setDisplayMediaPromise();
ipcRenderer.on('params', (event, message: string) => {
log.debug('ipcRenderer.params', { event, message });
const appArgs: unknown = JSON.parse(message) as OutputOptions;
log.info('nativefier.json', appArgs);
});
ipcRenderer.on('debug', (event, message: string) => {
log.debug('ipcRenderer.debug', { event, message });
});
// Copy-pastaed as unable to get imports to work in preload.
// If modifying, update also app/src/helpers/helpers.ts
function isWayland(): boolean {
return (
isLinux() &&
(Boolean(process.env.WAYLAND_DISPLAY) ||
process.env.XDG_SESSION_TYPE === 'wayland')
);
}
function isLinux(): boolean {
return os.platform() === 'linux';
}

View File

@ -0,0 +1,2 @@
env:
browser: true

57
app/src/static/login.css Normal file
View File

@ -0,0 +1,57 @@
label, input {
display: block;
}
* {
font-family: Verdana, sans-serif;
}
html, body {
height: 100%;
margin: 0;
-webkit-app-region: drag;
}
.login-form {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.login-form > div {
margin-bottom: 30px;
}
.login-form > div > label {
margin-bottom: 25px;
letter-spacing: 5px;
font-weight: 100;
text-transform: uppercase;
font-size: 18px;
text-align: center;
color: rgba(0, 0, 0, 0.7);
}
.login-form > div > input {
height: 30px;
width: 225px;
font-size: 16px;
-webkit-app-region: no-drag;
}
.login-form > div > button {
border: 0;
font-size: 15px;
font-weight: 100;
text-transform: uppercase;
height: 35px;
width: 225px;
background-color: #2196F3;
color: white;
-webkit-app-region: no-drag;
}
button:hover {
background-color: #0D47A1;
}

24
app/src/static/login.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" type="text/css" href="login.css">
</head>
<body>
<form class="login-form" id="login-form">
<div>
<label for="username-input">Username</label>
<input type="text" id="username-input" autofocus/>
</div>
<div>
<label for="password-input">Password</label>
<input type="password" id="password-input"/>
</div>
<div>
<button id="submit-form-button" type="submit">Login</button>
</div>
</form>
<script src="login.js"></script>
</body>
</html>

10
app/src/static/login.js Normal file
View File

@ -0,0 +1,10 @@
const { ipcRenderer } = require('electron');
document.getElementById('login-form').addEventListener('submit', (event) => {
event.preventDefault();
const usernameInput = document.getElementById('username-input');
const username = usernameInput.nodeValue || usernameInput.value;
const passwordInput = document.getElementById('password-input');
const password = passwordInput.nodeValue || passwordInput.value;
ipcRenderer.send('login-message', [username, password]);
});

36
app/tsconfig.json Normal file
View File

@ -0,0 +1,36 @@
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"outDir": "./dist",
// Here in app/tsconfig.json, we want to set the `target` and `lib` keys to
// the "best" values for the version of Node **coming with the chosen Electron**.
// Careful: we're *not* talking about Nativefier's (CLI) required Node version,
// we're talking about the version of the Node runtime **bundled with Electron**.
//
// Like in our main tsconfig.json, we want to be as conservative as possible,
// to support (as much as reasonable) users using old versions of Electron.
// Then, at some point, an app dependency (declared in app/package.json)
// will require a more recent Node, then it's okay to bump our app compilerOptions
// to what's supported by the more recent Node.
//
// TS doesn't offer any easy "preset" for this, so the best we have is to
// believe people who know which {syntax, library} parts of current EcmaScript
// are supported for the version of Node coming with the Electron being used,
// and use what they recommend. For the current Node version, I followed
// https://stackoverflow.com/questions/51716406/typescript-tsconfig-settings-for-node-js-10
// and 'dom' to tell tsc it's okay to use the URL object (which is in Node >= 7)
"target": "es2018",
"lib": [
"es2018",
"dom"
]
},
"include": [
"./src/**/*"
],
"references": [
{
"path": "../shared"
}
]
}

36
app/webpack.config.js Normal file
View File

@ -0,0 +1,36 @@
const path = require('path');
// Q: Why do you use webpack?
// A: https://github.com/nativefier/nativefier/commit/cde5c1e13bdc2739604cab04bac64eae0d719ed1
module.exports = {
target: 'node',
entry: './src/main.ts',
devtool: 'source-map', // https://webpack.js.org/configuration/devtool/
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader', // https://webpack.js.org/guides/typescript/
exclude: /node_modules/,
},
],
},
// Don't mock __dirname; https://webpack.js.org/configuration/node/#root
node: {
__dirname: false,
},
// Prevent bundling of certain imported packages and instead retrieve these
// external deps at runtime. This is what we want for electron, placed in the
// app by electron-packager. https://webpack.js.org/configuration/externals/
externals: {
electron: 'commonjs electron',
},
resolve: {
extensions: [ '.ts', '.js' ],
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'lib'),
},
mode: 'none'
};

39
base-eslintrc.js Normal file
View File

@ -0,0 +1,39 @@
// # https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'prettier'],
extends: [
'eslint:recommended',
'prettier',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
rules: {
'no-console': 'error',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-invalid-void-type': 'error',
'@typescript-eslint/prefer-ts-expect-error': 'error',
'@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/typedef': 'error',
'@typescript-eslint/unified-signatures': 'error',
},
// https://eslint.org/docs/user-guide/configuring/ignoring-code#ignorepatterns-in-config-files
ignorePatterns: [
'node_modules/**',
'app/node_modules/**',
'app/lib/**',
'lib/**',
'built-tests/**',
'coverage/**',
],
};

56
icon-scripts/convertToIcns Executable file
View File

@ -0,0 +1,56 @@
#!/usr/bin/env bash
### USAGE
# ./convertToIcns <input png> <outp icns>
# Example
# ./convertToIcns ~/sample.png ~/Desktop/converted.icns
# exit the shell script on error immediately
set -e
# Exec Paths
HAVE_IMAGEMAGICK=
HAVE_ICONUTIL=
HAVE_SIPS=
HAVE_GRAPHICSMAGICK=
type convert &>/dev/null && HAVE_IMAGEMAGICK=true
type iconutil &>/dev/null && HAVE_ICONUTIL=true
type sips &>/dev/null && HAVE_SIPS=true
type gm &>/dev/null && gm version | grep GraphicsMagick &>/dev/null && HAVE_GRAPHICSMAGICK=true
[[ -z "$HAVE_ICONUTIL" ]] && { echo >&2 "Cannot find required iconutil executable"; exit 1; }
[[ -z "$HAVE_IMAGEMAGICK" && -z "$HAVE_SIPS" && -z "$HAVE_GRAPHICSMAGICK" ]] && { echo >&2 "Cannot find required image converter, please install sips, imagemagick or graphicsmagick"; exit 1; }
# Parameters
SOURCE="$1"
DEST="$2"
# Check source and destination arguments
if [ -z "${SOURCE}" ]; then
echo "No source image specified"
exit 1
fi
if [ -z "${DEST}" ]; then
echo "No destination specified"
exit 1
fi
TEMP_DIR="$(mktemp -d -t nativefier-icns-XXXXXX)"
ICONSET="${TEMP_DIR}/converted.iconset"
function cleanUp() {
rm -rf "${TEMP_DIR}"
}
trap cleanUp EXIT
"${BASH_SOURCE%/*}/convertToIconset" "${SOURCE}" "${ICONSET}"
# Create an icns file lefrom the iconset
iconutil -c icns "${ICONSET}" -o "${DEST}"
trap - EXIT
cleanUp

39
icon-scripts/convertToIco Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# USAGE
# ./convertToIco <input png or ico> <outfilename>.ico
# Example
# ./convertToPng ~/sample.png ~/converted.ico
set -e
CONVERT=
type gm &>/dev/null && gm version | grep GraphicsMagick &>/dev/null && CONVERT="gm convert"
type convert &>/dev/null && CONVERT="convert"
[[ -z "$CONVERT" ]] && { echo >&2 "Cannot find required ImageMagick Convert or GraphicsMagick executable"; exit 1; }
SOURCE=$1
DEST=$2
if [ -z "${SOURCE}" ]; then
echo "No source image specified"
exit 1
fi
if [ -z "${DEST}" ]; then
echo "No destination specified"
exit 1
fi
NAME=$(basename "${SOURCE}")
EXT="${NAME##*.}"
if [ "${EXT}" == "ico" ]; then
cp "${SOURCE}" "${DEST}"
exit 0
fi
$CONVERT "${SOURCE}" -resize 256x256 "${DEST}"

68
icon-scripts/convertToIconset Executable file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env bash
### USAGE
# ./convertToIconset <input png> <outp iconset>
# Example
# ./convertToIconset ~/sample.png ~/Desktop/converted.iconset
# exit the shell script on error immediately
set -e
make_iconset_imagemagick() {
local file iconset
file="${1}"
iconset="${2}"
mkdir "$iconset"
for size in {16,32,64,128,256,512}; do
$CONVERT "${file}" -define png:big-depth=16 -define png:color-type=6 -sample "${size}x${size}" "${iconset}/icon_${size}x${size}.png"
$CONVERT "${file}" -define png:big-depth=16 -define png:color-type=6 -sample "$((size * 2))x$((size * 2))" "${iconset}/icon_${size}x${size}@2x.png"
done
}
make_iconset_sips() {
local file iconset
file="${1}"
iconset="${2}"
mkdir "$iconset"
for size in {16,32,64,128,256,512}; do
sips --setProperty format png --resampleHeightWidth "${size}" "${size}" "${file}" --out "${iconset}/icon_${size}x${size}.png" &> /dev/null
sips --setProperty format png --resampleHeightWidth "$((size * 2))" "$((size * 2))" "${file}" --out "${iconset}/icon_${size}x${size}@2x.png" &> /dev/null
done
}
# Parameters
SOURCE="$1"
DEST="$2"
# Check source and destination arguments
if [ -z "${SOURCE}" ]; then
echo >&2 "No source image specified"; exit 1
fi
if [ -z "${DEST}" ]; then
echo >&2 "No destination specified"; exit 1
fi
HAVE_IMAGEMAGICK=
HAVE_SIPS=
HAVE_GRAPHICSMAGICK=
CONVERT=
type gm &>/dev/null && gm version | grep GraphicsMagick &>/dev/null && HAVE_GRAPHICSMAGICK=true && CONVERT="gm convert"
type convert &>/dev/null && HAVE_IMAGEMAGICK=true && CONVERT="convert"
type sips &>/dev/null && HAVE_SIPS=true
if [[ -n "$HAVE_IMAGEMAGICK" || -n "$HAVE_GRAPHICSMAGICK" ]]; then
PNG_PATH="$(mktemp -d -t nativefier-iconset-XXXXXX)/icon.png"
"${BASH_SOURCE%/*}/convertToPng" "${SOURCE}" "${PNG_PATH}"
make_iconset_imagemagick "${PNG_PATH}" "${DEST}"
elif [[ -n "$HAVE_SIPS" ]]; then
make_iconset_sips "${SOURCE}" "${DEST}"
else
echo >&2 "Cannot find convert or sips executables"; exit 1;
fi

76
icon-scripts/convertToPng Executable file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env bash
# USAGE
# ./convertToPng <input png or ico> <outfilename>.png
# Example
# ./convertToPng ~/sample.ico ~/Desktop/converted.png
set -e
HAVE_IMAGEMAGICK=
HAVE_GRAPHICSMAGICK=
type convert &>/dev/null && type identify &>/dev/null && HAVE_IMAGEMAGICK=true
type gm &>/dev/null && gm version | grep GraphicsMagick &>/dev/null && HAVE_GRAPHICSMAGICK=true
if [[ -z "$HAVE_IMAGEMAGICK" && -z "$HAVE_GRAPHICSMAGICK" ]]; then
type convert >/dev/null 2>&1 || echo >&2 "Cannot find required ImageMagick 'convert' executable"
type identify >/dev/null 2>&1 || echo >&2 "Cannot find required ImageMagick 'identify' executable"
type gm &>/dev/null && gm version | grep GraphicsMagick &>/dev/null && echo >&2 "Cannot find GraphicsMagick"
echo >&2 "ImageMagic or GraphicsMagic is required, please ensure they are in your PATH"
exit 1
fi
CONVERT="convert"
IDENTIFY="identify"
if [[ -z "$HAVE_IMAGEMAGICK" ]]; then
# we must have GraphicsMagick then
CONVERT="gm convert"
IDENTIFY="gm identify"
fi
# Parameters
SOURCE="$1"
DEST="$2"
# Check source and destination arguments
if [ -z "${SOURCE}" ]; then
echo "No source image specified"
exit 1
fi
if [ -z "${DEST}" ]; then
echo "No destination specified"
exit 1
fi
# File Infrastructure
NAME=$(basename "${SOURCE}")
BASE="${NAME%.*}"
TEMP_DIR="convert_temp"
function cleanUp() {
rm -rf "${TEMP_DIR}"
}
trap cleanUp EXIT
mkdir -p "${TEMP_DIR}"
# check if .ico is a sequence
# pipe into cat so no exit code is given for grep if no matches are found
IS_ICO_SET="$($IDENTIFY "${SOURCE}" | grep -e "\w\.ico\[0" | cat )"
$CONVERT "${SOURCE}" "${TEMP_DIR}/${BASE}.png"
if [ "${IS_ICO_SET}" ]; then
# extract the largest(?) image from the set
cp "${TEMP_DIR}/${BASE}-0.png" "${DEST}"
else
cp "${TEMP_DIR}/${BASE}.png" "${DEST}"
fi
rm -rf "${TEMP_DIR}"
trap - EXIT
cleanUp

32
icon-scripts/convertToTrayIcon Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
# USAGE
# ./convertToTrayIcon <input png or icns> <outfilename>.png
# Example
# ./convertToTrayIcon ~/sample.icns ~/converted.png
set -e
SOURCE=$1
DEST=$2
if [ -z "${SOURCE}" ]; then
echo "No source image specified"
exit 1
fi
if [ -z "${DEST}" ]; then
echo "No destination specified"
exit 1
fi
NAME=$(basename "${SOURCE}")
EXT="${NAME##*.}"
if [ "${EXT}" == "png" ]; then
cp "${SOURCE}" "${DEST}"
exit 0
fi
sips --setProperty format png --resampleHeightWidth "256" "256" "${SOURCE}" --out "${DEST}"

8144
npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

145
package.json Normal file
View File

@ -0,0 +1,145 @@
{
"name": "nativefier",
"version": "52.0.0",
"description": "Wrap web apps natively",
"license": "MIT",
"author": "Goh Jia Hao",
"engines_README": "Bumping the minimum required Node version? You must bump: 1. package.json -> engines.node, 2. package.json -> devDependencies.@types/node , 3. tsconfig.json -> {target, lib} , 4. .github/workflows/ci.yml -> node-version",
"engines_READMEforEnginesNode": "Here in engines.node, we require a version as old as possible, for Nativefier to be easily installable using the stock Node.js shipped by conservative Linux distros. It's a balancing act between this, and our own dependencies requiring more a recent Node; as much as possible, try to keep supporting Debian stable; https://packages.debian.org/search?suite=stable&keywords=nodejs",
"engines": {
"node": ">= 16.16.0",
"npm": ">= 8.11.0"
},
"keywords": [
"desktop",
"electron",
"app",
"native",
"wrapper"
],
"main": "lib/main.js",
"typings": "lib/main.d.ts",
"bin": {
"nativefier": "lib/cli.js"
},
"homepage": "https://github.com/nativefier/nativefier",
"repository": {
"type": "git",
"url": "git+https://github.com/nativefier/nativefier.git"
},
"bugs": {
"url": "https://github.com/nativefier/nativefier/issues"
},
"scripts": {
"build-app": "cd app && webpack",
"build-app-static": "ncp app/src/static/ app/lib/static/ && ncp app/dist/preload.js app/lib/preload.js && ncp app/dist/preload.js.map app/lib/preload.js.map",
"build": "npm run clean && tsc --build shared src app && npm run build-app && npm run build-app-static",
"build:watch": "npm run clean && tsc --build shared src app --watch",
"changelog": "./.github/generate-changelog",
"clean": "rimraf coverage/ lib/ app/lib/ app/dist/ shared/lib",
"clean:full": "npm run clean && rimraf app/node_modules/ node_modules/",
"lint:fix": "cd src && eslint . --ext .ts --fix && cd ../shared && eslint src --ext .ts --fix && cd ../app && eslint src --ext .ts --fix",
"lint:format": "prettier --write 'src/**/*.ts' 'app/src/**/*.ts' 'shared/src/**/*.ts'",
"lint": "eslint shared app src --ext .ts",
"list-outdated-deps": "npm out -l; cd app && npm out -l; true",
"prepare": "cd app && npm ci && cd .. && npm run build",
"relock:cli": "rm -rf ./node_modules/ ./npm-shrinkwrap.json && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out -l",
"relock:app": "rm -rf ./app/node_modules/ ./app/npm-shrinkwrap.json && cd app && npm install --ignore-scripts --package-lock && mv package-lock.json npm-shrinkwrap.json && npm out -l",
"relock": "npm run relock:cli; npm run relock:app",
"test:integration": "jest --testRegex=integration-test",
"test:manual": "npm run build && bash .github/manual-test",
"test:playwright": "jest --detectOpenHandles --testRegex=playwright-test",
"test:noplaywright": "jest --testPathIgnorePatterns=playwright",
"test:unit": "jest",
"test:watch": "echo 'Remember to run npm run build:watch for the test watcher to work!' && jest --watchAll --collectCoverage=false",
"test:watch:unit": "echo 'Remember to run npm run build:watch for the test watcher to work!' && jest --watchAll --collectCoverage=false --testPathIgnorePatterns=integration --testPathIgnorePatterns=playwright",
"test:withlog": "LOGLEVEL=trace npm run test",
"test": "jest",
"watch": "npx concurrently \"npm:*:watch\""
},
"dependencies": {
"@electron/asar": "^3.2.4",
"axios": "^1.4.0",
"electron-packager": "^17.1.1",
"fs-extra": "^11.1.1",
"gitcloud": "^0.2.4",
"hasbin": "^1.2.3",
"loglevel": "^1.8.1",
"ncp": "^2.0.0",
"page-icon": "^0.4.0",
"sanitize-filename": "^1.6.3",
"source-map-support": "^0.5.21",
"tmp": "^0.2.1",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/debug": "^4.1.8",
"@types/fs-extra": "^11.0.1",
"@types/hasbin": "^1.2.0",
"@types/jest": "^29.5.4",
"@types/ncp": "^2.0.5",
"@types/node": "^20.5.6",
"@types/page-icon": "^0.3.4",
"@types/tmp": "^0.2.3",
"@types/yargs": "^17.0.24",
"@typescript-eslint/eslint-plugin": "^6.4.1",
"@typescript-eslint/parser": "^6.4.1",
"electron": "^25.7.0",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.6.2",
"playwright": "^1.36.2",
"prettier": "^3.0.1",
"rimraf": "^5.0.1",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
},
"jest_COMMENTS": {
"testPathIgnorePatterns": "See https://jestjs.io/docs/configuration#testpathignorepatterns-arraystring . We set it to 1. ignore coverage for deps, and 2. be sure we test the compiled JS, which is in `lib`, not `src` or `dist`",
"watchPathIgnorePatterns": "See https://jestjs.io/docs/configuration#watchpathignorepatterns-arraystring . We set it for `jest --watch` (a.k.a. `npm run test:watch`) to trigger only after `tsc --watch` (a.k.a. `npm run build:watch`) completes its incremental compilation. Else, jest will pick up immediately on changes in `src` when TSC is barely running, hence testing not-recompiled-yet code and being super confusing, as 1. your changes won't be taken during this first run, and 2. the *next* run (e.g. after a second 'Save' in your editor) will actually have the new code :D"
},
"jest": {
"collectCoverage": true,
"collectCoverageFrom": [
"./app/dist/**/*.js",
"./lib/**/*.js",
"./shared/lib/**/*.js"
],
"coveragePathIgnorePatterns": [
"[.-]test.js$"
],
"moduleNameMapper": {
"^electron$": "<rootDir>/app/dist/mocks/electron.js"
},
"setupFiles": [
"./lib/jestSetupFiles"
],
"testEnvironment": "node",
"testPathIgnorePatterns": [
"<rootDir>/app/node_modules.*",
"<rootDir>/app/src.*",
"<rootDir>/app/lib.*",
"<rootDir>/src.*",
".+\\.d\\.ts",
".+\\.js\\.map"
],
"testRegex": "test\\.js",
"testTimeout": 15000,
"watchPathIgnorePatterns": [
"<rootDir>/app/lib.*",
"<rootDir>/app/src.*",
"<rootDir>/app/tsconfig.json",
"<rootDir>/shared/tsconfig.json",
"<rootDir>/src.*",
"<rootDir>/tsconfig-base.json"
]
},
"prettier": {
"arrowParens": "always",
"singleQuote": true,
"trailingComma": "all"
}
}

14
shared/.eslintrc.js Normal file
View File

@ -0,0 +1,14 @@
const base = require('../base-eslintrc');
// # https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md
module.exports = {
parser: base.parser,
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
plugins: base.plugins,
extends: base.extends,
rules: base.rules,
ignorePatterns: ['lib/**'],
};

238
shared/src/options/model.ts Normal file
View File

@ -0,0 +1,238 @@
import { CreateOptions } from '@electron/asar';
import { randomUUID } from 'crypto';
import * as electronPackager from 'electron-packager';
export type TitleBarValue =
| 'default'
| 'hidden'
| 'hiddenInset'
| 'customButtonsOnHover';
export type TrayValue = 'true' | 'false' | 'start-in-tray';
export interface ElectronPackagerOptions extends electronPackager.Options {
arch: string;
portable: boolean;
platform?: string;
targetUrl: string;
upgrade: boolean;
upgradeFrom?: string;
}
export interface AppOptions {
packager: ElectronPackagerOptions;
nativefier: {
accessibilityPrompt: boolean;
alwaysOnTop: boolean;
backgroundColor?: string;
basicAuthPassword?: string;
basicAuthUsername?: string;
blockExternalUrls: boolean;
bookmarksMenu?: string;
bounce: boolean;
browserwindowOptions?: BrowserWindowOptions;
clearCache: boolean;
counter: boolean;
crashReporter?: string;
disableContextMenu: boolean;
disableDevTools: boolean;
disableGpu: boolean;
disableOldBuildWarning: boolean;
diskCacheSize?: number;
electronVersionUsed?: string;
enableEs3Apis: boolean;
fastQuit: boolean;
fileDownloadOptions?: Record<string, unknown>;
flashPluginDir?: string;
fullScreen: boolean;
globalShortcuts?: GlobalShortcut[];
hideWindowFrame: boolean;
ignoreCertificate: boolean;
ignoreGpuBlacklist: boolean;
inject?: string[];
insecure: boolean;
internalUrls?: string;
lang?: string;
maximize: boolean;
nativefierVersion: string;
processEnvs?: string;
proxyRules?: string;
quiet?: boolean;
showMenuBar: boolean;
singleInstance: boolean;
strictInternalUrls: boolean;
titleBarStyle?: TitleBarValue;
tray: TrayValue;
userAgent?: string;
userAgentHonest: boolean;
verbose: boolean;
versionString?: string;
width?: number;
widevine: boolean;
height?: number;
minWidth?: number;
minHeight?: number;
maxWidth?: number;
maxHeight?: number;
x?: number;
y?: number;
zoom: number;
};
}
export type BrowserWindowOptions = Record<string, unknown> & {
webPreferences?: Record<string, unknown>;
};
export type GlobalShortcut = {
key: string;
inputEvents: {
type:
| 'mouseDown'
| 'mouseUp'
| 'mouseEnter'
| 'mouseLeave'
| 'contextMenu'
| 'mouseWheel'
| 'mouseMove'
| 'keyDown'
| 'keyUp'
| 'char';
keyCode: string;
}[];
};
export type NativefierOptions = Partial<
AppOptions['packager'] & AppOptions['nativefier']
>;
export type OutputOptions = NativefierOptions & {
blockExternalUrls: boolean;
browserwindowOptions?: BrowserWindowOptions;
buildDate: number;
companyName?: string;
disableDevTools: boolean;
fileDownloadOptions?: Record<string, unknown>;
internalUrls: string | RegExp | undefined;
isUpgrade: boolean;
name: string;
nativefierVersion: string;
oldBuildWarningText: string;
strictInternalUrls: boolean;
tabbingIdentifier?: string;
targetUrl: string;
userAgent?: string;
zoom?: number;
};
export type PackageJSON = {
name: string;
};
export type RawOptions = {
accessibilityPrompt?: boolean;
alwaysOnTop?: boolean;
appCopyright?: string;
appVersion?: string;
arch?: string;
asar?: boolean | CreateOptions;
backgroundColor?: string;
basicAuthPassword?: string;
basicAuthUsername?: string;
blockExternalUrls?: boolean;
bookmarksMenu?: string;
bounce?: boolean;
browserwindowOptions?: BrowserWindowOptions;
buildVersion?: string;
clearCache?: boolean;
conceal?: boolean;
counter?: boolean;
crashReporter?: string;
darwinDarkModeSupport?: boolean;
disableContextMenu?: boolean;
disableDevTools?: boolean;
disableGpu?: boolean;
disableOldBuildWarning?: boolean;
disableOldBuildWarningYesiknowitisinsecure?: boolean;
diskCacheSize?: number;
electronVersion?: string;
electronVersionUsed?: string;
enableEs3Apis?: boolean;
fastQuit?: boolean;
fileDownloadOptions?: Record<string, unknown>;
flashPath?: string;
flashPluginDir?: string;
fullScreen?: boolean;
globalShortcuts?: string | GlobalShortcut[];
height?: number;
hideWindowFrame?: boolean;
icon?: string;
ignoreCertificate?: boolean;
ignoreGpuBlacklist?: boolean;
inject?: string[];
insecure?: boolean;
internalUrls?: string;
lang?: string;
maxHeight?: number;
maximize?: boolean;
maxWidth?: number;
minHeight?: number;
minWidth?: number;
name?: string;
nativefierVersion?: string;
out?: string;
overwrite?: boolean;
platform?: string;
portable?: boolean;
processEnvs?: string;
proxyRules?: string;
quiet?: boolean;
showMenuBar?: boolean;
singleInstance?: boolean;
strictInternalUrls?: boolean;
targetUrl?: string;
titleBarStyle?: TitleBarValue;
tray?: TrayValue;
upgrade?: string | boolean;
upgradeFrom?: string;
userAgent?: string;
userAgentHonest?: boolean;
verbose?: boolean;
versionString?: string;
widevine?: boolean;
width?: number;
win32metadata?: electronPackager.Win32MetadataOptions;
x?: number;
y?: number;
zoom?: number;
};
export type WindowOptions = {
autoHideMenuBar: boolean;
blockExternalUrls: boolean;
browserwindowOptions?: BrowserWindowOptions;
insecure: boolean;
internalUrls?: string | RegExp;
strictInternalUrls?: boolean;
name: string;
proxyRules?: string;
show?: boolean;
tabbingIdentifier?: string;
targetUrl: string;
userAgent?: string;
zoom: number;
};
export function outputOptionsToWindowOptions(
options: OutputOptions,
generateTabbingIdentifierIfMissing: boolean,
): WindowOptions {
return {
...options,
autoHideMenuBar: !options.showMenuBar,
insecure: options.insecure ?? false,
tabbingIdentifier: generateTabbingIdentifierIfMissing
? options.tabbingIdentifier ?? randomUUID()
: options.tabbingIdentifier,
zoom: options.zoom ?? 1.0,
};
}

18
shared/tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"composite": true,
"outDir": "./lib",
// Here we want to set target and lib to the *worst* of app/tsconfig.json and src/tsconfig.json
// (plus "dom"), because shared code will run both in CLI Node and app Node.
// See comments in app/tsconfig.json and src/tsconfig.json
"target": "es2018",
"lib": [
"es2018",
"dom"
]
},
"include": [
"./src/**/*"
],
}

13
src/.eslintrc.js Normal file
View File

@ -0,0 +1,13 @@
const base = require('../base-eslintrc');
// # https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md
module.exports = {
parser: base.parser,
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
plugins: base.plugins,
extends: base.extends,
rules: base.rules,
};

97
src/build/buildIcon.ts Normal file
View File

@ -0,0 +1,97 @@
import * as path from 'path';
import * as log from 'loglevel';
import { isOSX } from '../helpers/helpers';
import {
convertToPng,
convertToIco,
convertToIcns,
convertToTrayIcon,
} from '../helpers/iconShellHelpers';
import { AppOptions } from '../../shared/src/options/model';
function iconIsIco(iconPath: string): boolean {
return path.extname(iconPath) === '.ico';
}
function iconIsPng(iconPath: string): boolean {
return path.extname(iconPath) === '.png';
}
function iconIsIcns(iconPath: string): boolean {
return path.extname(iconPath) === '.icns';
}
/**
* Will convert a `.png` icon to the appropriate arch format (if necessary),
* and return adjusted options
*/
export function convertIconIfNecessary(options: AppOptions): void {
if (!options.packager.icon) {
log.debug('Option "icon" not set, skipping icon conversion.');
return;
}
if (options.packager.platform === 'win32') {
if (iconIsIco(options.packager.icon)) {
log.debug(
'Building for Windows and icon is already a .ico, no conversion needed',
);
return;
}
try {
const iconPath = convertToIco(options.packager.icon);
options.packager.icon = iconPath;
return;
} catch (err: unknown) {
log.warn('Failed to convert icon to .ico, skipping.', err);
return;
}
}
if (options.packager.platform === 'linux') {
if (iconIsPng(options.packager.icon)) {
log.debug(
'Building for Linux and icon is already a .png, no conversion needed',
);
return;
}
try {
const iconPath = convertToPng(options.packager.icon);
options.packager.icon = iconPath;
return;
} catch (err: unknown) {
log.warn('Failed to convert icon to .png, skipping.', err);
return;
}
}
if (iconIsIcns(options.packager.icon)) {
log.debug(
'Building for macOS and icon is already a .icns, no conversion needed',
);
}
if (!isOSX()) {
log.warn(
'Skipping icon conversion to .icns, conversion is only supported on macOS',
);
return;
}
try {
if (!iconIsIcns(options.packager.icon)) {
const iconPath = convertToIcns(options.packager.icon);
options.packager.icon = iconPath;
}
if (options.nativefier.tray !== 'false') {
convertToTrayIcon(options.packager.icon);
}
} catch (err: unknown) {
log.warn('Failed to convert icon to .icns, skipping.', err);
options.packager.icon = undefined;
}
}

View File

@ -0,0 +1,267 @@
import * as path from 'path';
import * as electronGet from '@electron/get';
import electronPackager from 'electron-packager';
import * as fs from 'fs-extra';
import * as log from 'loglevel';
import { convertIconIfNecessary } from './buildIcon';
import {
getTempDir,
hasWine,
isWindows,
isWindowsAdmin,
} from '../helpers/helpers';
import { useOldAppOptions, findUpgradeApp } from '../helpers/upgrade/upgrade';
import { AppOptions, RawOptions } from '../../shared/src/options/model';
import { getOptions } from '../options/optionsMain';
import { prepareElectronApp } from './prepareElectronApp';
const OPTIONS_REQUIRING_WINDOWS_FOR_WINDOWS_BUILD = [
'icon',
'appCopyright',
'appVersion',
'buildVersion',
'versionString',
'win32metadata',
];
/**
* For Windows & Linux, we have to copy over the icon to the resources/app
* folder, which the BrowserWindow is hard-coded to read the icon from
*/
async function copyIconsIfNecessary(
options: AppOptions,
appPath: string,
): Promise<void> {
log.debug('Copying icons if necessary');
if (!options.packager.icon) {
log.debug('No icon specified in options; aborting');
return;
}
if (
options.packager.platform === 'darwin' ||
options.packager.platform === 'mas'
) {
if (options.nativefier.tray !== 'false') {
//tray icon needs to be .png
log.debug('Copying icon for tray application');
const trayIconFileName = `tray-icon.png`;
const destIconPath = path.join(appPath, 'icon.png');
await fs.copy(
`${path.dirname(options.packager.icon)}/${trayIconFileName}`,
destIconPath,
);
} else {
log.debug('No copying necessary on macOS; aborting');
}
return;
}
// windows & linux: put the icon file into the app
const destFileName = `icon${path.extname(options.packager.icon)}`;
const destIconPath = path.join(appPath, destFileName);
log.debug(`Copying icon ${options.packager.icon} to`, destIconPath);
await fs.copy(options.packager.icon, destIconPath);
}
/**
* Checks the app path array to determine if packaging completed successfully
*/
function getAppPath(appPath: string | string[]): string | undefined {
if (!Array.isArray(appPath)) {
return appPath;
}
if (appPath.length === 0) {
return undefined; // directory already exists and `--overwrite` not set
}
if (appPath.length > 1) {
log.warn(
'Warning: This should not be happening, packaged app path contains more than one element:',
appPath,
);
}
return appPath[0];
}
function isUpgrade(rawOptions: RawOptions): boolean {
if (
rawOptions.upgrade !== undefined &&
typeof rawOptions.upgrade === 'string' &&
rawOptions.upgrade !== ''
) {
rawOptions.upgradeFrom = rawOptions.upgrade;
rawOptions.upgrade = true;
return true;
}
return false;
}
function trimUnprocessableOptions(options: AppOptions): void {
if (options.packager.platform === 'win32' && !isWindows() && !hasWine()) {
const optionsPresent = Object.entries(options)
.filter(
([key, value]) =>
OPTIONS_REQUIRING_WINDOWS_FOR_WINDOWS_BUILD.includes(key) && !!value,
)
.map(([key]) => key);
if (optionsPresent.length === 0) {
return;
}
log.warn(
`*Not* setting [${optionsPresent.join(', ')}], as couldn't find Wine.`,
'Wine is required when packaging a Windows app under on non-Windows platforms.',
'Also, note that Windows apps built under non-Windows platforms without Wine *will lack* certain',
'features, like a correct icon and process name. Do yourself a favor and install Wine, please.',
);
for (const keyToUnset of optionsPresent) {
(options as unknown as Record<string, undefined>)[keyToUnset] = undefined;
}
}
}
function getOSRunHelp(platform?: string): string {
if (platform === 'win32') {
return `the contained .exe file.`;
} else if (platform === 'linux') {
return `the contained executable file (prefixing with ./ if necessary)\nMenu/desktop shortcuts are up to you, because Nativefier cannot know where you're going to move the app. Search for "linux .desktop file" for help, or see https://wiki.archlinux.org/index.php/Desktop_entries`;
} else if (platform === 'darwin') {
return `the app bundle.`;
}
return '';
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export async function buildNativefierApp(
rawOptions: RawOptions,
): Promise<string> {
// early-suppress potential logging before full options handling
if (rawOptions.quiet) {
log.setLevel('silent');
}
log.warn(
'\n\n Hi! Nativefier is minimally maintained these days, and needs more hands.\n' +
' If you have the time & motivation, help with bugfixes and maintenance is VERY welcome.\n' +
' Please go to https://github.com/nativefier/nativefier and help how you can. Thanks.\n\n',
);
log.info('\nProcessing options...');
let finalOutDirectory = rawOptions.out ?? process.cwd();
if (isUpgrade(rawOptions)) {
log.debug('Attempting to upgrade from', rawOptions.upgradeFrom);
const oldApp = findUpgradeApp(rawOptions.upgradeFrom as string);
if (!oldApp) {
throw new Error(
`Could not find an old Nativfier app in "${
rawOptions.upgradeFrom as string
}"`,
);
}
rawOptions = useOldAppOptions(rawOptions, oldApp);
if (rawOptions.out === undefined && rawOptions.overwrite) {
finalOutDirectory = oldApp.appRoot;
rawOptions.out = getTempDir('appUpgrade', 0o755);
}
}
log.debug('rawOptions', rawOptions);
const options = await getOptions(rawOptions);
log.debug('options', options);
if (options.packager.platform === 'darwin' && isWindows()) {
// electron-packager has to extract the desired electron package for the target platform.
// For a target platform of Mac, this zip file contains symlinks. And on Windows, extracting
// files that are symlinks need Admin permissions. So we'll check if the user is an admin, and
// fail early if not.
// For reference
// https://github.com/electron/electron-packager/issues/933
// https://github.com/electron/electron-packager/issues/1194
// https://github.com/electron/electron/issues/11094
if (!isWindowsAdmin()) {
throw new Error(
'Building an app with a target platform of Mac on a Windows machine requires admin priveleges to perform. Please rerun this command in an admin command prompt.',
);
}
}
log.info('\nPreparing Electron app...');
const tmpPath = getTempDir('app', 0o755);
await prepareElectronApp(options.packager.dir, tmpPath, options);
log.info('\nConverting icons...');
options.packager.dir = tmpPath;
convertIconIfNecessary(options);
await copyIconsIfNecessary(options, tmpPath);
options.packager.quiet = !rawOptions.verbose;
log.info(
"\nPackaging... This will take a few seconds, maybe minutes if the requested Electron isn't cached yet...",
);
trimUnprocessableOptions(options);
electronGet.initializeProxy(); // https://github.com/electron/get#proxies
const appPathArray = await electronPackager(options.packager);
log.info('\nFinalizing build...');
let appPath = getAppPath(appPathArray);
if (!appPath) {
throw new Error('App Path could not be determined.');
}
if (
options.packager.upgrade &&
options.packager.upgradeFrom &&
options.packager.overwrite
) {
if (options.packager.platform === 'darwin') {
try {
// This is needed due to a funky thing that happens when copying Squirrel.framework
// over where it gets into a circular file reference somehow.
await fs.remove(
path.join(
finalOutDirectory,
`${options.packager.name ?? ''}.app`,
'Contents',
'Frameworks',
),
);
} catch (err: unknown) {
log.warn(
'Encountered an error when attempting to pre-delete old frameworks:',
err,
);
}
await fs.copy(
path.join(appPath, `${options.packager.name ?? ''}.app`),
path.join(finalOutDirectory, `${options.packager.name ?? ''}.app`),
{
overwrite: options.packager.overwrite,
preserveTimestamps: true,
},
);
} else {
await fs.copy(appPath, finalOutDirectory, {
overwrite: options.packager.overwrite,
preserveTimestamps: true,
});
}
await fs.remove(appPath);
appPath = finalOutDirectory;
}
const osRunHelp = getOSRunHelp(options.packager.platform);
log.info(
`App built to ${appPath}, move to wherever it makes sense for you and run ${osRunHelp}`,
);
return appPath;
}

View File

@ -0,0 +1,11 @@
import { normalizeAppName } from './prepareElectronApp';
describe('normalizeAppName', () => {
test('it is stable', () => {
// Non-determinism / unstability would cause using a different appName
// at each app regen, thus a different appData folder, which would cause
// losing user state, including login state through cookies.
const normalizedTrello = normalizeAppName('Trello', 'https://trello.com');
expect(normalizedTrello).toBe('trello-nativefier-679e8e');
});
});

View File

@ -0,0 +1,219 @@
import * as crypto from 'crypto';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as log from 'loglevel';
import { generateRandomSuffix } from '../helpers/helpers';
import {
AppOptions,
OutputOptions,
PackageJSON,
} from '../../shared/src/options/model';
import { parseJson } from '../utils/parseUtils';
import { DEFAULT_APP_NAME } from '../constants';
/**
* Only picks certain app args to pass to nativefier.json
*/
function pickElectronAppArgs(options: AppOptions): OutputOptions {
return {
accessibilityPrompt: options.nativefier.accessibilityPrompt,
alwaysOnTop: options.nativefier.alwaysOnTop,
appBundleId: options.packager.appBundleId,
appCategoryType: options.packager.appCategoryType,
appCopyright: options.packager.appCopyright,
appVersion: options.packager.appVersion,
arch: options.packager.arch,
asar: options.packager.asar,
backgroundColor: options.nativefier.backgroundColor,
basicAuthPassword: options.nativefier.basicAuthPassword,
basicAuthUsername: options.nativefier.basicAuthUsername,
blockExternalUrls: options.nativefier.blockExternalUrls,
bounce: options.nativefier.bounce,
browserwindowOptions: options.nativefier.browserwindowOptions,
buildDate: new Date().getTime(),
buildVersion: options.packager.buildVersion,
clearCache: options.nativefier.clearCache,
counter: options.nativefier.counter,
crashReporter: options.nativefier.crashReporter,
darwinDarkModeSupport: options.packager.darwinDarkModeSupport,
derefSymlinks: options.packager.derefSymlinks,
disableContextMenu: options.nativefier.disableContextMenu,
disableDevTools: options.nativefier.disableDevTools,
disableGpu: options.nativefier.disableGpu,
disableOldBuildWarning: options.nativefier.disableOldBuildWarning,
diskCacheSize: options.nativefier.diskCacheSize,
download: options.packager.download,
electronVersionUsed: options.packager.electronVersion,
enableEs3Apis: options.nativefier.enableEs3Apis,
executableName: options.packager.executableName,
fastQuit: options.nativefier.fastQuit,
fileDownloadOptions: options.nativefier.fileDownloadOptions,
flashPluginDir: options.nativefier.flashPluginDir,
fullScreen: options.nativefier.fullScreen,
globalShortcuts: options.nativefier.globalShortcuts,
height: options.nativefier.height,
helperBundleId: options.packager.helperBundleId,
hideWindowFrame: options.nativefier.hideWindowFrame,
ignoreCertificate: options.nativefier.ignoreCertificate,
ignoreGpuBlacklist: options.nativefier.ignoreGpuBlacklist,
insecure: options.nativefier.insecure,
internalUrls: options.nativefier.internalUrls,
isUpgrade: options.packager.upgrade,
junk: options.packager.junk,
lang: options.nativefier.lang,
maximize: options.nativefier.maximize,
maxHeight: options.nativefier.maxHeight,
maxWidth: options.nativefier.maxWidth,
minHeight: options.nativefier.minHeight,
minWidth: options.nativefier.minWidth,
name: options.packager.name ?? DEFAULT_APP_NAME,
nativefierVersion: options.nativefier.nativefierVersion,
osxNotarize: options.packager.osxNotarize,
osxSign: options.packager.osxSign,
portable: options.packager.portable,
processEnvs: options.nativefier.processEnvs,
protocols: options.packager.protocols,
proxyRules: options.nativefier.proxyRules,
prune: options.packager.prune,
quiet: options.packager.quiet,
showMenuBar: options.nativefier.showMenuBar,
singleInstance: options.nativefier.singleInstance,
strictInternalUrls: options.nativefier.strictInternalUrls,
targetUrl: options.packager.targetUrl,
titleBarStyle: options.nativefier.titleBarStyle,
tray: options.nativefier.tray,
usageDescription: options.packager.usageDescription,
userAgent: options.nativefier.userAgent,
userAgentHonest: options.nativefier.userAgentHonest,
versionString: options.nativefier.versionString,
width: options.nativefier.width,
widevine: options.nativefier.widevine,
win32metadata: options.packager.win32metadata,
x: options.nativefier.x,
y: options.nativefier.y,
zoom: options.nativefier.zoom,
// OLD_BUILD_WARNING_TEXT is an undocumented env. var to let *packagers*
// tweak the message shown on warning about an old build, to something
// more tailored to their audience (who might not even know Nativefier).
// See https://github.com/kelyvin/Google-Messages-For-Desktop/issues/34#issuecomment-812731144
// and https://github.com/nativefier/nativefier/issues/1131#issuecomment-812646988
oldBuildWarningText: process.env.OLD_BUILD_WARNING_TEXT || '',
};
}
async function maybeCopyScripts(
srcs: string[] | undefined,
dest: string,
): Promise<void> {
if (!srcs || srcs.length === 0) {
log.debug('No files to inject, skipping copy.');
return;
}
const supportedInjectionExtensions = ['.css', '.js'];
log.debug(`Copying ${srcs.length} files to inject in app.`);
for (const src of srcs) {
if (!fs.existsSync(src)) {
throw new Error(
`File ${src} not found. Note that Nativefier expects *local* files, not URLs.`,
);
}
if (supportedInjectionExtensions.indexOf(path.extname(src)) < 0) {
log.warn('Skipping unsupported injection file', src);
continue;
}
const postFixHash = generateRandomSuffix();
const destFileName = `inject-${postFixHash}${path.extname(src)}`;
const destPath = path.join(dest, 'inject', destFileName);
log.debug(`Copying injection file "${src}" to "${destPath}"`);
await fs.copy(src, destPath);
}
}
/**
* Use a basic 6-character hash to prevent collisions. The hash is deterministic url & name,
* so that an upgrade (same URL) of an app keeps using the same appData folder.
* Warning! Changing this normalizing & hashing will change the way appNames are generated,
* changing appData folder, and users will get logged out of their apps after an upgrade.
*/
export function normalizeAppName(appName: string, url: string): string {
const hash = crypto.createHash('md5');
hash.update(url);
const postFixHash = hash.digest('hex').substring(0, 6);
const normalized = appName
.toLowerCase()
.replace(/[,:.]/g, '')
.replace(/[\s_]/g, '-');
return `${normalized}-nativefier-${postFixHash}`;
}
function changeAppPackageJsonName(
appPath: string,
name: string,
url: string,
): string {
const packageJsonPath = path.join(appPath, '/package.json');
const packageJson = parseJson<PackageJSON>(
fs.readFileSync(packageJsonPath).toString(),
);
if (!packageJson) {
throw new Error(`Could not load package.json from ${packageJsonPath}`);
}
const normalizedAppName = normalizeAppName(name, url);
packageJson.name = normalizedAppName;
log.debug(`Updating ${packageJsonPath} 'name' field to ${normalizedAppName}`);
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
return normalizedAppName;
}
/**
* Creates a temporary directory, copies the './app folder' inside,
* and adds a text file with the app configuration.
*/
export async function prepareElectronApp(
src: string,
dest: string,
options: AppOptions,
): Promise<void> {
log.debug(`Copying electron app from ${src} to ${dest}`);
try {
await fs.copy(src, dest);
} catch (err: unknown) {
throw `Error copying electron app from ${src} to temp dir ${dest}. Error: ${
(err as Error).message
}`;
}
const appJsonPath = path.join(dest, '/nativefier.json');
const pickedOptions = pickElectronAppArgs(options);
log.debug(`Writing app config to ${appJsonPath}`, pickedOptions);
await fs.writeFile(appJsonPath, JSON.stringify(pickedOptions));
if (options.nativefier.bookmarksMenu) {
const bookmarksJsonPath = path.join(dest, '/bookmarks.json');
try {
await fs.copy(options.nativefier.bookmarksMenu, bookmarksJsonPath);
} catch (err: unknown) {
log.error('Error copying bookmarks menu config file.', err);
}
}
try {
await maybeCopyScripts(options.nativefier.inject, dest);
} catch (err: unknown) {
log.error('Error copying injection files.', err);
}
const normalizedAppName = changeAppPackageJsonName(
dest,
options.packager.name as string,
options.packager.targetUrl,
);
options.packager.appBundleId = `com.electron.nativefier.${normalizedAppName}`;
}

326
src/cli.test.ts Normal file
View File

@ -0,0 +1,326 @@
import 'source-map-support/register';
import { initArgs, parseArgs } from './cli';
import { parseJson } from './utils/parseUtils';
describe('initArgs + parseArgs', () => {
let mockExit: jest.SpyInstance;
beforeEach(() => {
mockExit = jest.spyOn(process, 'exit').mockImplementation();
});
afterEach(() => {
mockExit.mockRestore();
});
test('--help forces exit', () => {
// Mock console.log to not pollute the log with the yargs help text
const mockLog = jest.spyOn(console, 'log').mockImplementation();
initArgs(['https://www.google.com', '--help']);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockLog).toBeCalled();
mockLog.mockRestore();
});
test('--version forces exit', () => {
// Mock console.log to not pollute the log with the yargs help text
const mockLog = jest.spyOn(console, 'log').mockImplementation();
initArgs(['https://www.google.com', '--version']);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockLog).toBeCalled();
mockLog.mockRestore();
});
// Positional options
test('first positional becomes targetUrl', () => {
const args = parseArgs(initArgs(['https://google.com']));
expect(args.targetUrl).toBe('https://google.com');
expect(args.upgrade).toBeUndefined();
});
test('second positional becomes out', () => {
const args = parseArgs(initArgs(['https://google.com', 'tmp']));
expect(args.out).toBe('tmp');
expect(args.targetUrl).toBe('https://google.com');
expect(args.upgrade).toBeUndefined();
});
// App Creation Options
test('upgrade arg', () => {
const args = parseArgs(initArgs(['--upgrade', 'pathToUpgrade']));
expect(args.upgrade).toBe('pathToUpgrade');
expect(args.targetUrl).toBeUndefined();
});
test('upgrade arg with out dir', () => {
const args = parseArgs(initArgs(['tmp', '--upgrade', 'pathToUpgrade']));
expect(args.upgrade).toBe('pathToUpgrade');
expect(args.out).toBe('tmp');
expect(args.targetUrl).toBeUndefined();
});
test('upgrade arg with targetUrl', () => {
expect(() =>
parseArgs(
initArgs(['https://www.google.com', '--upgrade', 'path/to/upgrade']),
),
).toThrow();
});
test('multi-inject', () => {
const args = parseArgs(
initArgs([
'https://google.com',
'--inject',
'test.js',
'--inject',
'test2.js',
'--inject',
'test.css',
'--inject',
'test2.css',
]),
);
expect(args.inject).toEqual([
'test.js',
'test2.js',
'test.css',
'test2.css',
]);
});
test.each([
{ arg: 'app-copyright', shortArg: '', value: '(c) Nativefier' },
{ arg: 'app-version', shortArg: '', value: '2.0.0' },
{ arg: 'background-color', shortArg: '', value: '#FFAA88' },
{ arg: 'basic-auth-username', shortArg: '', value: 'user' },
{ arg: 'basic-auth-password', shortArg: '', value: 'p@ssw0rd' },
{ arg: 'bookmarks-menu', shortArg: '', value: 'bookmarks.json' },
{
arg: 'browserwindow-options',
shortArg: '',
value: '{"test": 456}',
isJsonString: true,
},
{ arg: 'build-version', shortArg: '', value: '3.0.0' },
{
arg: 'crash-reporter',
shortArg: '',
value: 'https://crash-reporter.com',
},
{ arg: 'electron-version', shortArg: 'e', value: '1.0.0' },
{
arg: 'file-download-options',
shortArg: '',
value: '{"test": 789}',
isJsonString: true,
},
{ arg: 'flash-path', shortArg: '', value: 'pathToFlash' },
{ arg: 'global-shortcuts', shortArg: '', value: 'shortcuts.json' },
{ arg: 'icon', shortArg: 'i', value: 'icon.png' },
{ arg: 'internal-urls', shortArg: '', value: '.*' },
{ arg: 'lang', shortArg: '', value: 'fr' },
{ arg: 'name', shortArg: 'n', value: 'Google' },
{
arg: 'process-envs',
shortArg: '',
value: '{"test": 123}',
isJsonString: true,
},
{ arg: 'proxy-rules', shortArg: '', value: 'RULE: PROXY' },
{ arg: 'tray', shortArg: '', value: 'true' },
{ arg: 'user-agent', shortArg: 'u', value: 'FIREFOX' },
{
arg: 'win32metadata',
shortArg: '',
value: '{"ProductName": "Google"}',
isJsonString: true,
},
])('test string arg %s', ({ arg, shortArg, value, isJsonString }) => {
const args = parseArgs(
initArgs(['https://google.com', `--${arg}`, value]),
) as unknown as Record<string, string>;
if (!isJsonString) {
expect(args[arg]).toBe(value);
} else {
expect(args[arg]).toEqual(parseJson(value));
}
if (shortArg) {
const argsShort = parseArgs(
initArgs(['https://google.com', `-${shortArg}`, value]),
) as unknown as Record<string, string>;
if (!isJsonString) {
expect(argsShort[arg]).toBe(value);
} else {
expect(argsShort[arg]).toEqual(parseJson(value));
}
}
});
test.each([
{ arg: 'arch', shortArg: 'a', value: 'x64', badValue: '486' },
{ arg: 'platform', shortArg: 'p', value: 'mac', badValue: 'os2' },
{
arg: 'title-bar-style',
shortArg: '',
value: 'hidden',
badValue: 'cool',
},
])('limited choice arg %s', ({ arg, shortArg, value, badValue }) => {
const args = parseArgs(
initArgs(['https://google.com', `--${arg}`, value]),
) as unknown as Record<string, string>;
expect(args[arg]).toBe(value);
// Mock console.error to not pollute the log with the yargs help text
const mockError = jest.spyOn(console, 'error').mockImplementation();
initArgs(['https://google.com', `--${arg}`, badValue]);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockError).toBeCalled();
mockExit.mockClear();
mockError.mockClear();
if (shortArg) {
const argsShort = parseArgs(
initArgs(['https://google.com', `-${shortArg}`, value]),
) as unknown as Record<string, string>;
expect(argsShort[arg]).toBe(value);
initArgs(['https://google.com', `-${shortArg}`, badValue]);
expect(mockExit).toHaveBeenCalledTimes(1);
expect(mockError).toBeCalled();
}
mockError.mockRestore();
});
test.each([
{ arg: 'always-on-top', shortArg: '' },
{ arg: 'block-external-urls', shortArg: '' },
{ arg: 'bounce', shortArg: '' },
{ arg: 'clear-cache', shortArg: '' },
{ arg: 'conceal', shortArg: 'c' },
{ arg: 'counter', shortArg: '' },
{ arg: 'darwin-dark-mode-support', shortArg: '' },
{ arg: 'disable-context-menu', shortArg: '' },
{ arg: 'disable-dev-tools', shortArg: '' },
{ arg: 'disable-gpu', shortArg: '' },
{ arg: 'disable-old-build-warning-yesiknowitisinsecure', shortArg: '' },
{ arg: 'enable-es3-apis', shortArg: '' },
{ arg: 'fast-quit', shortArg: 'f' },
{ arg: 'flash', shortArg: '' },
{ arg: 'full-screen', shortArg: '' },
{ arg: 'hide-window-frame', shortArg: '' },
{ arg: 'honest', shortArg: '' },
{ arg: 'ignore-certificate', shortArg: '' },
{ arg: 'ignore-gpu-blacklist', shortArg: '' },
{ arg: 'insecure', shortArg: '' },
{ arg: 'maximize', shortArg: '' },
{ arg: 'portable', shortArg: '' },
{ arg: 'show-menu-bar', shortArg: 'm' },
{ arg: 'single-instance', shortArg: '' },
{ arg: 'strict-internal-urls', shortArg: '' },
{ arg: 'verbose', shortArg: '' },
{ arg: 'widevine', shortArg: '' },
])('test boolean arg %s', ({ arg, shortArg }) => {
const defaultArgs = parseArgs(
initArgs(['https://google.com']),
) as unknown as Record<string, boolean>;
expect(defaultArgs[arg]).toBe(false);
const args = parseArgs(
initArgs(['https://google.com', `--${arg}`]),
) as unknown as Record<string, boolean>;
expect(args[arg]).toBe(true);
if (shortArg) {
const argsShort = parseArgs(
initArgs(['https://google.com', `-${shortArg}`]),
) as unknown as Record<string, boolean>;
expect(argsShort[arg]).toBe(true);
}
});
test.each([{ arg: 'no-overwrite', shortArg: '' }])(
'test inversible boolean arg %s',
({ arg, shortArg }) => {
const inverse = arg.startsWith('no-') ? arg.substr(3) : `no-${arg}`;
const defaultArgs = parseArgs(
initArgs(['https://google.com']),
) as unknown as Record<string, boolean>;
expect(defaultArgs[arg]).toBe(false);
expect(defaultArgs[inverse]).toBe(true);
const args = parseArgs(
initArgs(['https://google.com', `--${arg}`]),
) as unknown as Record<string, boolean>;
expect(args[arg]).toBe(true);
expect(args[inverse]).toBe(false);
if (shortArg) {
const argsShort = parseArgs(
initArgs(['https://google.com', `-${shortArg}`]),
) as unknown as Record<string, boolean>;
expect(argsShort[arg]).toBe(true);
expect(argsShort[inverse]).toBe(true);
}
},
);
test.each([
{ arg: 'disk-cache-size', shortArg: '', value: 100 },
{ arg: 'height', shortArg: '', value: 200 },
{ arg: 'max-height', shortArg: '', value: 300 },
{ arg: 'max-width', shortArg: '', value: 400 },
{ arg: 'min-height', shortArg: '', value: 500 },
{ arg: 'min-width', shortArg: '', value: 600 },
{ arg: 'width', shortArg: '', value: 700 },
{ arg: 'x', shortArg: '', value: 800 },
{ arg: 'y', shortArg: '', value: 900 },
])('test numeric arg %s', ({ arg, shortArg, value }) => {
const args = parseArgs(
initArgs(['https://google.com', `--${arg}`, `${value}`]),
) as unknown as Record<string, number>;
expect(args[arg]).toBe(value);
const badArgs = parseArgs(
initArgs(['https://google.com', `--${arg}`, 'abcd']),
) as unknown as Record<string, number>;
expect(badArgs[arg]).toBeNaN();
if (shortArg) {
const shortArgs = parseArgs(
initArgs(['https://google.com', `-${shortArg}`, `${value}`]),
) as unknown as Record<string, number>;
expect(shortArgs[arg]).toBe(value);
const badShortArgs = parseArgs(
initArgs(['https://google.com', `-${shortArg}`, 'abcd']),
) as unknown as Record<string, number>;
expect(badShortArgs[arg]).toBeNaN();
}
});
test.each([
{ arg: 'tray', value: 'true' },
{ arg: 'tray', value: 'false' },
{ arg: 'tray', value: 'start-in-tray' },
{ arg: 'tray', value: '' },
])('test tray valyue %s', ({ arg, value }) => {
const args = parseArgs(
initArgs(['https://google.com', `--${arg}`, `${value}`]),
) as unknown as Record<string, number>;
if (value !== '') {
expect(args[arg]).toBe(value);
} else {
expect(args[arg]).toBe('true');
}
});
test('test tray value defaults to false', () => {
const args = parseArgs(initArgs(['https://google.com']));
expect(args.tray).toBe('false');
});
});

705
src/cli.ts Executable file
View File

@ -0,0 +1,705 @@
#!/usr/bin/env node
import 'source-map-support/register';
import electronPackager = require('electron-packager');
import * as log from 'loglevel';
import yargs from 'yargs';
import { DEFAULT_ELECTRON_VERSION } from './constants';
import {
camelCased,
checkInternet,
getProcessEnvs,
isArgFormatInvalid,
} from './helpers/helpers';
import { supportedArchs, supportedPlatforms } from './infer/inferOs';
import { buildNativefierApp } from './main';
import { RawOptions } from '../shared/src/options/model';
import { parseJson } from './utils/parseUtils';
// @types/yargs@17.x started pretending yargs.argv can be a promise:
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/8e17f9ca957a06040badb53ae7688fbb74229ccf/types/yargs/index.d.ts#L73
// Dunno in which case it happens, but it doesn't for us! So, having to await
// (and end up having to flag sync code as async) would be useless and annoying.
// So, copy-pastaing and axing the Promise half of yargs's type definition,
// to have a *non*-promise type. Maybe that's wrong. If it is, this type should
// be dropped, and extra async-ness should be added where needed.
type YargsArgvSync<T> = {
[key in keyof yargs.Arguments<T> as
| key
| yargs.CamelCaseKey<key>]: yargs.Arguments<T>[key];
};
export function initArgs(argv: string[]): yargs.Argv<RawOptions> {
const sanitizedArgs = sanitizeArgs(argv);
const args = yargs(sanitizedArgs)
.scriptName('nativefier')
.usage(
'$0 <targetUrl> [outputDirectory] [other options]\nor\n$0 --upgrade <pathToExistingApp> [other options]',
)
.example(
'$0 <targetUrl> -n <name>',
'Make an app from <targetUrl> and set the application name to <name>',
)
.example(
'$0 --upgrade <pathToExistingApp>',
'Upgrade (in place) the existing Nativefier app at <pathToExistingApp>',
)
.example(
'$0 <targetUrl> -p <platform> -a <arch>',
'Make an app from <targetUrl> for the OS <platform> and CPU architecture <arch>',
)
.example(
'for more examples and help...',
'See https://github.com/nativefier/nativefier/blob/master/CATALOG.md',
)
.positional('targetUrl', {
description:
'the URL that you wish to to turn into a native app; required if not using --upgrade',
type: 'string',
})
.positional('outputDirectory', {
defaultDescription:
'defaults to the current directory, or env. var. NATIVEFIER_APPS_DIR if set',
description: 'the directory to generate the app in',
normalize: true,
type: 'string',
})
// App Creation Options
.option('a', {
alias: 'arch',
choices: supportedArchs,
defaultDescription: "current Node's arch",
description: 'the CPU architecture to build for',
type: 'string',
})
.option('c', {
alias: 'conceal',
default: false,
description: 'package the app source code into an asar archive',
type: 'boolean',
})
.option('e', {
alias: 'electron-version',
defaultDescription: DEFAULT_ELECTRON_VERSION,
description:
"specify the electron version to use (without the 'v'); see https://github.com/electron/electron/releases",
})
.option('global-shortcuts', {
description:
'define global keyboard shortcuts via a JSON file; See https://github.com/nativefier/nativefier/blob/master/API.md#global-shortcuts',
normalize: true,
type: 'string',
})
.option('i', {
alias: 'icon',
description:
'the icon file to use as the icon for the app (.ico on Windows, .icns/.png on macOS, .png on Linux)',
normalize: true,
type: 'string',
})
.option('n', {
alias: 'name',
defaultDescription: 'the title of the page passed via targetUrl',
description: 'specify the name of the app',
type: 'string',
})
.option('no-overwrite', {
default: false,
description: 'do not overwrite output directory if it already exists',
type: 'boolean',
})
.option('overwrite', {
// This is needed to have the `no-overwrite` flag to work correctly
default: true,
hidden: true,
type: 'boolean',
})
.option('p', {
alias: 'platform',
choices: supportedPlatforms,
defaultDescription: 'current operating system',
description: 'the operating system platform to build for',
type: 'string',
})
.option('portable', {
default: false,
description:
'make the app store its user data in the app folder; WARNING: see https://github.com/nativefier/nativefier/blob/master/API.md#portable for security risks',
type: 'boolean',
})
.option('upgrade', {
description:
'upgrade an app built by an older version of Nativefier\nYou must pass the full path to the existing app executable (app will be overwritten with upgraded version by default)',
normalize: true,
type: 'string',
})
.option('widevine', {
default: false,
description:
"use a Widevine-enabled version of Electron for DRM playback (use at your own risk, it's unofficial, provided by CastLabs)",
type: 'boolean',
})
.group(
[
'arch',
'conceal',
'electron-version',
'global-shortcuts',
'icon',
'name',
'no-overwrite',
'platform',
'portable',
'upgrade',
'widevine',
],
decorateYargOptionGroup('App Creation Options'),
)
// App Window Options
.option('always-on-top', {
default: false,
description: 'enable always on top window',
type: 'boolean',
})
.option('background-color', {
description:
"set the app background color, for better integration while the app is loading. Example value: '#2e2c29'",
type: 'string',
})
.option('bookmarks-menu', {
description:
'create a bookmarks menu (via JSON file); See https://github.com/nativefier/nativefier/blob/master/API.md#bookmarks-menu',
normalize: true,
type: 'string',
})
.option('browserwindow-options', {
coerce: parseJson,
description:
'override Electron BrowserWindow options (via JSON string); see https://github.com/nativefier/nativefier/blob/master/API.md#browserwindow-options',
})
.option('disable-context-menu', {
default: false,
description: 'disable the context menu (right click)',
type: 'boolean',
})
.option('disable-dev-tools', {
default: false,
description: 'disable developer tools (Ctrl+Shift+I / F12)',
type: 'boolean',
})
.option('full-screen', {
default: false,
description: 'always start the app full screen',
type: 'boolean',
})
.option('height', {
defaultDescription: '800',
description: 'set window default height in pixels',
type: 'number',
})
.option('hide-window-frame', {
default: false,
description: 'disable window frame and controls',
type: 'boolean',
})
.option('m', {
alias: 'show-menu-bar',
default: false,
description: 'set menu bar visible',
type: 'boolean',
})
.option('max-height', {
defaultDescription: 'unlimited',
description: 'set window maximum height in pixels',
type: 'number',
})
.option('max-width', {
defaultDescription: 'unlimited',
description: 'set window maximum width in pixels',
type: 'number',
})
.option('maximize', {
default: false,
description: 'always start the app maximized',
type: 'boolean',
})
.option('min-height', {
defaultDescription: '0',
description: 'set window minimum height in pixels',
type: 'number',
})
.option('min-width', {
defaultDescription: '0',
description: 'set window minimum width in pixels',
type: 'number',
})
.option('process-envs', {
coerce: getProcessEnvs,
description:
'a JSON string of key/value pairs to be set as environment variables before any browser windows are opened',
})
.option('single-instance', {
default: false,
description: 'allow only a single instance of the app',
type: 'boolean',
})
.option('tray', {
default: 'false',
description:
"allow app to stay in system tray. If 'start-in-tray' is set as argument, don't show main window on first start",
choices: ['true', 'false', 'start-in-tray'],
})
.option('width', {
defaultDescription: '1280',
description: 'app window default width in pixels',
type: 'number',
})
.option('x', {
description: 'set window x location in pixels from left',
type: 'number',
})
.option('y', {
description: 'set window y location in pixels from top',
type: 'number',
})
.option('zoom', {
default: 1.0,
description: 'set the default zoom factor for the app',
type: 'number',
})
.group(
[
'always-on-top',
'background-color',
'bookmarks-menu',
'browserwindow-options',
'disable-context-menu',
'disable-dev-tools',
'full-screen',
'height',
'hide-window-frame',
'm',
'max-width',
'max-height',
'maximize',
'min-height',
'min-width',
'process-envs',
'single-instance',
'tray',
'width',
'x',
'y',
'zoom',
],
decorateYargOptionGroup('App Window Options'),
)
// Internal Browser Options
.option('file-download-options', {
coerce: parseJson,
description:
'a JSON string defining file download options; see https://github.com/sindresorhus/electron-dl',
})
.option('inject', {
description:
'path to a CSS/JS file to be injected; pass multiple times to inject multiple files',
string: true,
type: 'array',
})
.option('lang', {
defaultDescription: 'os language at runtime of the app',
description:
'set the language or locale to render the web site as (e.g., "fr", "en-US", "es", etc.)',
type: 'string',
})
.option('u', {
alias: 'user-agent',
description:
"set the app's user agent string; may also use 'edge', 'firefox', or 'safari' to have one auto-generated",
type: 'string',
})
.option('user-agent-honest', {
alias: 'honest',
default: false,
description:
'prevent the normal changing of the user agent string to appear as a regular Chrome browser',
type: 'boolean',
})
.group(
[
'file-download-options',
'inject',
'lang',
'user-agent',
'user-agent-honest',
],
decorateYargOptionGroup('Internal Browser Options'),
)
// Internal Browser Cache Options
.option('clear-cache', {
default: false,
description: 'prevent the app from preserving cache between launches',
type: 'boolean',
})
.option('disk-cache-size', {
defaultDescription: 'chromium default',
description:
'set the maximum disk space (in bytes) to be used by the disk cache',
type: 'number',
})
.group(
['clear-cache', 'disk-cache-size'],
decorateYargOptionGroup('Internal Browser Cache Options'),
)
// URL Handling Options
.option('block-external-urls', {
default: false,
description: `forbid navigation to URLs not considered "internal" (see '--internal-urls'). Instead of opening in an external browser, attempts to navigate to external URLs will be blocked`,
type: 'boolean',
})
.option('internal-urls', {
defaultDescription: 'URLs sharing the same base domain',
description: `regex of URLs to consider "internal"; by default matches based on domain (see '--strict-internal-urls'); all other URLs will be opened in an external browser`,
type: 'string',
})
.option('strict-internal-urls', {
default: false,
description: 'disable domain-based matching on internal URLs',
type: 'boolean',
})
.option('proxy-rules', {
description:
'proxy rules; see https://www.electronjs.org/docs/api/session#sessetproxyconfig',
type: 'string',
})
.group(
[
'block-external-urls',
'internal-urls',
'strict-internal-urls',
'proxy-rules',
],
decorateYargOptionGroup('URL Handling Options'),
)
// Auth Options
.option('basic-auth-password', {
description: 'basic http(s) auth password',
type: 'string',
})
.option('basic-auth-username', {
description: 'basic http(s) auth username',
type: 'string',
})
.group(
['basic-auth-password', 'basic-auth-username'],
decorateYargOptionGroup('Auth Options'),
)
// Graphics Options
.option('disable-gpu', {
default: false,
description: 'disable hardware acceleration',
type: 'boolean',
})
.option('enable-es3-apis', {
default: false,
description: 'force activation of WebGL 2.0',
type: 'boolean',
})
.option('ignore-gpu-blacklist', {
default: false,
description: 'force WebGL apps to work on unsupported GPUs',
type: 'boolean',
})
.group(
['disable-gpu', 'enable-es3-apis', 'ignore-gpu-blacklist'],
decorateYargOptionGroup('Graphics Options'),
)
// (In)Security Options
.option('disable-old-build-warning-yesiknowitisinsecure', {
default: false,
description:
'disable warning shown when opening an app made too long ago; Nativefier uses the Chrome browser (through Electron), and it is dangerous to keep using an old version of it',
type: 'boolean',
})
.option('ignore-certificate', {
default: false,
description: 'ignore certificate-related errors',
type: 'boolean',
})
.option('insecure', {
default: false,
description: 'enable loading of insecure content',
type: 'boolean',
})
.group(
[
'disable-old-build-warning-yesiknowitisinsecure',
'ignore-certificate',
'insecure',
],
decorateYargOptionGroup('(In)Security Options'),
)
// Flash Options (DEPRECATED)
.option('flash', {
default: false,
deprecated: true,
description: 'enable Adobe Flash',
hidden: true,
type: 'boolean',
})
.option('flash-path', {
deprecated: true,
description: 'path to Chrome flash plugin; find it in `chrome://plugins`',
hidden: true,
normalize: true,
type: 'string',
})
// Platform Specific Options
.option('app-copyright', {
description:
'(macOS, windows only) set a human-readable copyright line for the app; maps to `LegalCopyright` metadata property on Windows, and `NSHumanReadableCopyright` on macOS',
type: 'string',
})
.option('app-version', {
description:
'(macOS, windows only) set the version of the app; maps to the `ProductVersion` metadata property on Windows, and `CFBundleShortVersionString` on macOS',
type: 'string',
})
.option('bounce', {
default: false,
description:
'(macOS only) make the dock icon bounce when the counter increases',
type: 'boolean',
})
.option('build-version', {
description:
'(macOS, windows only) set the build version of the app; maps to `FileVersion` metadata property on Windows, and `CFBundleVersion` on macOS',
type: 'string',
})
.option('counter', {
default: false,
description:
'(macOS only) set a dock count badge, determined by looking for a number in the window title',
type: 'boolean',
})
.option('darwin-dark-mode-support', {
default: false,
description: '(macOS only) enable Dark Mode support on macOS 10.14+',
type: 'boolean',
})
.option('f', {
alias: 'fast-quit',
default: false,
description: '(macOS only) quit app on window close',
type: 'boolean',
})
.option('title-bar-style', {
choices: ['hidden', 'hiddenInset'],
description:
'(macOS only) set title bar style; consider injecting custom CSS (via --inject) for better integration',
type: 'string',
})
.option('win32metadata', {
coerce: (value: string) =>
parseJson<electronPackager.Win32MetadataOptions>(value),
description:
'(windows only) a JSON string of key/value pairs (ProductName, InternalName, FileDescription) to embed as executable metadata',
})
.group(
[
'app-copyright',
'app-version',
'bounce',
'build-version',
'counter',
'darwin-dark-mode-support',
'fast-quit',
'title-bar-style',
'win32metadata',
],
decorateYargOptionGroup('Platform-Specific Options'),
)
// Debug Options
.option('crash-reporter', {
description: 'remote server URL to send crash reports',
type: 'string',
})
.option('verbose', {
default: false,
description: 'enable verbose/debug/troubleshooting logs',
type: 'boolean',
})
.option('quiet', {
default: false,
description: 'suppress all logging',
type: 'boolean',
})
.group(
['crash-reporter', 'verbose', 'quiet'],
decorateYargOptionGroup('Debug Options'),
)
.version()
.help()
.group(['version', 'help'], 'Other Options')
.wrap(yargs.terminalWidth());
// We must access argv in order to get yargs to actually process args
// Do this now to go ahead and get any errors out of the way
args.argv as YargsArgvSync<RawOptions>;
return args as yargs.Argv<RawOptions>;
}
function decorateYargOptionGroup(value: string): string {
return `====== ${value} ======`;
}
export function parseArgs(args: yargs.Argv<RawOptions>): RawOptions {
const parsed = { ...(args.argv as YargsArgvSync<RawOptions>) };
// In yargs, the _ property of the parsed args is an array of the positional args
// https://github.com/yargs/yargs/blob/master/docs/examples.md#and-non-hyphenated-options-too-just-use-argv_
// So try to extract the targetUrl and outputDirectory from these
parsed.targetUrl = parsed._.length > 0 ? parsed._[0].toString() : undefined;
parsed.out = parsed._.length > 1 ? (parsed._[1] as string) : undefined;
if (parsed.upgrade && parsed.targetUrl) {
let targetAndUpgrade = false;
if (!parsed.out) {
// If we're upgrading, the first positional args might be the outputDirectory, so swap these if we can
try {
// If this succeeds, we have a problem
new URL(parsed.targetUrl);
targetAndUpgrade = true;
} catch {
// Cool, it's not a URL
parsed.out = parsed.targetUrl;
parsed.targetUrl = undefined;
}
} else {
// Someone supplied a targetUrl, an outputDirectory, and --upgrade. That's not cool.
targetAndUpgrade = true;
}
if (targetAndUpgrade) {
throw new Error(
'ERROR: Nativefier must be called with either a targetUrl or the --upgrade option, not both.\n',
);
}
}
if (!parsed.targetUrl && !parsed.upgrade) {
throw new Error(
'ERROR: Nativefier must be called with either a targetUrl or the --upgrade option.\n',
);
}
parsed.noOverwrite = parsed['no-overwrite'] = !parsed.overwrite;
// Since coerce in yargs seems to have broken since
// https://github.com/yargs/yargs/pull/1978
for (const arg of [
'win32metadata',
'browserwindow-options',
'file-download-options',
]) {
if (parsed[arg] && typeof parsed[arg] === 'string') {
parsed[arg] = parseJson(parsed[arg] as string);
// sets fileDownloadOptions and browserWindowOptions
// as parsed object as they were still strings in `nativefier.json`
// because only their snake-cased variants were being parsed above
parsed[camelCased(arg)] = parsed[arg];
}
}
if (parsed['process-envs'] && typeof parsed['process-envs'] === 'string') {
parsed['process-envs'] = getProcessEnvs(parsed['process-envs']);
}
return parsed;
}
function sanitizeArgs(argv: string[]): string[] {
const sanitizedArgs: string[] = [];
argv.forEach((arg) => {
if (isArgFormatInvalid(arg)) {
throw new Error(
`Invalid argument passed: ${arg} .\nNativefier supports short options (like "-n") and long options (like "--name"), all lowercase. Run "nativefier --help" for help.\nAborting`,
);
}
const isLastArg = sanitizedArgs.length + 1 === argv.length;
if (sanitizedArgs.length > 0) {
const previousArg = sanitizedArgs[sanitizedArgs.length - 1];
log.debug({ arg, previousArg, isLastArg });
// Work around commander.js not supporting default argument for options
if (
previousArg === '--tray' &&
!['true', 'false', 'start-in-tray'].includes(arg)
) {
sanitizedArgs.push('true');
}
}
sanitizedArgs.push(arg);
if (arg === '--tray' && isLastArg) {
// Add a true if --tray is last so it gets enabled
sanitizedArgs.push('true');
}
});
return sanitizedArgs;
}
if (require.main === module) {
let args: yargs.Argv<RawOptions> | undefined = undefined;
let parsedArgs: RawOptions;
try {
args = initArgs(process.argv.slice(2));
parsedArgs = parseArgs(args);
} catch (err: unknown) {
if (args) {
log.error(err);
args.showHelp();
} else {
log.error('Failed to parse command-line arguments. Aborting.', err);
}
process.exit(1);
}
const options: RawOptions = {
...parsedArgs,
};
if (options.verbose) {
log.setLevel('trace');
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
require('debug').enable('electron-packager');
} catch (err: unknown) {
log.debug(
'Failed to enable electron-packager debug output. This should not happen,',
'and suggests their internals changed. Please report an issue.',
);
}
log.debug(
'Running in verbose mode! This will produce a mountain of logs and',
'is recommended only for troubleshooting or if you like Shakespeare.',
);
} else if (options.quiet) {
log.setLevel('silent');
} else {
log.setLevel('info');
}
checkInternet();
if (!options.out && process.env.NATIVEFIER_APPS_DIR) {
options.out = process.env.NATIVEFIER_APPS_DIR;
}
buildNativefierApp(options).catch((error) => {
log.error('Error during build. Run with --verbose for details.', error);
});
}

29
src/constants.ts Normal file
View File

@ -0,0 +1,29 @@
import * as path from 'path';
export const DEFAULT_APP_NAME = 'APP';
// Upgrade both DEFAULT_ELECTRON_VERSION and DEFAULT_CHROME_VERSION together, and
// - upgrade app / package.json / "devDependencies" / "electron"
// - upgrade package.json / "devDependencies" / "electron"
// Doing a *major* upgrade? Read https://github.com/nativefier/nativefier/blob/master/HACKING.md#deps-major-upgrading-electron
export const DEFAULT_ELECTRON_VERSION = '25.7.0';
// https://atom.io/download/atom-shell/index.json
// https://www.electronjs.org/releases/stable
export const DEFAULT_CHROME_VERSION = '114.0.5735.289';
// Update each of these periodically
// https://product-details.mozilla.org/1.0/firefox_versions.json
export const DEFAULT_FIREFOX_VERSION = '116.0.3';
// https://en.wikipedia.org/wiki/Safari_version_history
export const DEFAULT_SAFARI_VERSION = {
majorVersion: 16,
version: '16.6',
webkitVersion: '605.1.15',
};
export const ELECTRON_MAJOR_VERSION = parseInt(
DEFAULT_ELECTRON_VERSION.split('.')[0],
10,
);
export const PLACEHOLDER_APP_DIR = path.join(__dirname, './../', 'app');

19
src/helpers/fsHelpers.ts Normal file
View File

@ -0,0 +1,19 @@
import * as fs from 'fs';
export function dirExists(dirName: string): boolean {
try {
const dirStat = fs.statSync(dirName);
return dirStat.isDirectory();
} catch {
return false;
}
}
export function fileExists(fileName: string): boolean {
try {
const fileStat = fs.statSync(fileName);
return fileStat.isFile();
} catch {
return false;
}
}

View File

@ -0,0 +1,84 @@
import {
isArgFormatInvalid,
generateRandomSuffix,
camelCased,
} from './helpers';
describe('isArgFormatInvalid', () => {
test('is false for correct short args', () => {
expect(isArgFormatInvalid('-t')).toBe(false);
});
test('is true for improperly double-dashed short args', () => {
expect(isArgFormatInvalid('--t')).toBe(true);
});
test('is false for --x and --y (backwards compat, we should have made these short, oh well)', () => {
expect(isArgFormatInvalid('--x')).toBe(false);
expect(isArgFormatInvalid('--y')).toBe(false);
});
test('is false for correct long args', () => {
expect(isArgFormatInvalid('--test')).toBe(false);
});
test('is true for improperly triple-dashed long args', () => {
expect(isArgFormatInvalid('---test')).toBe(true);
});
test('is true for improperly single-dashed long args', () => {
expect(isArgFormatInvalid('-test')).toBe(true);
});
test('is false for correct long args with dashes', () => {
expect(isArgFormatInvalid('--test-run')).toBe(false);
});
test('is false for correct long args with many dashes', () => {
expect(isArgFormatInvalid('--test-run-with-many-dashes')).toBe(false);
});
});
describe('generateRandomSuffix', () => {
test('is not empty', () => {
expect(generateRandomSuffix()).not.toBe('');
});
test('is not null', () => {
expect(generateRandomSuffix()).not.toBeNull();
});
test('is not undefined', () => {
expect(generateRandomSuffix()).toBeDefined();
});
test('is different per call', () => {
expect(generateRandomSuffix()).not.toBe(generateRandomSuffix());
});
test('respects the length param', () => {
expect(generateRandomSuffix(10).length).toBe(10);
});
});
describe('camelCased', () => {
test('has no hyphens in camel case', () => {
expect(camelCased('file-download')).toEqual(expect.not.stringMatching(/-/));
});
test('returns camel cased string', () => {
expect(camelCased('file-download')).toBe('fileDownload');
});
test('has no spaces in camel case', () => {
expect(camelCased('--file--download--')).toBe('fileDownload');
});
test('handles multiple hyphens properly', () => {
expect(camelCased('file--download--options')).toBe('fileDownloadOptions');
});
test('does not affect non-snake cased strings', () => {
expect(camelCased('win32options')).toBe('win32options');
});
});

211
src/helpers/helpers.ts Normal file
View File

@ -0,0 +1,211 @@
import { spawnSync } from 'child_process';
import * as crypto from 'crypto';
import * as os from 'os';
import * as path from 'path';
import axios from 'axios';
import * as dns from 'dns';
import * as hasbin from 'hasbin';
import * as log from 'loglevel';
import * as tmp from 'tmp';
import { parseJson } from '../utils/parseUtils';
tmp.setGracefulCleanup(); // cleanup temp dirs even when an uncaught exception occurs
const now = new Date();
const TMP_TIME = `${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`;
export type DownloadResult = {
data: Buffer;
ext: string;
};
type ProcessEnvs = Record<string, unknown>;
export function hasWine(): boolean {
return hasbin.sync('wine');
}
// I tried to place this (and the other is* functions) in
// a new shared helpers, but alas eslint gets real confused
// about the type signatures and thinks they're all any.
// TODO: Figure out a way to refactor duplicate code from
// src/helpers/helpers.ts and app/src/helpers/helpers.ts
// into the shared module
export function isLinux(): boolean {
return os.platform() === 'linux';
}
export function isOSX(): boolean {
return os.platform() === 'darwin';
}
export function isWindows(): boolean {
return os.platform() === 'win32';
}
export function isWindowsAdmin(): boolean {
if (process.platform !== 'win32') {
return false;
}
// https://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights
// https://stackoverflow.com/questions/57009374/check-admin-or-non-admin-users-in-nodejs-or-javascript
return spawnSync('fltmc').status === 0;
}
/**
* Create a temp directory with a debug-friendly name, and return its path.
* Will be automatically deleted on exit.
*/
export function getTempDir(prefix: string, mode?: number): string {
return tmp.dirSync({
mode,
unsafeCleanup: true, // recursively remove tmp dir on exit, even if not empty.
prefix: `nativefier-${TMP_TIME}-${prefix}-`,
}).name;
}
export function downloadFile(
fileUrl: string,
): Promise<DownloadResult | undefined> {
log.debug(`Downloading ${fileUrl}`);
return axios
.get<Buffer>(fileUrl, {
responseType: 'arraybuffer',
})
.then((response) => {
if (!response.data) {
return undefined;
}
return {
data: response.data,
ext: path.extname(fileUrl),
};
});
}
export function getAllowedIconFormats(platform: string): string[] {
const hasIdentify = hasbin.sync('identify') || hasbin.sync('gm');
const hasConvert = hasbin.sync('convert') || hasbin.sync('gm');
const hasIconUtil = hasbin.sync('iconutil');
const pngToIcns = hasConvert && hasIconUtil;
const pngToIco = hasConvert;
const icoToIcns = pngToIcns && hasIdentify;
const icoToPng = hasConvert;
// Unsupported
const icnsToPng = false;
const icnsToIco = false;
const formats: string[] = [];
// Shell scripting is not supported on windows, temporary override
if (isWindows()) {
switch (platform) {
case 'darwin':
formats.push('.icns');
break;
case 'linux':
formats.push('.png');
break;
case 'win32':
formats.push('.ico');
break;
default:
throw new Error(`Unknown platform ${platform}`);
}
log.debug(
`Allowed icon formats when building for ${platform} (limited on Windows):`,
formats,
);
return formats;
}
switch (platform) {
case 'darwin':
formats.push('.icns');
if (pngToIcns) {
formats.push('.png');
}
if (icoToIcns) {
formats.push('.ico');
}
break;
case 'linux':
formats.push('.png');
if (icoToPng) {
formats.push('.ico');
}
if (icnsToPng) {
formats.push('.icns');
}
break;
case 'win32':
formats.push('.ico');
if (pngToIco) {
formats.push('.png');
}
if (icnsToIco) {
formats.push('.icns');
}
break;
default:
throw new Error(`Unknown platform ${platform}`);
}
log.debug(`Allowed icon formats when building for ${platform}:`, formats);
return formats;
}
/**
* Refuse args like '--n' or '-name', we accept either short '-n' or long '--name'
*/
export function isArgFormatInvalid(arg: string): boolean {
return (
(arg.startsWith('---') ||
/^--[a-z]$/i.exec(arg) !== null ||
/^-[a-z]{2,}$/i.exec(arg) !== null) &&
!['--x', '--y'].includes(arg) // exception for long args --{x,y}
);
}
export function generateRandomSuffix(length = 6): string {
const hash = crypto.createHash('md5');
// Add a random salt to help avoid collisions
hash.update(crypto.randomBytes(256));
return hash.digest('hex').substring(0, length);
}
export function getProcessEnvs(val: string): ProcessEnvs | undefined {
if (!val) {
return undefined;
}
return parseJson<ProcessEnvs>(val);
}
export function checkInternet(): void {
dns.lookup('npmjs.com', (err) => {
if (err && err.code === 'ENOTFOUND') {
log.warn(
'\nNo Internet Connection\nTo offline build, download electron from https://github.com/electron/electron/releases\nand place in ~/AppData/Local/electron/Cache/ on Windows,\n~/.cache/electron on Linux or ~/Library/Caches/electron/ on Mac\nUse --electron-version to specify the version you downloaded.',
);
}
});
}
/**
* Takes in a snake-cased string and converts to camelCase
*/
export function camelCased(str: string): string {
return str
.split('-')
.filter((s) => s.length > 0)
.map((word, i) => {
if (i === 0) return word;
return `${word[0].toUpperCase()}${word.substring(1)}`;
})
.join('');
}

View File

@ -0,0 +1,100 @@
import * as path from 'path';
import { spawnSync } from 'child_process';
import { isWindows, isOSX, getTempDir } from './helpers';
import * as log from 'loglevel';
const SCRIPT_PATHS = {
singleIco: path.join(__dirname, '../..', 'icon-scripts/singleIco'),
convertToPng: path.join(__dirname, '../..', 'icon-scripts/convertToPng'),
convertToIco: path.join(__dirname, '../..', 'icon-scripts/convertToIco'),
convertToIcns: path.join(__dirname, '../..', 'icon-scripts/convertToIcns'),
convertToTrayIcon: path.join(
__dirname,
'../..',
'icon-scripts/convertToTrayIcon',
),
};
/**
* Executes a shell script with the form "./pathToScript param1 param2"
*/
function iconShellHelper(
shellScriptPath: string,
icoSource: string,
icoDestination: string,
): string {
if (isWindows()) {
throw new Error(
'Icon conversion only supported on macOS or Linux. ' +
'If building for Windows, download/create a .ico and pass it with --icon favicon.ico . ' +
'If building for macOS/Linux, do it from macOS/Linux',
);
}
const shellCommand = `"${shellScriptPath}" "${icoSource}" "${icoDestination}"`;
log.debug(
`Converting icon ${icoSource} to ${icoDestination}.`,
`Calling shell command: ${shellCommand}`,
);
const { stdout, stderr, status } = spawnSync(
shellScriptPath,
[icoSource, icoDestination],
{ timeout: 10000 },
);
if (status) {
throw new Error(
`Icon conversion failed with status code ${status}.\nstdout: ${stdout.toString()}\nstderr: ${stderr.toString()}`,
);
}
log.debug(`Conversion succeeded and produced icon at ${icoDestination}`);
return icoDestination;
}
export function singleIco(icoSrc: string): string {
return iconShellHelper(
SCRIPT_PATHS.singleIco,
icoSrc,
`${getTempDir('iconconv')}/icon.ico`,
);
}
export function convertToPng(icoSrc: string): string {
return iconShellHelper(
SCRIPT_PATHS.convertToPng,
icoSrc,
`${getTempDir('iconconv')}/icon.png`,
);
}
export function convertToIco(icoSrc: string): string {
return iconShellHelper(
SCRIPT_PATHS.convertToIco,
icoSrc,
`${getTempDir('iconconv')}/icon.ico`,
);
}
export function convertToIcns(icoSrc: string): string {
if (!isOSX()) {
throw new Error('macOS is required to convert to a .icns icon');
}
return iconShellHelper(
SCRIPT_PATHS.convertToIcns,
icoSrc,
`${getTempDir('iconconv')}/icon.icns`,
);
}
export function convertToTrayIcon(icoSrc: string): string {
if (!isOSX()) {
throw new Error('macOS is required to convert from a .icns icon');
}
return iconShellHelper(
SCRIPT_PATHS.convertToTrayIcon,
icoSrc,
`${path.dirname(icoSrc)}/tray-icon.png`,
);
}

View File

@ -0,0 +1,208 @@
import * as fs from 'fs';
import * as path from 'path';
import * as log from 'loglevel';
import { NativefierOptions } from '../../../shared/src/options/model';
import { getVersionString } from './rceditGet';
import { fileExists } from '../fsHelpers';
type ExecutableInfo = {
arch?: string;
};
function getExecutableBytes(executablePath: string): Uint8Array {
return fs.readFileSync(executablePath);
}
function getExecutableArch(
exeBytes: Uint8Array,
platform: string,
): string | undefined {
switch (platform) {
case 'linux':
// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
switch (exeBytes[0x12]) {
case 0x03:
return 'ia32';
case 0x28:
return 'armv7l';
case 0x3e:
return 'x64';
case 0xb7:
return 'arm64';
default:
return undefined;
}
case 'darwin':
case 'mas':
// https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
switch ((exeBytes[0x04] << 8) + exeBytes[0x05]) {
case 0x0700:
return 'x64';
case 0x0c00:
return 'arm64';
default:
return undefined;
}
case 'win32':
// https://en.wikibooks.org/wiki/X86_Disassembly/Windows_Executable_Files#COFF_Header
switch ((exeBytes[0x7d] << 8) + exeBytes[0x7c]) {
case 0x014c:
return 'ia32';
case 0x8664:
return 'x64';
case 0xaa64:
return 'arm64';
default:
return undefined;
}
default:
return undefined;
}
}
function getExecutableInfo(
executablePath: string,
platform: string,
): ExecutableInfo | undefined {
if (!fileExists(executablePath)) {
return undefined;
}
const exeBytes = getExecutableBytes(executablePath);
return {
arch: getExecutableArch(exeBytes, platform),
};
}
export function getOptionsFromExecutable(
appResourcesDir: string,
priorOptions: NativefierOptions,
): NativefierOptions {
const newOptions: NativefierOptions = { ...priorOptions };
if (!newOptions.name) {
throw new Error(
'Can not extract options from executable with no name specified.',
);
}
const name: string = newOptions.name;
let executablePath: string | undefined = undefined;
const appRoot = path.resolve(path.join(appResourcesDir, '..', '..'));
const children = fs.readdirSync(appRoot, { withFileTypes: true });
const looksLikeMacOS =
children.filter((c) => c.name === 'MacOS' && c.isDirectory()).length > 0;
const looksLikeWindows =
children.filter((c) => c.name.toLowerCase().endsWith('.exe') && c.isFile())
.length > 0;
const looksLikeLinux =
children.filter((c) => c.name.toLowerCase().endsWith('.so') && c.isFile())
.length > 0;
if (looksLikeMacOS) {
log.debug('This looks like a MacOS app...');
if (newOptions.platform === undefined) {
newOptions.platform =
children.filter((c) => c.name === 'Library' && c.isDirectory()).length >
0
? 'mas'
: 'darwin';
}
executablePath = path.join(
appRoot,
'MacOS',
fs.readdirSync(path.join(appRoot, 'MacOS'))[0],
);
} else if (looksLikeWindows) {
log.debug('This looks like a Windows app...');
if (newOptions.platform === undefined) {
newOptions.platform = 'win32';
}
executablePath = path.join(
appRoot,
children.filter(
(c) =>
c.name.toLowerCase() === `${name.toLowerCase()}.exe` && c.isFile(),
)[0].name,
);
if (newOptions.appVersion === undefined) {
// https://github.com/electron/electron-packager/blob/f1c159f4c844d807968078ea504fba40ca7d9c73/src/win32.js#L46-L48
newOptions.appVersion = getVersionString(
executablePath,
'ProductVersion',
);
log.debug(
`Extracted app version from executable: ${
newOptions.appVersion as string
}`,
);
}
if (newOptions.buildVersion === undefined) {
//https://github.com/electron/electron-packager/blob/f1c159f4c844d807968078ea504fba40ca7d9c73/src/win32.js#L50-L52
newOptions.buildVersion = getVersionString(executablePath, 'FileVersion');
if (newOptions.appVersion == newOptions.buildVersion) {
newOptions.buildVersion = undefined;
} else {
log.debug(
`Extracted build version from executable: ${
newOptions.buildVersion as string
}`,
);
}
}
if (newOptions.appCopyright === undefined) {
// https://github.com/electron/electron-packager/blob/f1c159f4c844d807968078ea504fba40ca7d9c73/src/win32.js#L54-L56
newOptions.appCopyright = getVersionString(
executablePath,
'LegalCopyright',
);
log.debug(
`Extracted app copyright from executable: ${
newOptions.appCopyright as string
}`,
);
}
} else if (looksLikeLinux) {
log.debug('This looks like a Linux app...');
if (newOptions.platform === undefined) {
newOptions.platform = 'linux';
}
executablePath = path.join(
appRoot,
children.filter((c) => c.name == name && c.isFile())[0].name,
);
}
if (!executablePath || !newOptions.platform) {
throw Error(
`Could not find executablePath or platform of app in ${appRoot}`,
);
}
log.debug(`Executable path: ${executablePath}`);
if (newOptions.arch === undefined) {
const executableInfo = getExecutableInfo(
executablePath,
newOptions.platform,
);
if (!executableInfo) {
throw new Error(
`Could not get executable info for executable path: ${executablePath}`,
);
}
newOptions.arch = executableInfo.arch;
log.debug(`Extracted arch from executable: ${newOptions.arch as string}`);
}
if (newOptions.platform === undefined || newOptions.arch == undefined) {
throw Error(`Could not determine platform / arch of app in ${appRoot}`);
}
return newOptions;
}

View File

@ -0,0 +1,39 @@
export function extractBoolean(
infoPlistXML: string,
plistKey: string,
): boolean | undefined {
const plistValue = extractRaw(infoPlistXML, plistKey);
return plistValue === undefined
? undefined
: plistValue.split('<')[1].split('/>')[0].toLowerCase() === 'true';
}
export function extractString(
infoPlistXML: string,
plistKey: string,
): string | undefined {
const plistValue = extractRaw(infoPlistXML, plistKey);
return plistValue === undefined
? undefined
: plistValue.split('<string>')[1].split('</string>')[0];
}
function extractRaw(
infoPlistXML: string,
plistKey: string,
): string | undefined {
// This would be easier with xml2js, but let's not add a dependency for something this minor.
const fullKey = `\n <key>${plistKey}</key>`;
if (infoPlistXML.indexOf(fullKey) === -1) {
// This value wasn't set, so we'll stay agnostic to it
return undefined;
}
return infoPlistXML
.split(fullKey)[1]
.split('\n </dict>')[0] // Get everything between here and the end of the main plist dict
.split('\n <key>')[0]; // Get everything before the next key (if it exists)
}

View File

@ -0,0 +1,42 @@
import * as os from 'os';
import * as path from 'path';
import { spawnSync } from 'child_process';
// A modification of https://github.com/electron/node-rcedit to support the retrieval
// of information.
export function getVersionString(
executablePath: string,
versionString: string,
): string | undefined {
let rcedit = path.resolve(
__dirname,
'..',
'..',
'..',
'node_modules',
'rcedit',
'bin',
process.arch === 'x64' ? 'rcedit-x64.exe' : 'rcedit.exe',
);
const args = [executablePath, `--get-version-string`, versionString];
const spawnOptions = {
env: { ...process.env },
};
// Use Wine on non-Windows platforms except for WSL, which doesn't need it
if (process.platform !== 'win32' && !os.release().endsWith('Microsoft')) {
args.unshift(rcedit);
rcedit = process.arch === 'x64' ? 'wine64' : 'wine';
// Suppress "fixme:" stderr log messages
spawnOptions.env.WINEDEBUG = '-all';
}
try {
const child = spawnSync(rcedit, args, spawnOptions);
const result = child.output?.toString().split(',wine: ')[0];
return result.startsWith(',') ? result.substr(1) : result;
} catch {
return undefined;
}
}

View File

@ -0,0 +1,234 @@
import * as fs from 'fs';
import * as path from 'path';
import * as log from 'loglevel';
import {
NativefierOptions,
RawOptions,
} from '../../../shared/src/options/model';
import { dirExists, fileExists } from '../fsHelpers';
import { extractBoolean, extractString } from './plistInfoXMLHelpers';
import { getOptionsFromExecutable } from './executableHelpers';
import { parseJson } from '../../utils/parseUtils';
export type UpgradeAppInfo = {
appResourcesDir: string;
appRoot: string;
options: NativefierOptions;
};
function findUpgradeAppResourcesDir(searchDir: string): string | null {
searchDir = dirExists(searchDir) ? searchDir : path.dirname(searchDir);
log.debug(`Searching for nativfier.json in ${searchDir}`);
const children = fs.readdirSync(searchDir, { withFileTypes: true });
if (fileExists(path.join(searchDir, 'nativefier.json'))) {
// Found 'nativefier.json', so this must be it!
return path.resolve(searchDir);
}
const childDirectories = children.filter((c) => c.isDirectory());
for (const childDir of childDirectories) {
// We must go deeper!
const result = findUpgradeAppResourcesDir(
path.join(searchDir, childDir.name, 'nativefier.json'),
);
if (result !== null) {
return path.resolve(result);
}
}
// Didn't find it down here
return null;
}
function getAppRoot(
appResourcesDir: string,
options: NativefierOptions,
): string {
switch (options.platform) {
case 'darwin':
return path.resolve(path.join(appResourcesDir, '..', '..', '..', '..'));
case 'linux':
case 'win32':
return path.resolve(path.join(appResourcesDir, '..', '..'));
default:
throw new Error(
`Could not find the app root for platform: ${
options.platform ?? 'undefined'
}`,
);
}
}
function getIconPath(appResourcesDir: string): string | undefined {
const icnsPath = path.join(appResourcesDir, '..', 'electron.icns');
if (fileExists(icnsPath)) {
log.debug(`Found icon at: ${icnsPath}`);
return path.resolve(icnsPath);
}
const icoPath = path.join(appResourcesDir, 'icon.ico');
if (fileExists(icoPath)) {
log.debug(`Found icon at: ${icoPath}`);
return path.resolve(icoPath);
}
const pngPath = path.join(appResourcesDir, 'icon.png');
if (fileExists(pngPath)) {
log.debug(`Found icon at: ${pngPath}`);
return path.resolve(pngPath);
}
log.debug('Could not find icon file.');
return undefined;
}
function getInfoPListOptions(
appResourcesDir: string,
priorOptions: NativefierOptions,
): NativefierOptions {
if (!fileExists(path.join(appResourcesDir, '..', '..', 'Info.plist'))) {
// Not a darwin/mas app, so this is irrelevant
return priorOptions;
}
const newOptions = { ...priorOptions };
const infoPlistXML: string = fs
.readFileSync(path.join(appResourcesDir, '..', '..', 'Info.plist'))
.toString();
if (newOptions.appCopyright === undefined) {
// https://github.com/electron/electron-packager/blob/0d3f84374e9ab3741b171610735ebc6be3e5e75f/src/mac.js#L230-L232
newOptions.appCopyright = extractString(
infoPlistXML,
'NSHumanReadableCopyright',
);
log.debug(
`Extracted app copyright from Info.plist: ${
newOptions.appCopyright as string
}`,
);
}
if (newOptions.appVersion === undefined) {
// https://github.com/electron/electron-packager/blob/0d3f84374e9ab3741b171610735ebc6be3e5e75f/src/mac.js#L214-L216
// This could also be the buildVersion, but since they end up in the same place, that SHOULDN'T matter
const bundleVersion = extractString(infoPlistXML, 'CFBundleVersion');
newOptions.appVersion =
bundleVersion === undefined || bundleVersion === '1.0.0' // If it's 1.0.0, that's just the default
? undefined
: bundleVersion;
(newOptions.darwinDarkModeSupport =
newOptions.darwinDarkModeSupport === undefined
? undefined
: newOptions.darwinDarkModeSupport === false),
log.debug(
`Extracted app version from Info.plist: ${
newOptions.appVersion as string
}`,
);
}
if (newOptions.darwinDarkModeSupport === undefined) {
// https://github.com/electron/electron-packager/blob/0d3f84374e9ab3741b171610735ebc6be3e5e75f/src/mac.js#L234-L236
newOptions.darwinDarkModeSupport = extractBoolean(
infoPlistXML,
'NSRequiresAquaSystemAppearance',
);
log.debug(
`Extracted Darwin dark mode support from Info.plist: ${
newOptions.darwinDarkModeSupport ? 'Yes' : 'No'
}`,
);
}
return newOptions;
}
function getInjectPaths(appResourcesDir: string): string[] | undefined {
const injectDir = path.join(appResourcesDir, 'inject');
if (!dirExists(injectDir)) {
return undefined;
}
const injectPaths = fs
.readdirSync(injectDir, { withFileTypes: true })
.filter(
(fd) =>
fd.isFile() &&
(fd.name.toLowerCase().endsWith('.css') ||
fd.name.toLowerCase().endsWith('.js')),
)
.map((fd) => path.resolve(path.join(injectDir, fd.name)));
log.debug(`CSS/JS Inject paths: ${injectPaths.join(', ')}`);
return injectPaths;
}
function isAsar(appResourcesDir: string): boolean {
const asar = fileExists(path.join(appResourcesDir, '..', 'electron.asar'));
log.debug(`Is this app an ASAR? ${asar ? 'Yes' : 'No'}`);
return asar;
}
export function findUpgradeApp(upgradeFrom: string): UpgradeAppInfo | null {
const searchDir = dirExists(upgradeFrom)
? upgradeFrom
: path.dirname(upgradeFrom);
log.debug(`Looking for old options file in ${searchDir}`);
const appResourcesDir = findUpgradeAppResourcesDir(searchDir);
if (appResourcesDir === null) {
log.debug(`No nativefier.json file found in ${searchDir}`);
return null;
}
const nativefierJSONPath = path.join(appResourcesDir, 'nativefier.json');
log.debug(`Loading ${nativefierJSONPath}`);
let options = parseJson<NativefierOptions>(
fs.readFileSync(nativefierJSONPath, 'utf8'),
);
if (!options) {
throw new Error(
`Could not read Nativefier options from ${nativefierJSONPath}`,
);
}
options.electronVersion = undefined;
options = {
...options,
...getOptionsFromExecutable(appResourcesDir, options),
};
const appRoot = getAppRoot(appResourcesDir, options);
return {
appResourcesDir,
appRoot,
options: {
...options,
...getInfoPListOptions(appResourcesDir, options),
asar: options.asar !== undefined ? options.asar : isAsar(appResourcesDir),
icon: getIconPath(appResourcesDir),
inject: getInjectPaths(appResourcesDir),
},
};
}
export function useOldAppOptions(
rawOptions: RawOptions,
oldApp: UpgradeAppInfo,
): RawOptions {
if (rawOptions.targetUrl !== undefined && dirExists(rawOptions.targetUrl)) {
// You got your ouput dir in my targetUrl!
rawOptions.out = rawOptions.targetUrl;
}
log.debug('oldApp', oldApp);
const combinedOptions = { ...rawOptions, ...oldApp.options };
log.debug('Combined options', combinedOptions);
return combinedOptions;
}

View File

@ -0,0 +1,58 @@
import axios from 'axios';
import * as log from 'loglevel';
import {
DEFAULT_CHROME_VERSION,
DEFAULT_ELECTRON_VERSION,
} from '../../constants';
type ElectronRelease = {
version: string;
date: string;
node: string;
v8: string;
uv: string;
zlib: string;
openssl: string;
modules: string;
chrome: string;
files: string[];
};
const ELECTRON_VERSIONS_URL = 'https://releases.electronjs.org/releases.json';
export async function getChromeVersionForElectronVersion(
electronVersion: string,
url = ELECTRON_VERSIONS_URL,
): Promise<string> {
if (!electronVersion || electronVersion === DEFAULT_ELECTRON_VERSION) {
// Exit quickly for the scenario that we already know about
return DEFAULT_CHROME_VERSION;
}
try {
log.debug('Grabbing electron<->chrome versions file from', url);
const response = await axios.get<ElectronRelease[]>(url, { timeout: 5000 });
if (response.status !== 200) {
throw new Error(`Bad request: Status code ${response.status}`);
}
const electronReleases: ElectronRelease[] = response.data;
const electronVersionToChromeVersion: { [key: string]: string } = {};
for (const release of electronReleases) {
electronVersionToChromeVersion[release.version] = release.chrome;
}
if (!(electronVersion in electronVersionToChromeVersion)) {
throw new Error(
`Electron version '${electronVersion}' not found in retrieved version list!`,
);
}
const chromeVersion = electronVersionToChromeVersion[electronVersion];
log.debug(
`Associated electron v${electronVersion} to chrome v${chromeVersion}`,
);
return chromeVersion;
} catch (err: unknown) {
log.error('getChromeVersionForElectronVersion ERROR', err);
log.debug('Falling back to default Chrome version', DEFAULT_CHROME_VERSION);
return DEFAULT_CHROME_VERSION;
}
}

View File

@ -0,0 +1,49 @@
import axios from 'axios';
import * as log from 'loglevel';
import { DEFAULT_FIREFOX_VERSION } from '../../constants';
type FirefoxVersions = {
FIREFOX_AURORA: string;
FIREFOX_DEVEDITION: string;
FIREFOX_ESR: string;
FIREFOX_ESR_NEXT: string;
FIREFOX_NIGHTLY: string;
LAST_MERGE_DATE: string;
LAST_RELEASE_DATE: string;
LAST_SOFTFREEZE_DATE: string;
LATEST_FIREFOX_DEVEL_VERSION: string;
LATEST_FIREFOX_OLDER_VERSION: string;
LATEST_FIREFOX_RELEASED_DEVEL_VERSION: string;
LATEST_FIREFOX_VERSION: string;
NEXT_MERGE_DATE: string;
NEXT_RELEASE_DATE: string;
NEXT_SOFTFREEZE_DATE: string;
};
const FIREFOX_VERSIONS_URL =
'https://product-details.mozilla.org/1.0/firefox_versions.json';
export async function getLatestFirefoxVersion(
url = FIREFOX_VERSIONS_URL,
): Promise<string> {
try {
log.debug('Grabbing Firefox version data from', url);
const response = await axios.get<FirefoxVersions>(url, { timeout: 5000 });
if (response.status !== 200) {
throw new Error(`Bad request: Status code ${response.status}`);
}
const firefoxVersions: FirefoxVersions = response.data;
log.debug(
`Got latest Firefox version ${firefoxVersions.LATEST_FIREFOX_VERSION}`,
);
return firefoxVersions.LATEST_FIREFOX_VERSION;
} catch (err: unknown) {
log.error('getLatestFirefoxVersion ERROR', err);
log.debug(
'Falling back to default Firefox version',
DEFAULT_FIREFOX_VERSION,
);
return DEFAULT_FIREFOX_VERSION;
}
}

View File

@ -0,0 +1,77 @@
import axios from 'axios';
import * as log from 'loglevel';
import { DEFAULT_SAFARI_VERSION } from '../../constants';
export type SafariVersion = {
majorVersion: number;
version: string;
webkitVersion: string;
};
const SAFARI_VERSIONS_HISTORY_URL =
'https://en.wikipedia.org/wiki/Safari_version_history';
export async function getLatestSafariVersion(
url = SAFARI_VERSIONS_HISTORY_URL,
): Promise<SafariVersion> {
try {
log.debug('Grabbing apple version data from', url);
const response = await axios.get<string>(url, { timeout: 5000 });
if (response.status !== 200) {
throw new Error(`Bad request: Status code ${response.status}`);
}
// This would be easier with an HTML parser, but we're not going to include an extra dependency for something that dumb
const rawData: string = response.data;
const majorVersions = [
...rawData.matchAll(
/class="mw-headline" id="Safari_[0-9]*">Safari ([0-9]*)</g,
),
].map((match) => match[1]);
const majorVersion = parseInt(majorVersions[majorVersions.length - 1]);
const majorVersionTable = rawData
.split('>Release history<')[2]
.split('<table')
.filter((table) => table.includes(`Safari ${majorVersion}.x`))[0];
const versionRows = majorVersionTable.split('<tbody')[1].split('<tr');
let version: string | undefined = undefined;
let webkitVersion: string | undefined = undefined;
for (const versionRow of versionRows.reverse()) {
const versionMatch = [
...versionRow.matchAll(/>\s*(([0-9]*\.){2}[0-9])\s*</g),
];
if (versionMatch.length > 0 && !version) {
version = versionMatch[0][1];
}
const webkitVersionMatch = [
...versionRow.matchAll(/>\s*(([0-9]*\.){3,4}[0-9])\s*</g),
];
if (webkitVersionMatch.length > 0 && !webkitVersion) {
webkitVersion = webkitVersionMatch[0][1];
}
if (version && webkitVersion) {
break;
}
}
if (version && webkitVersion) {
return {
majorVersion,
version,
webkitVersion,
};
}
return DEFAULT_SAFARI_VERSION;
} catch (err: unknown) {
log.error('getLatestSafariVersion ERROR', err);
log.debug('Falling back to default Safari version', DEFAULT_SAFARI_VERSION);
return DEFAULT_SAFARI_VERSION;
}
}

126
src/infer/inferIcon.ts Normal file
View File

@ -0,0 +1,126 @@
import * as path from 'path';
import { writeFile } from 'fs';
import { promisify } from 'util';
import gitCloud = require('gitcloud');
import pageIcon from 'page-icon';
import {
downloadFile,
DownloadResult,
getAllowedIconFormats,
getTempDir,
} from '../helpers/helpers';
import * as log from 'loglevel';
const writeFileAsync = promisify(writeFile);
const GITCLOUD_SPACE_DELIMITER = '-';
const GITCLOUD_URL = 'https://nativefier.github.io/nativefier-icons/';
type GitCloudIcon = {
ext?: string;
name?: string;
score?: number;
url?: string;
};
function getMaxMatchScore(iconWithScores: GitCloudIcon[]): number {
const score = iconWithScores.reduce((maxScore, currentIcon) => {
const currentScore = currentIcon.score;
if (currentScore && currentScore > maxScore) {
return currentScore;
}
return maxScore;
}, 0);
log.debug('Max icon match score:', score);
return score;
}
function getMatchingIcons(
iconsWithScores: GitCloudIcon[],
maxScore: number,
): GitCloudIcon[] {
return iconsWithScores.filter((item) => item.score === maxScore);
}
function mapIconWithMatchScore(
cloudIcons: { name: string; url: string }[],
targetUrl: string,
): GitCloudIcon[] {
const normalisedTargetUrl = targetUrl.toLowerCase();
return cloudIcons.map((item) => {
const itemWords = item.name.split(GITCLOUD_SPACE_DELIMITER);
const score: number = itemWords.reduce(
(currentScore: number, word: string) => {
if (normalisedTargetUrl.includes(word)) {
return currentScore + 1;
}
return currentScore;
},
0,
);
return { ...item, ext: path.extname(item.url), score };
});
}
async function inferIconFromStore(
targetUrl: string,
platform: string,
): Promise<DownloadResult | undefined> {
log.debug(`Inferring icon from store for ${targetUrl} on ${platform}`);
const allowedFormats = new Set<string | undefined>(
getAllowedIconFormats(platform),
);
const cloudIcons = await gitCloud(GITCLOUD_URL);
log.debug(`Got ${cloudIcons.length} icons from gitcloud`);
const iconWithScores = mapIconWithMatchScore(cloudIcons, targetUrl);
const maxScore = getMaxMatchScore(iconWithScores);
if (maxScore === 0) {
log.debug('No relevant icon in store.');
return undefined;
}
const iconsMatchingScore = getMatchingIcons(iconWithScores, maxScore);
const iconsMatchingExt = iconsMatchingScore.filter((icon) =>
allowedFormats.has(icon.ext ?? path.extname(icon.url as string)),
);
const matchingIcon = iconsMatchingExt[0];
const iconUrl = matchingIcon && matchingIcon.url;
if (!iconUrl) {
log.debug('Could not infer icon from store');
return undefined;
}
return downloadFile(iconUrl);
}
export async function inferIcon(
targetUrl: string,
platform: string,
): Promise<string | undefined> {
log.debug(`Inferring icon for ${targetUrl} on ${platform}`);
const tmpDirPath = getTempDir('iconinfer');
let icon: { ext: string; data: Buffer } | undefined =
await inferIconFromStore(targetUrl, platform);
if (!icon) {
const ext = platform === 'win32' ? '.ico' : '.png';
log.debug(`Trying to extract a ${ext} icon from the page.`);
icon = await pageIcon(targetUrl, { ext });
}
if (!icon) {
return undefined;
}
log.debug(`Got an icon from the page.`);
const iconPath = path.join(tmpDirPath, `/icon${icon.ext}`);
log.debug(
`Writing ${(icon.data.length / 1024).toFixed(1)} kb icon to ${iconPath}`,
);
await writeFileAsync(iconPath, icon.data);
return iconPath;
}

35
src/infer/inferOs.ts Normal file
View File

@ -0,0 +1,35 @@
import * as os from 'os';
import * as log from 'loglevel';
// Ideally we'd get this list directly from electron-packager, but it's not
// possible to convert a literal type to an array of strings in current TypeScript
export const supportedArchs = ['x64', 'armv7l', 'arm64', 'universal'];
export const supportedPlatforms = [
'darwin',
'linux',
'mac',
'mas',
'osx',
'win32',
'windows',
];
export function inferPlatform(): string {
const platform = os.platform();
if (['darwin', 'linux', 'win32'].includes(platform)) {
log.debug('Inferred platform', platform);
return platform;
}
throw new Error(`Untested platform ${platform} detected`);
}
export function inferArch(): string {
const arch = os.arch();
if (!supportedArchs.includes(arch)) {
throw new Error(`Incompatible architecture ${arch} detected`);
}
log.debug('Inferred arch', arch);
return arch;
}

View File

@ -0,0 +1,25 @@
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { inferTitle } from './inferTitle';
test('it returns the correct title', async () => {
const axiosGetMock = jest.spyOn(axios, 'get');
const mockedResponse: AxiosResponse<string> = {
data: `
<HTML>
<head>
<title>TEST_TITLE</title>
</head>
</HTML>`,
status: 200,
statusText: 'OK',
headers: {},
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
config: {} as unknown as InternalAxiosRequestConfig<unknown>,
};
axiosGetMock.mockResolvedValue(mockedResponse);
const result = await inferTitle('someurl');
expect(axiosGetMock).toHaveBeenCalledTimes(1);
expect(result).toBe('TEST_TITLE');
});

20
src/infer/inferTitle.ts Normal file
View File

@ -0,0 +1,20 @@
import axios from 'axios';
import * as log from 'loglevel';
const USER_AGENT =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15';
export async function inferTitle(url: string): Promise<string> {
const { data } = await axios.get<string>(url, {
headers: {
// Fake user agent for pages like http://messenger.com
'User-Agent': USER_AGENT,
},
});
log.debug(`Fetched ${(data.length / 1024).toFixed(1)} kb page at`, url);
const inferredTitle =
/<\s*title.*?>(?<title>.+?)<\s*\/title\s*?>/i.exec(data)?.groups?.title ??
'Webapp';
log.debug('Inferred title:', inferredTitle);
return inferredTitle;
}

220
src/integration-test.ts Normal file
View File

@ -0,0 +1,220 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { DEFAULT_ELECTRON_VERSION } from './constants';
import { getTempDir } from './helpers/helpers';
import { getChromeVersionForElectronVersion } from './infer/browsers/inferChromeVersion';
import { getLatestFirefoxVersion } from './infer/browsers/inferFirefoxVersion';
import { getLatestSafariVersion } from './infer/browsers/inferSafariVersion';
import { inferArch } from './infer/inferOs';
import { buildNativefierApp } from './main';
import { userAgent } from './options/fields/userAgent';
import {
GlobalShortcut,
NativefierOptions,
RawOptions,
} from '../shared/src/options/model';
import { parseJson } from './utils/parseUtils';
async function checkApp(
appRoot: string,
inputOptions: RawOptions,
): Promise<void> {
const arch = inputOptions.arch ? inputOptions.arch : inferArch();
if (inputOptions.out !== undefined) {
expect(
path.join(
inputOptions.out,
`npm-${inputOptions.platform as string}-${arch}`,
),
).toBe(appRoot);
}
let relativeResourcesDir = 'resources';
if (inputOptions.platform === 'darwin') {
relativeResourcesDir = path.join('npm.app', 'Contents', 'Resources');
}
const appPath = path.join(appRoot, relativeResourcesDir, 'app');
const configPath = path.join(appPath, 'nativefier.json');
const nativefierConfig: NativefierOptions | undefined =
parseJson<NativefierOptions>(fs.readFileSync(configPath).toString());
expect(nativefierConfig).not.toBeUndefined();
expect(inputOptions.targetUrl).toBe(nativefierConfig?.targetUrl);
// Test name inferring
expect(nativefierConfig?.name).toBe('npm');
// Test icon writing
const iconFile =
inputOptions.platform === 'darwin'
? path.join('..', 'electron.icns')
: inputOptions.platform === 'linux'
? 'icon.png'
: 'icon.ico';
const iconPath = path.join(appPath, iconFile);
expect(fs.existsSync(iconPath)).toEqual(true);
expect(fs.statSync(iconPath).size).toBeGreaterThan(1000);
// Test arch
if (inputOptions.arch !== undefined) {
expect(inputOptions.arch).toEqual(nativefierConfig?.arch);
} else {
expect(os.arch()).toEqual(nativefierConfig?.arch);
}
// Test electron version
expect(nativefierConfig?.electronVersionUsed).toBe(
inputOptions.electronVersion || DEFAULT_ELECTRON_VERSION,
);
// Test user agent
if (inputOptions.userAgent) {
const translatedUserAgent = await userAgent({
packager: {
platform: inputOptions.platform,
electronVersion:
inputOptions.electronVersion || DEFAULT_ELECTRON_VERSION,
},
nativefier: { userAgent: inputOptions.userAgent },
});
inputOptions.userAgent = translatedUserAgent || inputOptions.userAgent;
}
expect(nativefierConfig?.userAgent).toEqual(inputOptions.userAgent);
// Test lang
expect(nativefierConfig?.lang).toEqual(inputOptions.lang);
// Test global shortcuts
if (inputOptions.globalShortcuts) {
let shortcutData: GlobalShortcut[] | undefined = [];
if (typeof inputOptions.globalShortcuts === 'string') {
shortcutData = parseJson<GlobalShortcut[]>(
fs.readFileSync(inputOptions.globalShortcuts, 'utf8'),
);
} else {
shortcutData = inputOptions.globalShortcuts;
}
expect(nativefierConfig?.globalShortcuts).toStrictEqual(shortcutData);
}
}
describe('Nativefier', () => {
jest.setTimeout(300000);
test.each(['darwin', 'linux'])(
'builds a Nativefier app for platform %s',
async (platform) => {
const tempDirectory = getTempDir('integtest');
const options: RawOptions = {
lang: 'en-US',
out: tempDirectory,
overwrite: true,
platform,
targetUrl: 'https://npmjs.com/',
};
const appPath = await buildNativefierApp(options);
expect(appPath).not.toBeUndefined();
await checkApp(appPath, options);
},
);
});
function generateShortcutsFile(dir: string): string {
const shortcuts = [
{
key: 'MediaPlayPause',
inputEvents: [
{
type: 'keyDown',
keyCode: 'Space',
},
],
},
{
key: 'MediaNextTrack',
inputEvents: [
{
type: 'keyDown',
keyCode: 'Right',
},
],
},
];
const filename = path.join(dir, 'shortcuts.json');
fs.writeFileSync(filename, JSON.stringify(shortcuts));
return filename;
}
describe('Nativefier upgrade', () => {
jest.setTimeout(300000);
test.each([
{ platform: 'darwin', arch: 'x64' },
{ platform: 'linux', arch: 'arm64', userAgent: 'FIREFOX 60' },
// Exhaustive integration testing here would be neat, but takes too long.
// -> For now, only testing a subset of platforms/archs
// { platform: 'win32', arch: 'x64' },
// { platform: 'darwin', arch: 'arm64' },
// { platform: 'linux', arch: 'x64' },
// { platform: 'linux', arch: 'armv7l' },
])(
'can upgrade a Nativefier app for platform/arch: %s',
async (baseAppOptions) => {
const tempDirectory = getTempDir('integtestUpgrade1');
const shortcuts = generateShortcutsFile(tempDirectory);
const options: RawOptions = {
electronVersion: '11.2.3',
globalShortcuts: shortcuts,
out: tempDirectory,
overwrite: true,
targetUrl: 'https://npmjs.com/',
...baseAppOptions,
};
const appPath = await buildNativefierApp(options);
expect(appPath).not.toBeUndefined();
await checkApp(appPath, options);
const upgradeOptions: RawOptions = {
upgrade: appPath,
overwrite: true,
};
const upgradeAppPath = await buildNativefierApp(upgradeOptions);
options.electronVersion = DEFAULT_ELECTRON_VERSION;
options.userAgent = baseAppOptions.userAgent;
expect(upgradeAppPath).not.toBeUndefined();
await checkApp(upgradeAppPath, options);
},
);
});
describe('Browser version retrieval', () => {
test('get chrome version with electron version', async () => {
await expect(getChromeVersionForElectronVersion('12.0.0')).resolves.toBe(
'89.0.4389.69',
);
});
test('get latest firefox version', async () => {
const firefoxVersion = await getLatestFirefoxVersion();
const majorVersion = parseInt(firefoxVersion.split('.')[0]);
expect(majorVersion).toBeGreaterThanOrEqual(88);
});
test('get latest safari version', async () => {
const safariVersion = await getLatestSafariVersion();
expect(safariVersion.majorVersion).toBeGreaterThanOrEqual(14);
});
});

9
src/jestSetupFiles.ts Normal file
View File

@ -0,0 +1,9 @@
import * as log from 'loglevel';
if (process.env.LOGLEVEL) {
log.setLevel(process.env.LOGLEVEL as log.LogLevelDesc);
} else {
log.disableAll();
}
process.traceDeprecation = true;

21
src/main.ts Normal file
View File

@ -0,0 +1,21 @@
import 'source-map-support/register';
import { buildNativefierApp } from './build/buildNativefierApp';
import { RawOptions } from '../shared/src/options/model';
export { buildNativefierApp };
/**
* Only for compatibility with Nativefier <= 7.7.1 !
* Use the better, modern async `buildNativefierApp` instead if you can!
*/
function buildNativefierAppOldCallbackStyle(
options: RawOptions, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types
callback: (err?: Error, result?: string) => void,
): void {
buildNativefierApp(options)
.then((result) => callback(undefined, result))
.catch((err: Error) => callback(err));
}
export default buildNativefierAppOldCallbackStyle;

View File

@ -0,0 +1,12 @@
import * as log from 'loglevel';
import { processOptions } from './fields/fields';
import { AppOptions } from '../../shared/src/options/model';
/**
* Takes the options object and infers new values needing async work
*/
export async function asyncConfig(options: AppOptions): Promise<AppOptions> {
log.debug('\nPerforming async options post-processing.');
return await processOptions(options);
}

View File

@ -0,0 +1,105 @@
import { AppOptions } from '../../../shared/src/options/model';
import { processOptions } from './fields';
describe('fields', () => {
let options: AppOptions;
beforeEach(() => {
options = {
nativefier: {
accessibilityPrompt: false,
alwaysOnTop: false,
backgroundColor: undefined,
basicAuthPassword: undefined,
basicAuthUsername: undefined,
blockExternalUrls: false,
bookmarksMenu: undefined,
bounce: false,
browserwindowOptions: undefined,
clearCache: false,
counter: false,
crashReporter: undefined,
disableContextMenu: false,
disableDevTools: false,
disableGpu: false,
disableOldBuildWarning: false,
diskCacheSize: undefined,
enableEs3Apis: false,
fastQuit: true,
fileDownloadOptions: undefined,
flashPluginDir: undefined,
fullScreen: false,
globalShortcuts: undefined,
height: undefined,
hideWindowFrame: false,
ignoreCertificate: false,
ignoreGpuBlacklist: false,
inject: [],
insecure: false,
internalUrls: undefined,
maximize: false,
maxHeight: undefined,
minWidth: undefined,
minHeight: undefined,
maxWidth: undefined,
nativefierVersion: '1.0.0',
processEnvs: undefined,
proxyRules: undefined,
showMenuBar: false,
singleInstance: false,
strictInternalUrls: false,
titleBarStyle: undefined,
tray: 'false',
userAgent: undefined,
userAgentHonest: false,
verbose: false,
versionString: '1.0.0',
width: undefined,
widevine: false,
x: undefined,
y: undefined,
zoom: 1,
},
packager: {
arch: process.arch,
dir: '',
platform: process.platform,
portable: false,
targetUrl: '',
upgrade: false,
},
};
});
test('fully-defined async options are returned as-is', async () => {
options.packager.icon = '/my/icon.png';
options.packager.name = 'my beautiful app ';
options.packager.platform = 'darwin';
options.nativefier.userAgent = 'random user agent';
await processOptions(options);
expect(options.packager.icon).toEqual('/my/icon.png');
expect(options.packager.name).toEqual('my beautiful app');
expect(options.nativefier.userAgent).toEqual('random user agent');
});
test('name has spaces stripped in linux', async () => {
options.packager.name = 'my beautiful app ';
options.packager.platform = 'linux';
await processOptions(options);
expect(options.packager.name).toEqual('mybeautifulapp');
});
test('user agent is ignored if not provided', async () => {
await processOptions(options);
expect(options.nativefier.userAgent).toBeUndefined();
});
test('user agent short code is populated', async () => {
options.nativefier.userAgent = 'edge';
await processOptions(options);
expect(options.nativefier.userAgent).not.toBe('edge');
});
});

View File

@ -0,0 +1,42 @@
import { icon } from './icon';
import { userAgent } from './userAgent';
import { AppOptions } from '../../../shared/src/options/model';
import { name } from './name';
type OptionPostprocessor = {
namespace: 'nativefier' | 'packager';
option: 'icon' | 'name' | 'userAgent';
processor: (options: AppOptions) => Promise<string | undefined>;
};
const OPTION_POSTPROCESSORS: OptionPostprocessor[] = [
{ namespace: 'nativefier', option: 'userAgent', processor: userAgent },
{ namespace: 'packager', option: 'icon', processor: icon },
{ namespace: 'packager', option: 'name', processor: name },
];
export async function processOptions(options: AppOptions): Promise<AppOptions> {
const processedOptions = await Promise.all(
OPTION_POSTPROCESSORS.map(async ({ namespace, option, processor }) => {
const result = await processor(options);
return {
namespace,
option,
result,
};
}),
);
for (const { namespace, option, result } of processedOptions) {
if (
result &&
namespace in options &&
options[namespace] &&
option in options[namespace]
) {
// @ts-expect-error We're fiddling with objects at the string key level, which TS doesn't support well.
options[namespace][option] = result;
}
}
return options;
}

View File

@ -0,0 +1,60 @@
import * as log from 'loglevel';
import { icon } from './icon';
import { inferIcon } from '../../infer/inferIcon';
jest.mock('./../../infer/inferIcon');
jest.mock('loglevel');
const mockedResult = 'icon path';
const ICON_PARAMS_PROVIDED = {
packager: {
icon: './icon.png',
targetUrl: 'https://google.com',
platform: 'mac',
},
};
const ICON_PARAMS_NEEDS_INFER = {
packager: {
targetUrl: 'https://google.com',
platform: 'mac',
},
};
describe('when the icon parameter is passed', () => {
test('it should return the icon parameter', async () => {
expect(inferIcon).toHaveBeenCalledTimes(0);
await expect(icon(ICON_PARAMS_PROVIDED)).resolves.toBeUndefined();
});
});
describe('when the icon parameter is not passed', () => {
test('it should call inferIcon', async () => {
(inferIcon as jest.Mock).mockImplementationOnce(() =>
Promise.resolve(mockedResult),
);
const result = await icon(ICON_PARAMS_NEEDS_INFER);
expect(result).toBe(mockedResult);
expect(inferIcon).toHaveBeenCalledWith(
ICON_PARAMS_NEEDS_INFER.packager.targetUrl,
ICON_PARAMS_NEEDS_INFER.packager.platform,
);
});
describe('when inferIcon resolves with an error', () => {
test('it should handle the error', async () => {
(inferIcon as jest.Mock).mockImplementationOnce(() =>
Promise.reject(new Error('some error')),
);
const result = await icon(ICON_PARAMS_NEEDS_INFER);
expect(result).toBeUndefined();
expect(inferIcon).toHaveBeenCalledWith(
ICON_PARAMS_NEEDS_INFER.packager.targetUrl,
ICON_PARAMS_NEEDS_INFER.packager.platform,
);
expect(log.warn).toHaveBeenCalledTimes(1); // eslint-disable-line @typescript-eslint/unbound-method
});
});
});

View File

@ -0,0 +1,38 @@
import * as log from 'loglevel';
import { inferIcon } from '../../infer/inferIcon';
type IconParams = {
packager: {
icon?: string;
targetUrl: string;
platform?: string;
};
};
export async function icon(options: IconParams): Promise<string | undefined> {
if (options.packager.icon) {
log.debug('Got icon from options. Using it, no inferring needed');
return undefined;
}
if (!options.packager.platform) {
log.error('No platform specified. Icon can not be inferrerd.');
return undefined;
}
try {
return await inferIcon(
options.packager.targetUrl,
options.packager.platform,
);
} catch (err: unknown) {
// eslint-disable-next-line
const errorUrl: string = (err as any)?.config?.url;
log.warn(
'Cannot automatically retrieve the app icon:',
errorUrl ? `${(err as Error).message} on ${errorUrl}` : err,
);
return undefined;
}
}

View File

@ -0,0 +1,110 @@
import * as log from 'loglevel';
import { name } from './name';
import { DEFAULT_APP_NAME } from '../../constants';
import { inferTitle } from '../../infer/inferTitle';
import { sanitizeFilename } from '../../utils/sanitizeFilename';
jest.mock('./../../infer/inferTitle');
jest.mock('./../../utils/sanitizeFilename');
jest.mock('loglevel');
const inferTitleMockedResult = 'mock name';
const NAME_PARAMS_PROVIDED = {
packager: {
name: 'appname',
targetUrl: 'https://google.com',
platform: 'linux',
},
};
const NAME_PARAMS_NEEDS_INFER = {
packager: {
targetUrl: 'https://google.com',
platform: 'mac',
},
};
beforeAll(() => {
(sanitizeFilename as jest.Mock).mockImplementation(
(_, filename: string) => filename,
);
});
describe('well formed name parameters', () => {
test('it should not call inferTitle', async () => {
const result = await name(NAME_PARAMS_PROVIDED);
expect(inferTitle).toHaveBeenCalledTimes(0);
expect(result).toBe(NAME_PARAMS_PROVIDED.packager.name);
});
test('it should call sanitize filename', async () => {
const result = await name(NAME_PARAMS_PROVIDED);
expect(sanitizeFilename).toHaveBeenCalledWith(
NAME_PARAMS_PROVIDED.packager.platform,
result,
);
});
});
describe('bad name parameters', () => {
beforeEach(() => {
(inferTitle as jest.Mock).mockResolvedValue(inferTitleMockedResult);
});
const params = { packager: { targetUrl: 'some url', platform: 'whatever' } };
test('it should call inferTitle when the name is undefined', async () => {
await name(params);
expect(inferTitle).toHaveBeenCalledWith(params.packager.targetUrl);
});
test('it should call inferTitle when the name is an empty string', async () => {
const testParams = {
...params,
name: '',
};
await name(testParams);
expect(inferTitle).toHaveBeenCalledWith(params.packager.targetUrl);
});
test('it should call sanitize filename', async () => {
const result = await name(params);
expect(sanitizeFilename).toHaveBeenCalledWith(
params.packager.platform,
result,
);
});
});
describe('handling inferTitle results', () => {
test('it should return the result from inferTitle', async () => {
const result = await name(NAME_PARAMS_NEEDS_INFER);
expect(result).toEqual(inferTitleMockedResult);
expect(inferTitle).toHaveBeenCalledWith(
NAME_PARAMS_NEEDS_INFER.packager.targetUrl,
);
});
test('it should return the default app name when the returned pageTitle is falsey', async () => {
(inferTitle as jest.Mock).mockResolvedValue(null);
const result = await name(NAME_PARAMS_NEEDS_INFER);
expect(result).toEqual(DEFAULT_APP_NAME);
expect(inferTitle).toHaveBeenCalledWith(
NAME_PARAMS_NEEDS_INFER.packager.targetUrl,
);
});
test('it should return the default app name when inferTitle rejects', async () => {
(inferTitle as jest.Mock).mockRejectedValue('some error');
const result = await name(NAME_PARAMS_NEEDS_INFER);
expect(result).toEqual(DEFAULT_APP_NAME);
expect(inferTitle).toHaveBeenCalledWith(
NAME_PARAMS_NEEDS_INFER.packager.targetUrl,
);
expect(log.warn).toHaveBeenCalledTimes(1); // eslint-disable-line @typescript-eslint/unbound-method
});
});

View File

@ -0,0 +1,36 @@
import * as log from 'loglevel';
import { sanitizeFilename } from '../../utils/sanitizeFilename';
import { inferTitle } from '../../infer/inferTitle';
import { DEFAULT_APP_NAME } from '../../constants';
type NameParams = {
packager: {
name?: string;
platform?: string;
targetUrl: string;
};
};
async function tryToInferName(targetUrl: string): Promise<string> {
try {
log.debug('Inferring name for', targetUrl);
const pageTitle = await inferTitle(targetUrl);
return pageTitle || DEFAULT_APP_NAME;
} catch (err: unknown) {
log.warn(
`Unable to automatically determine app name, falling back to '${DEFAULT_APP_NAME}'.`,
err,
);
return DEFAULT_APP_NAME;
}
}
export async function name(options: NameParams): Promise<string> {
let name: string | undefined = options.packager.name;
if (!name) {
name = await tryToInferName(options.packager.targetUrl);
}
return sanitizeFilename(options.packager.platform, name);
}

View File

@ -0,0 +1,90 @@
import { getChromeVersionForElectronVersion } from '../../infer/browsers/inferChromeVersion';
import { getLatestFirefoxVersion } from '../../infer/browsers/inferFirefoxVersion';
import { getLatestSafariVersion } from '../../infer/browsers/inferSafariVersion';
import { userAgent } from './userAgent';
jest.mock('./../../infer/browsers/inferChromeVersion');
jest.mock('./../../infer/browsers/inferFirefoxVersion');
jest.mock('./../../infer/browsers/inferSafariVersion');
test('when a userAgent parameter is passed', async () => {
const params = {
packager: {},
nativefier: { userAgent: 'valid user agent' },
};
await expect(userAgent(params)).resolves.toBeUndefined();
});
test('no userAgent parameter is passed', async () => {
const params = {
packager: { platform: 'mac' },
nativefier: {},
};
await expect(userAgent(params)).resolves.toBeUndefined();
});
test('edge userAgent parameter is passed', async () => {
(getChromeVersionForElectronVersion as jest.Mock).mockImplementationOnce(() =>
Promise.resolve('99.0.0'),
);
const params = {
packager: { platform: 'darwin' },
nativefier: { userAgent: 'edge' },
};
const parsedUserAgent = await userAgent(params);
expect(parsedUserAgent).not.toBe(params.nativefier.userAgent);
expect(parsedUserAgent).toContain('Edg/99.0.0');
});
test('firefox userAgent parameter is passed', async () => {
(getLatestFirefoxVersion as jest.Mock).mockImplementationOnce(() =>
Promise.resolve('100.0.0'),
);
const params = {
packager: { platform: 'win32' },
nativefier: { userAgent: 'firefox' },
};
const parsedUserAgent = await userAgent(params);
expect(parsedUserAgent).not.toBe(params.nativefier.userAgent);
expect(parsedUserAgent).toContain('Firefox/100.0.0');
});
test('safari userAgent parameter is passed', async () => {
(getLatestSafariVersion as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({
majorVersion: 101,
version: '101.0.0',
webkitVersion: '600.0.0.0',
}),
);
const params = {
packager: { platform: 'linux' },
nativefier: { userAgent: 'safari' },
};
const parsedUserAgent = await userAgent(params);
expect(parsedUserAgent).not.toBe(params.nativefier.userAgent);
expect(parsedUserAgent).toContain('Version/101.0.0 Safari');
});
test('short userAgent parameter is passed with an electronVersion', async () => {
(getChromeVersionForElectronVersion as jest.Mock).mockImplementationOnce(() =>
Promise.resolve('102.0.0'),
);
const params = {
packager: { electronVersion: '16.0.0', platform: 'darwin' },
nativefier: { userAgent: 'edge' },
};
const parsedUserAgent = await userAgent(params);
expect(parsedUserAgent).not.toBe(params.nativefier.userAgent);
expect(parsedUserAgent).toContain('102.0.0');
expect(getChromeVersionForElectronVersion).toHaveBeenCalledWith('16.0.0');
});

View File

@ -0,0 +1,93 @@
import * as log from 'loglevel';
import { DEFAULT_ELECTRON_VERSION } from '../../constants';
import { getChromeVersionForElectronVersion } from '../../infer/browsers/inferChromeVersion';
import { getLatestFirefoxVersion } from '../../infer/browsers/inferFirefoxVersion';
import { getLatestSafariVersion } from '../../infer/browsers/inferSafariVersion';
import { normalizePlatform } from '../optionsMain';
export type UserAgentOpts = {
packager: {
electronVersion?: string;
platform?: string;
};
nativefier: {
userAgent?: string;
};
};
const USER_AGENT_PLATFORM_MAPS: Record<string, string> = {
darwin: 'Macintosh; Intel Mac OS X 10_15_7',
linux: 'X11; Linux x86_64',
win32: 'Windows NT 10.0; Win64; x64',
};
const USER_AGENT_SHORT_CODE_MAPS: Record<
string,
(platform: string, electronVersion: string) => Promise<string>
> = {
edge: edgeUserAgent,
firefox: firefoxUserAgent,
safari: safariUserAgent,
};
export async function userAgent(
options: UserAgentOpts,
): Promise<string | undefined> {
if (!options.nativefier.userAgent) {
// No user agent got passed. Let's handle it with the app.userAgentFallback
return undefined;
}
if (
!Object.keys(USER_AGENT_SHORT_CODE_MAPS).includes(
options.nativefier.userAgent.toLowerCase(),
)
) {
// Real user agent got passed. No need to translate it.
log.debug(
`${options.nativefier.userAgent.toLowerCase()} not found in`,
Object.keys(USER_AGENT_SHORT_CODE_MAPS),
);
return undefined;
}
options.packager.platform = normalizePlatform(options.packager.platform);
const userAgentPlatform: string =
USER_AGENT_PLATFORM_MAPS[
options.packager.platform === 'mas' ? 'darwin' : options.packager.platform
];
const mapFunction = USER_AGENT_SHORT_CODE_MAPS[options.nativefier.userAgent];
return await mapFunction(
userAgentPlatform,
options.packager.electronVersion ?? DEFAULT_ELECTRON_VERSION,
);
}
async function edgeUserAgent(
platform: string,
electronVersion: string,
): Promise<string> {
const chromeVersion =
await getChromeVersionForElectronVersion(electronVersion);
return `Mozilla/5.0 (${platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36 Edg/${chromeVersion}`;
}
async function firefoxUserAgent(platform: string): Promise<string> {
const firefoxVersion = await getLatestFirefoxVersion();
return `Mozilla/5.0 (${platform}; rv:${firefoxVersion}) Gecko/20100101 Firefox/${firefoxVersion}`.replace(
'10_15_7',
'10.15',
);
}
async function safariUserAgent(platform: string): Promise<string> {
const safariVersion = await getLatestSafariVersion();
return `Mozilla/5.0 (${platform}) AppleWebKit/${safariVersion.webkitVersion} (KHTML, like Gecko) Version/${safariVersion.version} Safari/${safariVersion.webkitVersion}`;
}

Some files were not shown because too many files have changed in this diff Show More