Compare commits

..

72 Commits

Author SHA1 Message Date
Florian Rappl
b6e496b2c6 Merge pull request #1082 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.WebApp/ElectronHostHook/js-yaml-4.3.0
Bump js-yaml from 4.1.1 to 4.3.0 in /src/ElectronNET.WebApp/ElectronHostHook
2026-07-09 15:33:25 +02:00
Florian Rappl
5974cb9e43 Merge branch 'develop' into dependabot/npm_and_yarn/src/ElectronNET.WebApp/ElectronHostHook/js-yaml-4.3.0 2026-07-09 10:42:03 +02:00
dependabot[bot]
d4d4e9dddd Bump js-yaml in /src/ElectronNET.WebApp/ElectronHostHook
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.3.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.3.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-08 23:02:16 +00:00
Florian Rappl
3b5f21c724 Merge pull request #1081 from epsitec-sa/bug/default-runtime
chore: use default sdk runtime identifier
2026-07-06 15:17:02 +02:00
Samuel Nussbaum
5aebe807c3 change to less brittle solution 2026-07-06 11:02:48 +02:00
Samuel Nussbaum
bdee008cc8 use default sdk runtime identifier 2026-07-06 10:44:05 +02:00
Florian Rappl
7fc5c9b611 Merge pull request #1080 from epsitec-sa/bug/#1058
fix(shore): bloated asar and corrupted packaged directory
2026-07-05 11:08:43 +02:00
Samuel Nussbaum
86e5d74c03 dump to latest version 2026-07-03 15:24:49 +02:00
Samuel Nussbaum
e79b029e16 Fix scoped css naming issue 2026-07-03 15:09:49 +02:00
Samuel Nussbaum
5bdf6230b9 fix examples build and publish 2026-07-03 15:08:57 +02:00
Samuel Nussbaum
8c2382a05c Split packaging to avoid duplication in .asar archive 2026-07-03 11:28:52 +02:00
Florian Rappl
10b1b686cb Merge pull request #1079 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.WebApp/ElectronHostHook/js-yaml-4.2.0
Bump js-yaml from 4.1.1 to 4.2.0 in /src/ElectronNET.WebApp/ElectronHostHook
2026-06-24 14:51:00 +02:00
Florian Rappl
d7d2521d0c Merge pull request #1078 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.Host/multi-5f1280885e
Bump ws, engine.io and socket.io-adapter in /src/ElectronNET.Host
2026-06-24 14:50:35 +02:00
Florian Rappl
392419e5a7 Merge branch 'develop' into dependabot/npm_and_yarn/src/ElectronNET.Host/multi-5f1280885e 2026-06-24 14:50:10 +02:00
dependabot[bot]
f92f07a378 Bump js-yaml in /src/ElectronNET.WebApp/ElectronHostHook
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.2.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.2.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-24 07:11:36 +00:00
dependabot[bot]
9b045cbf01 Bump ws, engine.io and socket.io-adapter in /src/ElectronNET.Host
Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/socket.io) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.20.1 to 8.21.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.20.1...8.21.0)

Updates `engine.io` from 6.6.8 to 6.6.9
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.8...engine.io@6.6.9)

Updates `socket.io-adapter` from 2.5.7 to 2.5.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.7...socket.io-adapter@2.5.8)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.21.0
  dependency-type: indirect
- dependency-name: engine.io
  dependency-version: 6.6.9
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.8
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-24 03:30:33 +00:00
Florian Rappl
94ff3c1c4d Updated token usage 2026-06-22 23:23:44 +02:00
Florian Rappl
773937448d Updated version 2026-06-22 23:23:34 +02:00
Florian Rappl
b094051dcb Merge pull request #1077 from AeonSake/1076-add-overloads-for-useelectron
Add Overloads for `UseElectron`
2026-06-21 21:46:56 +02:00
Aeon
3a10e7e98d Add missing null-check 2026-06-20 01:38:58 +02:00
Aeon
056661028b Remove unused property 2026-06-20 01:26:55 +02:00
Aeon
27a8c69a9a Add missing extension method overloads 2026-06-20 01:26:25 +02:00
Aeon
8e900655b9 Replace static property with resolver service 2026-06-20 01:26:00 +02:00
Aeon
066efa80e2 Add service for resolving on-app-ready callback 2026-06-20 01:24:30 +02:00
Florian Rappl
18944fe222 Release 0.5.1 (#1074)
* Bump ws, engine.io and socket.io-adapter

Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/socket.io) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.17.1 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.17.1...8.20.1)

Updates `engine.io` from 6.6.4 to 6.6.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.4...engine.io@6.6.8)

Updates `socket.io-adapter` from 2.5.5 to 2.5.7
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.5...socket.io-adapter@2.5.7)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
- dependency-name: engine.io
  dependency-version: 6.6.8
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump tmp and exceljs in /src/ElectronNET.WebApp/ElectronHostHook

Bumps [tmp](https://github.com/raszi/node-tmp) to 0.2.7 and updates ancestor dependency [exceljs](https://github.com/exceljs/exceljs). These dependencies need to be updated together.


Updates `tmp` from 0.1.0 to 0.2.7
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.1.0...v0.2.7)

Updates `exceljs` from 1.15.0 to 4.4.0
- [Release notes](https://github.com/exceljs/exceljs/releases)
- [Commits](https://github.com/exceljs/exceljs/compare/v1.15.0...v4.4.0)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.7
  dependency-type: indirect
- dependency-name: exceljs
  dependency-version: 4.4.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ws, engine.io and socket.io-adapter in /src/ElectronNET.Host

Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/socket.io) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.18.3 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.20.1)

Updates `engine.io` from 6.6.5 to 6.6.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.5...engine.io@6.6.8)

Updates `socket.io-adapter` from 2.5.6 to 2.5.7
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.6...socket.io-adapter@2.5.7)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
- dependency-name: engine.io
  dependency-version: 6.6.8
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump ws, engine.io and socket.io-adapter

Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/socket.io) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.18.3 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.20.1)

Updates `engine.io` from 6.6.5 to 6.6.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.5...engine.io@6.6.8)

Updates `socket.io-adapter` from 2.5.6 to 2.5.7
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.6...socket.io-adapter@2.5.7)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
- dependency-name: engine.io
  dependency-version: 6.6.8
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Improved argument forwarding

* Added support for Electron 42+

* Removed wrongly placed line

* Use path directly

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-08 21:28:22 +02:00
Florian Rappl
0e83af032c Merge branch 'develop' of https://github.com/ElectronNET/Electron.NET into develop 2026-06-07 14:48:15 +02:00
Florian Rappl
1ec84cfef0 Use path directly 2026-06-07 14:48:07 +02:00
Florian Rappl
1620c50627 Merge pull request #1071 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.WebApp/ElectronHostHook/multi-8c9a216550
Bump ws, engine.io and socket.io-adapter in /src/ElectronNET.WebApp/ElectronHostHook
2026-06-07 13:44:13 +02:00
Florian Rappl
295b250e58 Merge pull request #1070 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.Host/multi-8c9a216550
Bump ws, engine.io and socket.io-adapter in /src/ElectronNET.Host
2026-06-07 13:44:04 +02:00
Florian Rappl
644628c803 Merge pull request #1069 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.WebApp/ElectronHostHook/multi-19724e71a7
Bump tmp and exceljs in /src/ElectronNET.WebApp/ElectronHostHook
2026-06-07 13:43:45 +02:00
Florian Rappl
a18944d4a4 Removed wrongly placed line 2026-06-07 13:41:21 +02:00
Florian Rappl
b5858a697a Added support for Electron 42+ 2026-06-07 13:22:25 +02:00
Florian Rappl
58b9ad0125 Improved argument forwarding 2026-06-06 21:10:19 +02:00
dependabot[bot]
e44927a655 Bump ws, engine.io and socket.io-adapter
Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/socket.io) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.18.3 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.20.1)

Updates `engine.io` from 6.6.5 to 6.6.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.5...engine.io@6.6.8)

Updates `socket.io-adapter` from 2.5.6 to 2.5.7
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.6...socket.io-adapter@2.5.7)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
- dependency-name: engine.io
  dependency-version: 6.6.8
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 21:29:26 +00:00
dependabot[bot]
ed9a24bb5e Bump ws, engine.io and socket.io-adapter in /src/ElectronNET.Host
Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/socket.io) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.18.3 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.20.1)

Updates `engine.io` from 6.6.5 to 6.6.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.5...engine.io@6.6.8)

Updates `socket.io-adapter` from 2.5.6 to 2.5.7
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.6...socket.io-adapter@2.5.7)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
- dependency-name: engine.io
  dependency-version: 6.6.8
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 03:59:46 +00:00
dependabot[bot]
94df47efa6 Bump tmp and exceljs in /src/ElectronNET.WebApp/ElectronHostHook
Bumps [tmp](https://github.com/raszi/node-tmp) to 0.2.7 and updates ancestor dependency [exceljs](https://github.com/exceljs/exceljs). These dependencies need to be updated together.


Updates `tmp` from 0.1.0 to 0.2.7
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.1.0...v0.2.7)

Updates `exceljs` from 1.15.0 to 4.4.0
- [Release notes](https://github.com/exceljs/exceljs/releases)
- [Commits](https://github.com/exceljs/exceljs/compare/v1.15.0...v4.4.0)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.7
  dependency-type: indirect
- dependency-name: exceljs
  dependency-version: 4.4.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 00:35:30 +00:00
Florian Rappl
d9f3da87a8 Merge pull request #1068 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.Host/ElectronHostHook/multi-8c9a216550
Bump ws, engine.io and socket.io-adapter in /src/ElectronNET.Host/ElectronHostHook
2026-05-25 22:22:22 +02:00
dependabot[bot]
a33d76ae2f Bump ws, engine.io and socket.io-adapter
Bumps [ws](https://github.com/websockets/ws), [engine.io](https://github.com/socketio/socket.io) and [socket.io-adapter](https://github.com/socketio/socket.io). These dependencies needed to be updated together.

Updates `ws` from 8.17.1 to 8.20.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.17.1...8.20.1)

Updates `engine.io` from 6.6.4 to 6.6.8
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/engine.io@6.6.4...engine.io@6.6.8)

Updates `socket.io-adapter` from 2.5.5 to 2.5.7
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-adapter@2.5.5...socket.io-adapter@2.5.7)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.20.1
  dependency-type: indirect
- dependency-name: engine.io
  dependency-version: 6.6.8
  dependency-type: indirect
- dependency-name: socket.io-adapter
  dependency-version: 2.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 20:11:16 +00:00
Florian Rappl
15d6a23016 Merge pull request #1067 from ElectronNET/develop
Release 0.5.0
2026-05-25 22:10:07 +02:00
Florian Rappl
c76ce9702d Merge pull request #1066 from ElectronNET/feature/auth-sample-docs
Added Auth Sample Docs
2026-05-25 21:56:35 +02:00
Florian Rappl
279a823eb7 Improved demo and updated docs 2026-05-25 21:44:07 +02:00
Florian Rappl
fdab81a3a6 Merge branch 'develop' of https://github.com/ElectronNET/Electron.NET into feature/auth-sample-docs 2026-05-25 21:27:52 +02:00
Florian Rappl
558e582789 Updated available lists 2026-05-25 21:18:00 +02:00
Florian Rappl
c4251955d9 Updated changelog 2026-05-25 20:56:21 +02:00
Florian Rappl
af5e70c000 Fixed missing namespace reference 2026-05-25 20:40:18 +02:00
Florian Rappl
fee3806953 Removed flag 2026-05-25 20:37:42 +02:00
Florian Rappl
979b25ffe7 Exclude file-system based transfer from #1038 (#1064) 2026-05-10 19:09:51 +02:00
Florian Rappl
07be82cca6 WIP 2026-05-10 18:21:30 +02:00
Gregor Biswanger
fe939e0fda ci: stop hanging PR workflows and remove duplicate runs (#1063)
- Build and Publish now only triggers on push to main/develop (was: every push), so feature-branch pushes no longer run a second full matrix in parallel to PR Validation.

- integration-tests concurrency group is now scoped per calling workflow (github.workflow + github.ref), preventing PR Validation and Build and Publish from canceling each other's reusable test runs.

- Added timeout-minutes (25 per matrix job, 15 per dotnet test step, 10 for summary job) so a hung Electron process no longer keeps a runner busy until the 6h default timeout, which previously required manual cancel.

- Added --blame-hang --blame-hang-timeout 5min to dotnet test so hung tests produce a dump and fail fast instead of stalling.

- Removed pointless 0-20s random sleep step at the start of every matrix job.
2026-05-09 22:26:43 +02:00
Gregor Biswanger
50c9076af6 fix: replace ReadAllLines with ReadAllText in MigrationChecks.targets… (#1062)
* fix: replace ReadAllLines with ReadAllText in MigrationChecks.targets (fixes #1035)

System.IO.File::ReadAllLines is not available as an MSBuild property function on
all platforms (e.g. macOS GitHub Actions), causing MSB4185.

ReadAllText is universally supported by MSBuild and sufficient for the regex check
that detects 'electron' references in a root package.json (ELECTRON008).
The intermediate ItemGroup for line accumulation is no longer needed.

* test: strengthen migration checks test with exit code assertion and remove reserved MSBuildProjectDirectory override

* test: locate MigrationChecks.targets via directory walk for path robustness

* test: replace em-dashes with ASCII hyphens to avoid bidi/hidden Unicode warning

* test: split build test into clean and electron-containing package.json scenarios
2026-05-09 22:08:36 +02:00
Gregor Biswanger
d7e1a09880 fix: correct ReleaseNotes deserialization for AutoUpdater (fixes #1039) (#1061)
* fix: correct ReleaseNotes deserialization for AutoUpdater (fixes #1039)

- TypeScript normalize() now maps string releaseNotes to { note } objects
  and also handles arrays of strings (both cases were broken in PR #1041)
- Added ReleaseNotesConverter (JsonConverter<ReleaseNoteInfo[]>) as defensive
  C# layer that handles all shapes: null, string, string[], object[]
- Added [JsonConverter] attribute on UpdateInfo.ReleaseNotes
- Added unit tests (no Electron required) covering all four input shapes

* chore: sync generated autoUpdater.js and .map with updated TypeScript source

* refactor: address Copilot PR review comments

- Fix namespace: ElectronNET.Converter -> ElectronNET.API.Converter
- Replace JsonDocument.ParseValue() with JsonSerializer.Deserialize<ReleaseNoteInfo>()
  for cleaner, allocation-free object array parsing
- Fix Write(): empty ReleaseNoteInfo[] now serializes as [] instead of null
- Use Array.Empty<ReleaseNoteInfo>() in UpdateInfo default initializer
- Add tests: Serialize_WithEmptyReleaseNotes and Serialize_WithNullReleaseNotes
2026-05-09 18:17:59 +02:00
dependabot[bot]
30b7b1d251 Bump socket.io-parser in /src/ElectronNET.Host/ElectronHostHook (#1043)
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.4 to 4.2.6.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.4...socket.io-parser@4.2.6)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-version: 4.2.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Florian Rappl <mail@florian-rappl.de>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 17:30:45 +02:00
dependabot[bot]
e9b3ba0add Bump socket.io-parser in /src/ElectronNET.WebApp/ElectronHostHook (#1044)
Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.5 to 4.2.6.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.5...socket.io-parser@4.2.6)

---
updated-dependencies:
- dependency-name: socket.io-parser
  dependency-version: 4.2.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Florian Rappl <mail@florian-rappl.de>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 17:30:27 +02:00
dependabot[bot]
89f721d1b3 Bump brace-expansion from 1.1.12 to 1.1.14 in /src/ElectronNET.WebApp/ElectronHostHook (#1060)
* Bump lodash in /src/ElectronNET.WebApp/ElectronHostHook (#1056)

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump brace-expansion in /src/ElectronNET.WebApp/ElectronHostHook

Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.12 to 1.1.14.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.14)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 17:26:55 +02:00
dependabot[bot]
833d288891 Bump lodash from 4.17.23 to 4.18.1 in /src/ElectronNET.Host (#1059)
* Bump lodash in /src/ElectronNET.WebApp/ElectronHostHook (#1056)

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump lodash from 4.17.23 to 4.18.1 in /src/ElectronNET.Host

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 17:24:25 +02:00
dependabot[bot]
e529d092c3 Bump lodash in /src/ElectronNET.WebApp/ElectronHostHook (#1056)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 17:11:36 +02:00
Florian Rappl
11f71feeb8 Automatic Port Selection and Secured Communication (#1038)
Adds dynamic OS-selected socket port handling and secured Electron/.NET communication.

The dotnet-first startup flow now generates the auth token on the .NET side and passes it to Electron through an environment variable. Electron reports the selected socket port through a temporary startup-info file, avoiding any dependency on parsing Electron console output.

Also avoids logging backend startup parameters because they include the auth token.
2026-05-09 17:03:32 +02:00
Florian Rappl
1e8b02648a Merge pull request #1041 from ElectronNET/hotfix/autoupdater-checkforchanges
Fixed Serialization of Release Notes
2026-03-26 10:27:43 +01:00
Florian Rappl
2a14840c6b Merge pull request #1017 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.WebApp/ElectronHostHook/lodash-4.17.23
Bump lodash from 4.17.21 to 4.17.23 in /src/ElectronNET.WebApp/ElectronHostHook
2026-03-26 10:27:18 +01:00
Florian Rappl
005feddcc5 Merge pull request #1049 from AeonSake/1048-hasshadow-true-by-default
Fix `HasShadow` Not Being `true` by Default
2026-03-25 23:31:08 +01:00
Florian Rappl
3f14f9ae7e Merge branch 'develop' into dependabot/npm_and_yarn/src/ElectronNET.WebApp/ElectronHostHook/lodash-4.17.23 2026-03-25 23:30:11 +01:00
Florian Rappl
ce271fd30a Merge pull request #1046 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.Host/flatted-3.4.2
Bump flatted from 3.3.3 to 3.4.2 in /src/ElectronNET.Host
2026-03-25 23:26:37 +01:00
Florian Rappl
1d1b9537b5 Merge pull request #1016 from ElectronNET/dependabot/npm_and_yarn/src/ElectronNET.Host/lodash-4.17.23
Bump lodash from 4.17.21 to 4.17.23 in /src/ElectronNET.Host
2026-03-25 23:25:44 +01:00
Aeon
4c48588ce2 Fix HasShadow not being true by default 2026-03-25 02:37:20 +01:00
dependabot[bot]
c9945a87a9 Bump flatted from 3.3.3 to 3.4.2 in /src/ElectronNET.Host
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-21 10:29:48 +00:00
Florian Rappl
8fc197b32a Proposed fix for releaseNotes serialization #1039 2026-03-18 20:12:06 +01:00
Florian Rappl
7930831b6d Added sample for auth 2026-03-18 19:49:48 +01:00
Florian Rappl
201046164c Merge pull request #1037 from ElectronNET/feature/socket-interface
Changed facade to socket connection
2026-03-05 23:18:24 +01:00
Florian Rappl
b8151a2fad Changed facade to socket connection 2026-03-05 11:25:22 +01:00
Florian Rappl
c1bf6d9423 Updated version 2026-03-05 11:13:07 +01:00
dependabot[bot]
f4da9588c9 Bump lodash in /src/ElectronNET.WebApp/ElectronHostHook
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-21 23:55:31 +00:00
dependabot[bot]
04c1415f6d Bump lodash from 4.17.21 to 4.17.23 in /src/ElectronNET.Host
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-21 23:29:29 +00:00
128 changed files with 1715 additions and 1408 deletions

View File

@@ -134,4 +134,16 @@ TL;DR: Unless there is a technical reason (e.g., a crucial new API not being ava
We pretty much release whenever we have something new (i.e., do fixes such as a 0.1.1, or add new features, such as a 0.2.0) quite quickly.
We will go for a 1.0.0 release of this as early as ~mid of January 2026 (unless we find some critical things or want to extend the beta phase for ElectronNET.Core). This should be sufficient time to get some user input and have enough experience to call it stable.
We will go for a 1.0.0 release of this as early as ~mid of June 2026 (unless we find some critical things or want to extend the beta phase for ElectronNET.Core). This should be sufficient time to get some user input and have enough experience to call it stable.
## Updating Electron Versions
The releases of Electron are found on the [releases.electronjs.org](https://releases.electronjs.org/release?page=1) website.
You can update the `src\ElectronNET\build\ElectronNETRules.Project.xaml` file with new entries using the following script (run it in the browser's console when being on the Electron Releases website):
```js
[...new Set([...document.querySelectorAll('tbody tr a[href^="/release/v"]')].map(m => m.getAttribute('href').replace('/release/v', '')))].map(m => `<EnumValue Name="${m}" DisplayName="${m}" />`).sort().join('\n')
```
Alternatively, use the website's information to feed into an AI agent of your choice.

View File

@@ -1,6 +1,8 @@
name: Build and Publish
on: [push]
on:
push:
branches: [main, develop]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -4,13 +4,14 @@ on:
workflow_call:
concurrency:
group: integration-tests-${{ github.ref }}
group: integration-tests-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
name: ${{ matrix.os }} API-${{ matrix.electronVersion }}
runs-on: ${{ matrix.os }}
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
@@ -42,13 +43,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Random delay (0-20 seconds)
shell: bash
run: |
DELAY=$((RANDOM % 21))
echo "Waiting for $DELAY seconds..."
sleep $DELAY
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
@@ -79,27 +73,31 @@ jobs:
- name: Run tests (Linux)
if: runner.os == 'Linux'
continue-on-error: true
timeout-minutes: 15
run: |
mkdir -p test-results
xvfb-run -a dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj \
-c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} \
--logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" \
--logger "console;verbosity=detailed" \
--blame-hang --blame-hang-timeout 5min \
--results-directory test-results
- name: Run tests (Windows)
if: runner.os == 'Windows'
continue-on-error: true
timeout-minutes: 15
run: |
New-Item -ItemType Directory -Force -Path test-results | Out-Null
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} --logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" --logger "console;verbosity=detailed" --results-directory test-results
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} --logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" --logger "console;verbosity=detailed" --blame-hang --blame-hang-timeout 5min --results-directory test-results
- name: Run tests (macOS)
if: runner.os == 'macOS'
continue-on-error: true
timeout-minutes: 15
run: |
mkdir -p test-results
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} --logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" --logger "console;verbosity=detailed" --results-directory test-results
dotnet test src/ElectronNET.IntegrationTests/ElectronNET.IntegrationTests.csproj -c Release --no-build -r ${{ matrix.rid }} -p:RuntimeIdentifier=${{ matrix.rid }} -p:ElectronVersion=${{ matrix.electronVersion }} --logger "trx;LogFileName=${{ matrix.os }}-electron-${{ matrix.electronVersion }}.trx" --logger "console;verbosity=detailed" --blame-hang --blame-hang-timeout 5min --results-directory test-results
- name: Upload raw test results
if: always()
@@ -114,6 +112,7 @@ jobs:
runs-on: ubuntu-24.04
if: always()
needs: [tests]
timeout-minutes: 10
permissions:
actions: read

View File

@@ -27,7 +27,7 @@ jobs:
run: |
echo "Inspecting jobs of workflow run $RUN_ID in $REPO"
jobs_json="$(gh api -R $REPO repos/$REPO/actions/runs/$RUN_ID/jobs)"
jobs_json="$(gh api repos/$REPO/actions/runs/$RUN_ID/jobs)"
echo "Jobs and conclusions:"
echo "$jobs_json" | jq '.jobs[] | {name: .name, conclusion: .conclusion}'

View File

@@ -1,3 +1,27 @@
# 0.5.2
## ElectronNET.Core
- Fixed token param being appended to external URLs (#1075)
- Added more variants for `UseElectron` (#1076) @AeonSake
# 0.5.1
## ElectronNET.Core
- Fixed slicing of arguments for packaged applications (#1072)
- Added support for Electron 42+ (#1073)
# 0.5.0
## ElectronNET.Core
- Updated internal facade from `SocketIoFacade` to `ISocketConnection` (#1037)
- Fixed `HasShadow` is not `true` by default (#1049) @AeonSake
- Fixed missing `ReadAllLines` in MSBuild execution (#1035)
- Fixed serialization of release notes for the updater (#1041)
- Added automatic port selection and secured IPC communication (#1038)
# 0.4.1
## ElectronNET.Core

View File

@@ -1,583 +0,0 @@
# Electron.NET SignalR Authentication Guide
This guide explains the token-based authentication system implemented for SignalR mode in Electron.NET, designed to protect applications in multi-user environments.
## Table of Contents
1. [Overview](#overview)
2. [Threat Model](#threat-model)
3. [Authentication Architecture](#authentication-architecture)
4. [Implementation Details](#implementation-details)
5. [Security Properties](#security-properties)
6. [Troubleshooting](#troubleshooting)
7. [FAQ](#faq)
## Overview
Electron.NET's SignalR mode includes built-in authentication to ensure that only the Electron process spawned by a specific .NET instance can connect to that instance's HTTP and SignalR endpoints.
**When is this important?**
- Multi-user Windows Server environments (Terminal Services, RDP)
- Shared development machines with multiple users
- Any scenario where multiple users run the same application simultaneously
**What does it protect against?**
- User A's Electron process connecting to User B's .NET backend
- Unauthorized port scanning and connection attempts from other users
- Accidental misconfigurations causing cross-user connections
## Threat Model
### The Problem
When multiple users run the same Electron.NET application on a shared server:
1. Each .NET process binds to a TCP port (e.g., `http://localhost:58971`)
2. TCP ports are visible to **all users** on the machine
3. Without authentication, User A's Electron could connect to User B's backend
```
┌─────────────────────────────────────────────────┐
│ Windows Server (Terminal Services) │
├─────────────────────────────────────────────────┤
│ User A Session │
│ ├─ .NET Process (localhost:58971) │
│ └─ Electron Process │
│ │
│ User B Session │
│ ├─ .NET Process (localhost:61234) │
│ └─ Electron Process (could connect to 58971) │ ❌ Prevent this!
└─────────────────────────────────────────────────┘
```
### The Solution
Each .NET process generates a unique authentication token when launching Electron. Only requests with the correct token are allowed to connect.
```
┌─────────────────────────────────────────────────┐
│ User A Session │
│ ├─ .NET (token: abc123...) │
│ └─ Electron (has token abc123...) │ ✅ Authenticated
│ │
│ User B Session │
│ ├─ .NET (token: xyz789...) │
│ └─ Electron (has token xyz789...) │ ✅ Authenticated
│ │
│ ❌ User B's Electron → User A's .NET │
│ (lacks token abc123...) │ ❌ Rejected (401)
└─────────────────────────────────────────────────┘
```
## Authentication Architecture
### Flow Diagram
```
┌──────────────┐ ┌─────────────────┐
│ .NET Process │ │ Electron Process│
└──────┬───────┘ └────────┬────────┘
│ │
│ 1. Generate Token (GUID) │
│ Token: a3f8b2c1d4e5... │
│ │
│ 2. Launch Electron │
│ --authtoken=a3f8b2c1d4e5... │
│────────────────────────────────────────────────────>│
│ │
│ │ 3. Extract Token
│ │ global.authToken = ...
│ │
│ │ 4. Initial Page Load
│<────────────────────────────────────────────────────│
│ GET /?token=a3f8b2c1d4e5... │
│ │
│ 5. Validate Token │
│ ✓ Valid → Set Cookie │
│────────────────────────────────────────────────────>│
│ HTTP 200 + Set-Cookie: ElectronAuth=... │
│ │
│ │ 6. SignalR Connection
│<────────────────────────────────────────────────────│
│ GET /electron-hub?token=a3f8b2c1d4e5... │
│ │
│ 7. Validate Token, Set Cookie │
│────────────────────────────────────────────────────>│
│ HTTP 200 + Set-Cookie │
│ │
│ │ 8. Subsequent Requests
│<────────────────────────────────────────────────────│
│ GET /api/data │
│ Cookie: ElectronAuth=a3f8b2c1d4e5... │
│ │
│ 9. Validate Cookie │
│────────────────────────────────────────────────────>│
│ HTTP 200 (authenticated) │
│ │
```
### Key Steps
1. **Token Generation**: .NET generates 128-bit cryptographic random GUID
2. **Command-Line Passing**: Token passed to Electron via `--authtoken` parameter
3. **Token Extraction**: Electron stores token in `global.authToken`
4. **URL Appending**: Token appended to initial page and SignalR connection URLs
5. **Middleware Validation**: Every HTTP request validated by middleware
6. **Cookie Setting**: Valid token results in secure HttpOnly cookie
7. **Cookie-Based Requests**: Subsequent requests use cookie (no token in URL)
## Implementation Details
### 1. Token Generation (.NET)
**File**: `src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirstSignalR.cs`
```csharp
private void LaunchElectron()
{
// Generate secure authentication token (128-bit cryptographic random GUID)
this.authenticationToken = Guid.NewGuid().ToString("N"); // 32 hex chars
// Register token with authentication service
this.authenticationService.SetExpectedToken(this.authenticationToken);
// Launch Electron with token
var args = $"--unpackeddotnetsignalr --electronurl={this.actualUrl} --authtoken={this.authenticationToken}";
this.electronProcess = new ElectronProcessActive(isUnPacked, ElectronNetRuntime.ElectronExecutable, args, this.port.Value);
_ = this.electronProcess.Start();
}
```
**Token Format**: 32-character hexadecimal string (e.g., `a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5`)
### 2. Authentication Service (.NET)
**File**: `src/ElectronNET.AspNet/Services/ElectronAuthenticationService.cs`
```csharp
public class ElectronAuthenticationService : IElectronAuthenticationService
{
private string _expectedToken;
private readonly object _lock = new object();
public void SetExpectedToken(string token)
{
lock (_lock)
{
_expectedToken = token;
}
}
public bool ValidateToken(string token)
{
if (string.IsNullOrEmpty(token))
return false;
lock (_lock)
{
if (string.IsNullOrEmpty(_expectedToken))
return false;
// Constant-time comparison prevents timing attacks
return ConstantTimeEquals(token, _expectedToken);
}
}
private static bool ConstantTimeEquals(string a, string b)
{
if (a == null || b == null || a.Length != b.Length)
return false;
var result = 0;
for (int i = 0; i < a.Length; i++)
{
result |= a[i] ^ b[i];
}
return result == 0;
}
}
```
**Key Features**:
- Thread-safe with lock
- Constant-time comparison (prevents timing attacks)
- Singleton lifetime (one per .NET instance)
### 3. Authentication Middleware (.NET)
**File**: `src/ElectronNET.AspNet/Middleware/ElectronAuthenticationMiddleware.cs`
```csharp
public async Task InvokeAsync(HttpContext context)
{
// Check if authentication cookie exists
var authCookie = context.Request.Cookies["ElectronAuth"];
if (!string.IsNullOrEmpty(authCookie))
{
if (_authService.ValidateToken(authCookie))
{
await _next(context);
return;
}
else
{
_logger.LogWarning("Invalid cookie for path {Path}", context.Request.Path);
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized: Invalid authentication");
return;
}
}
// No cookie - check for token in query string
var token = context.Request.Query["token"].ToString();
if (!string.IsNullOrEmpty(token) && _authService.ValidateToken(token))
{
// Valid token - set cookie for future requests
context.Response.Cookies.Append("ElectronAuth", token, new CookieOptions
{
HttpOnly = true,
SameSite = SameSiteMode.Strict,
Path = "/",
Secure = false, // localhost is HTTP
IsEssential = true
});
await _next(context);
return;
}
// Reject - no valid cookie or token
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Unauthorized: Authentication required");
}
```
**Middleware Order** (IMPORTANT):
```csharp
app.UseMiddleware<ElectronAuthenticationMiddleware>(); // ← FIRST
app.UseRouting();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapHub<ElectronHub>("/electron-hub");
app.MapRazorComponents<App>();
```
### 4. Token Extraction (Electron)
**File**: `src/ElectronNET.Host/main.js`
```javascript
// Extract authentication token from command-line
if (app.commandLine.hasSwitch('authtoken')) {
global.authToken = app.commandLine.getSwitchValue('authtoken');
}
```
### 5. Token in URLs (Electron)
**Initial Page Load** (`src/ElectronNET.Host/api/browserWindows.js`):
```javascript
// Append token to window URL
if (global.authToken) {
const separator = electronUrl.includes('?') ? '&' : '?';
electronUrl = `${electronUrl}${separator}token=${global.authToken}`;
}
window.loadURL(electronUrl);
```
**SignalR Connection** (`src/ElectronNET.Host/api/signalr-bridge.js`):
```javascript
async connect() {
// Append token to SignalR hub URL
const connectionUrl = this.authToken ?
`${this.hubUrl}?token=${this.authToken}` :
this.hubUrl;
this.connection = new signalR.HubConnectionBuilder()
.withUrl(connectionUrl)
.build();
await this.connection.start();
}
```
### 6. Service Registration (Application)
**File**: Your application's `Program.cs`
```csharp
var builder = WebApplication.CreateBuilder(args);
// Register authentication service as singleton
builder.Services.AddSingleton<IElectronAuthenticationService, ElectronAuthenticationService>();
builder.Services.AddElectron();
var app = builder.Build();
// Register middleware BEFORE UseRouting()
app.UseMiddleware<ElectronAuthenticationMiddleware>();
app.UseRouting();
app.MapHub<ElectronHub>("/electron-hub");
app.Run();
```
## Security Properties
### Cookie Configuration
| Property | Value | Purpose |
|----------|-------|---------|
| `HttpOnly` | `true` | Prevents JavaScript access (XSS protection) |
| `SameSite` | `Strict` | Prevents CSRF attacks |
| `Path` | `/` | Cookie sent with all requests |
| `Secure` | `false` | Cannot use on localhost HTTP |
| `IsEssential` | `true` | Required for app to function |
| **Lifetime** | Session | Expires when Electron closes |
### Token Properties
- **Entropy**: 128 bits (2^128 possible values)
- **Format**: 32 hexadecimal characters (GUID without hyphens)
- **Generation**: `Guid.NewGuid()` uses cryptographically secure RNG
- **Lifetime**: Entire application session
- **Uniqueness**: Each .NET instance generates unique token
### What's Protected
**All HTTP endpoints**:
- Blazor Server pages (`/`, `/counter`, etc.)
- Static files (`/css/app.css`, `/js/script.js`)
- API endpoints (custom controllers)
- SignalR hub (`/electron-hub`)
**Both transport modes**:
- Initial token-based authentication (query parameter)
- Cookie-based subsequent requests
**Cross-user isolation**:
- Different users = different tokens
- Invalid token = 401 Unauthorized
### What's NOT Protected Against
**Same-user attacks** (by design):
- Process memory inspection
- Debugger attachment
- Command-line parameter visibility
**Rationale**: A malicious process running as the same user already has full access to:
- Process memory (can read token from RAM)
- Cookies (stored in Electron's data directory)
- All files owned by the user
Token-based authentication focuses on **cross-user isolation**, not same-user security.
## Troubleshooting
### Problem: 401 Unauthorized on Initial Load
**Symptoms**:
```
GET / HTTP/1.1
Response: 401 Unauthorized
```
**Possible Causes**:
1. Token not passed to Electron
2. Token not appended to URL
3. Middleware rejecting valid token
**Debugging Steps**:
1. Check Electron command-line:
```javascript
console.log('Auth Token:', global.authToken);
```
Should print 32-character hex string.
2. Check URL in browser window:
```javascript
console.log('Loading URL:', electronUrl);
```
Should include `?token=<guid>` parameter.
3. Check .NET logs:
```
[Warning] Authentication failed: Invalid token (prefix: a3f8b2c1...)
```
4. Verify middleware registration:
```csharp
app.UseMiddleware<ElectronAuthenticationMiddleware>(); // Before UseRouting()
```
### Problem: SignalR Connection Fails
**Symptoms**:
```
[SignalRBridge] Authentication failed: The authentication token is invalid or missing.
```
**Possible Causes**:
1. Token not passed to `SignalRBridge` constructor
2. Token not appended to hub URL
3. Cookie not being sent
**Debugging Steps**:
1. Check token passed to SignalRBridge:
```javascript
console.log('SignalRBridge token:', this.authToken);
```
2. Check connection URL:
```javascript
console.log('Hub URL:', connectionUrl);
```
Should be `http://localhost:PORT/electron-hub?token=<guid>`.
3. Enable verbose logging:
```javascript
.configureLogging(signalR.LogLevel.Debug)
```
### Problem: Cookie Not Persisting
**Symptoms**: Every request includes token in URL, cookie never set.
**Possible Causes**:
1. Cookie settings incompatible with browser
2. Middleware not setting cookie
3. Response headers not sent
**Debugging Steps**:
1. Check response headers:
```
Set-Cookie: ElectronAuth=a3f8b2c1...; Path=/; HttpOnly; SameSite=Strict
```
2. Check subsequent requests:
```
Cookie: ElectronAuth=a3f8b2c1...
```
3. Enable middleware logging:
```csharp
_logger.LogInformation("Setting cookie for path {Path}", path);
```
### Problem: Token Visible in Process List
**Observation**:
```powershell
wmic process where "name='electron.exe'" get commandline
...--authtoken=a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5...
```
**Is this a problem?** No, by design.
**Explanation**:
- Command-line parameters are visible to same-user processes
- This is acceptable because:
- Same user already has access to process memory
- Same user can read cookies from Electron's data directory
- Token-based auth protects against **other users**, not same-user processes
**If you need same-user protection**: Use OS-level access controls (file permissions, process isolation) or consider named pipes with ACLs.
## FAQ
### Q: Why use tokens instead of Process ID validation?
**A**: PIDs can be recycled and reused, making validation unreliable. Additionally:
- PIDs don't provide cryptographic security
- Parent-child validation is platform-specific
- Adds complexity without meaningful security benefit
Token-based authentication provides:
- Cryptographic randomness (128-bit entropy)
- Simple cross-platform implementation
- No race conditions or PID recycling issues
### Q: Why not use HTTPS with certificates?
**A**: Localhost doesn't support HTTPS certificates easily:
- Self-signed certificates trigger browser warnings
- Certificate management adds complexity
- Token-based auth provides equivalent security for localhost IPC
### Q: Can I disable authentication?
**A**: Not recommended, but possible by removing middleware registration:
```csharp
// Remove this line:
// app.UseMiddleware<ElectronAuthenticationMiddleware>();
```
**Warning**: Only do this if:
- Application runs on single-user machines only
- No Terminal Services / RDP access
- You understand the security implications
### Q: Does this work with hot reload?
**A**: Yes, cookie persists across hot reload as long as Electron process keeps running.
### Q: What about multiple Electron windows?
**A**: All windows in the same Electron process share cookies automatically. Authentication works seamlessly across multiple windows.
### Q: How do I test authentication?
**Test 1 - Happy Path**:
1. Run application normally
2. Check logs for "Authentication successful"
3. Verify cookie is set (DevTools → Application → Cookies)
4. Subsequent requests should not include token in URL
**Test 2 - Invalid Token**:
1. Modify token in browser URL: `?token=invalid`
2. Should receive 401 Unauthorized
3. Check logs for "Authentication failed: Invalid token"
**Test 3 - No Token**:
1. Open browser manually to `http://localhost:PORT/`
2. Should receive 401 Unauthorized
3. Check logs for "Authentication failed: No cookie or token"
**Test 4 - Multi-User** (Windows Server/Terminal Services):
1. Launch app as User A
2. In User B session, try to connect to User A's port
3. Should receive 401 Unauthorized
### Q: What about packaged applications?
**A**: Authentication works identically in packaged mode. The `--authtoken` parameter is included in the packaged Electron executable.
### Q: Can I customize the cookie name?
**A**: Yes, modify `AuthCookieName` constant in `ElectronAuthenticationMiddleware.cs`:
```csharp
private const string AuthCookieName = "MyCustomCookieName";
```
## Summary
Electron.NET's token-based authentication provides:
**Security**: 128-bit entropy, constant-time comparison, secure cookies
**Simplicity**: Automatic token generation and validation
**Compatibility**: Works with Blazor, SignalR, and static files
**Monitoring**: Structured logging for security events
**Multi-User**: Cross-user isolation on shared servers
The authentication system is **enabled by default** in SignalR mode and requires minimal configuration. For most applications, simply register the services and middleware - everything else happens automatically.
For additional help or questions, see the [SignalR Implementation Summary](SignalR-Implementation-Summary.md) or open an issue on GitHub.

View File

@@ -54,7 +54,7 @@ Add the Electron.NET configuration to your `.csproj` file:
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.4.1" />
<PackageReference Include="ElectronNET.Core" Version="0.5.1" />
</ItemGroup>
```

View File

@@ -0,0 +1,32 @@
# Secure Communication
By default, the IPC communication between .NET and Node.js is secured on startup. Consequently, multiple instances running on different user accounts (but shared on the same machine) can safely co-exist. However, this protection is not enough to secure the web application behind - or make any security statement w.r.t. a malicious root user.
## Securing the Web Application
You can opt-in to also guard your ASP.NET Core application using the same mechanism that is already used to protected the IPC broker that deals with the .NET to Node.js communication.
The key to opt-in is to provide another service *before* calling `AddElectron` on the service collection.
The following two namespaces are used in the next instructions:
```cs
using ElectronNET.AspNet.Middleware;
using ElectronNET.AspNet.Services;
```
You'll need the following line:
```cs
builder.Services.AddSingleton<IElectronAuthenticationService, ElectronAuthenticationService>();
```
This way, Electron.NET is notified that you want to store and re-use the authentication token that has been negotiated between the .NET and Node.js processes at startup.
With this being set up you can register a middleware to actually deny requests that have originated outside of your Electron.NET application:
```cs
app.UseMiddleware<ElectronAuthenticationMiddleware>();
```
This must be placed above any routing (e.g., before calling `UseRouting` on the web application) in order to properly take effect.

View File

@@ -24,6 +24,7 @@
- [Startup-Methods](Using/Startup-Methods.md)
- [Debugging](Using/Debugging.md)
- [Package Building](Using/Package-Building.md)
- [Secure Communication](Using/Secure-Communication.md)
- [Adding a `custom_main.js`](Using/Custom_main.md)
# API Reference

View File

@@ -215,7 +215,8 @@ namespace ElectronNET.API.Entities
/// <summary>
/// Whether window should have a shadow. Default is true.
/// </summary>
public bool HasShadow { get; set; }
[DefaultValue(true)]
public bool HasShadow { get; set; } = true;
/// <summary>
/// Forces using dark theme for the window, only works on some GTK+3 desktop environments. Default is false.

View File

@@ -1,4 +1,8 @@
namespace ElectronNET.API.Entities
using System;
using System.Text.Json.Serialization;
using ElectronNET.API.Converter;
namespace ElectronNET.API.Entities
{
/// <summary>
///
@@ -24,7 +28,8 @@
/// <summary>
/// Gets or sets the release notes.
/// </summary>
public ReleaseNoteInfo[] ReleaseNotes { get; set; } = new ReleaseNoteInfo[0];
[JsonConverter(typeof(ReleaseNotesConverter))]
public ReleaseNoteInfo[] ReleaseNotes { get; set; } = Array.Empty<ReleaseNoteInfo>();
/// <summary>
/// Gets or sets the release date.

View File

@@ -2,8 +2,6 @@
// ReSharper disable once CheckNamespace
namespace ElectronNET.API
{
using ElectronNET.API.Bridge;
internal static class BridgeConnector
{
public static ISocketConnection Socket

View File

@@ -1,57 +1,56 @@
namespace ElectronNET.API.Bridge
namespace ElectronNET.API;
using System;
using System.Threading.Tasks;
/// <summary>
/// Common interface for communication facades.
/// Provides methods for bidirectional communication between .NET and Electron.
/// </summary>
internal interface ISocketConnection : IDisposable
{
using System;
using System.Threading.Tasks;
/// <summary>
/// Raised when the bridge connection is established.
/// </summary>
event EventHandler BridgeConnected;
/// <summary>
/// Common interface for communication facades.
/// Provides methods for bidirectional communication between .NET and Electron.
/// Raised when the bridge connection is lost.
/// </summary>
internal interface ISocketConnection : IDisposable
{
/// <summary>
/// Raised when the bridge connection is established.
/// </summary>
event EventHandler BridgeConnected;
event EventHandler BridgeDisconnected;
/// <summary>
/// Raised when the bridge connection is lost.
/// </summary>
event EventHandler BridgeDisconnected;
/// <summary>
/// Establishes the connection to Electron.
/// </summary>
void Connect();
/// <summary>
/// Establishes the connection to Electron.
/// </summary>
void Connect();
/// <summary>
/// Registers a persistent event handler.
/// </summary>
void On(string eventName, Action action);
/// <summary>
/// Registers a persistent event handler.
/// </summary>
void On(string eventName, Action action);
/// <summary>
/// Registers a persistent event handler with a typed parameter.
/// </summary>
void On<T>(string eventName, Action<T> action);
/// <summary>
/// Registers a persistent event handler with a typed parameter.
/// </summary>
void On<T>(string eventName, Action<T> action);
/// <summary>
/// Registers a one-time event handler.
/// </summary>
void Once(string eventName, Action action);
/// <summary>
/// Registers a one-time event handler.
/// </summary>
void Once(string eventName, Action action);
/// <summary>
/// Registers a one-time event handler with a typed parameter.
/// </summary>
void Once<T>(string eventName, Action<T> action);
/// <summary>
/// Registers a one-time event handler with a typed parameter.
/// </summary>
void Once<T>(string eventName, Action<T> action);
/// <summary>
/// Removes an event handler.
/// </summary>
void Off(string eventName);
/// <summary>
/// Removes an event handler.
/// </summary>
void Off(string eventName);
/// <summary>
/// Sends a message to Electron.
/// </summary>
Task Emit(string eventName, params object[] args);
}
}
/// <summary>
/// Sends a message to Electron.
/// </summary>
Task Emit(string eventName, params object[] args);
}

View File

@@ -5,7 +5,6 @@ namespace ElectronNET.API;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectronNET.API.Bridge;
using ElectronNET.API.Serialization;
using SocketIO.Serializer.SystemTextJson;
using SocketIO = SocketIOClient.SocketIO;

View File

@@ -0,0 +1,91 @@
using ElectronNET.API.Entities;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ElectronNET.API.Converter;
/// <summary>
/// Handles the polymorphic shape of releaseNotes coming from electron-builder.
/// Depending on the updater.fullChangelog setting, electron-builder sends:
/// - null → when there are no notes
/// - "some string" → plain string (FullChangelog = false, default)
/// - ["note A", "note B"] → array of strings (after broken normalize in older TS)
/// - [{ version, note }, ...] → array of objects (FullChangelog = true)
/// All forms are normalised to ReleaseNoteInfo[] so the C# model stays clean.
/// See: https://github.com/ElectronNET/Electron.NET/issues/1039
/// </summary>
public class ReleaseNotesConverter : JsonConverter<ReleaseNoteInfo[]>
{
// Ensure the converter is called even when the JSON token is null,
// so we can return an empty array instead of null.
public override bool HandleNull => true;
public override ReleaseNoteInfo[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
return Array.Empty<ReleaseNoteInfo>();
case JsonTokenType.String:
// Plain string: "Some release notes"
return new[] { new ReleaseNoteInfo { Note = reader.GetString() } };
case JsonTokenType.StartArray:
var list = new List<ReleaseNoteInfo>();
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
if (reader.TokenType == JsonTokenType.String)
{
// Array of strings: ["Note A", "Note B"]
list.Add(new ReleaseNoteInfo { Note = reader.GetString() });
}
else if (reader.TokenType == JsonTokenType.StartObject)
{
// Array of objects: [{ "version": "1.0", "note": "..." }]
var entry = JsonSerializer.Deserialize<ReleaseNoteInfo>(ref reader, options) ?? new ReleaseNoteInfo();
list.Add(entry);
}
else
{
reader.Skip();
}
}
return list.ToArray();
default:
throw new JsonException($"Unexpected token {reader.TokenType} when reading releaseNotes.");
}
}
public override void Write(Utf8JsonWriter writer, ReleaseNoteInfo[] value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
if (value.Length == 0)
{
writer.WriteStartArray();
writer.WriteEndArray();
return;
}
writer.WriteStartArray();
foreach (var item in value)
{
writer.WriteStartObject();
if (item.Version is not null)
{
writer.WriteString("version", item.Version);
}
writer.WriteString("note", item.Note);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
}

View File

@@ -1,7 +1,6 @@
namespace ElectronNET
{
using ElectronNET.API;
using ElectronNET.API.Bridge;
using ElectronNET.Runtime;
using ElectronNET.Runtime.Controllers;
using ElectronNET.Runtime.Data;
@@ -51,8 +50,6 @@
internal static int? ElectronProcessId { get; set; }
internal static Func<Task> OnAppReadyCallback { get; set; }
internal static ISocketConnection GetSocket()
{
return RuntimeControllerCore?.Socket;

View File

@@ -1,7 +1,6 @@
namespace ElectronNET.Runtime.Controllers
{
using ElectronNET.API;
using ElectronNET.API.Bridge;
using ElectronNET.Runtime.Services;
using ElectronNET.Runtime.Services.ElectronProcess;
using ElectronNET.Runtime.Services.SocketBridge;

View File

@@ -1,7 +1,6 @@
namespace ElectronNET.Runtime.Controllers
{
using ElectronNET.API;
using ElectronNET.API.Bridge;
using ElectronNET.Common;
using ElectronNET.Runtime.Data;
using ElectronNET.Runtime.Helpers;

View File

@@ -16,7 +16,7 @@
{
}
internal override SocketIOConnection Socket
internal override ISocketConnection Socket
{
get
{

View File

@@ -63,15 +63,13 @@
private static bool? CheckUnpackaged2()
{
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
var dir = new DirectoryInfo(baseDir);
var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
if (dir.Name == "bin" && dir.Parent?.Name == "resources")
{
return false;
}
if (Directory.Exists(Path.Combine(baseDir, ".electron")))
if (dir.GetDirectories().Any(e => e.Name == ".electron"))
{
return true;
}
@@ -86,6 +84,7 @@
return true;
}
return null;
}

View File

@@ -7,6 +7,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using ElectronNET.Common;
using ElectronNET.Runtime.Data;
@@ -18,6 +19,7 @@
internal class ElectronProcessActive : ElectronProcessBase
{
private readonly Regex extractor = new Regex("^Electron Socket: listening on port (\\d+) at .* using ([a-f0-9]+)$");
private readonly bool isUnpackaged;
private readonly string electronBinaryName;
private readonly string extraArguments;
@@ -104,7 +106,6 @@
}
var osPart = buildInfoRid.Split('-').First();
var mismatch = false;
switch (osPart)
@@ -158,9 +159,17 @@
return Task.CompletedTask;
}
private async Task StartInternal(string startCmd, string args, string directoriy)
private async Task StartInternal(string startCmd, string args, string directory)
{
var tcs = new TaskCompletionSource();
using var cts = new CancellationTokenSource(2 * 60_000); // cancel after 2 minutes
using var _ = cts.Token.Register(() =>
{
// Time is over - let's kill the process and move on
this.process.Cancel();
// We don't want to raise exceptions here - just pass the barrier
tcs.TrySetResult();
});
void Read_SocketIO_Parameters(object sender, string line)
{
@@ -179,15 +188,28 @@
}
}
void Monitor_SocketIO_Failure(object sender, EventArgs e)
{
// We don't want to raise exceptions here - just pass the barrier
if (tcs.Task.IsCompleted)
{
this.Process_Exited(sender, e);
}
else
{
tcs.TrySetResult();
}
}
try
{
Console.Error.WriteLine("[StartInternal]: startCmd: {0}", startCmd);
Console.Error.WriteLine("[StartInternal]: args: {0}", args);
this.process = new ProcessRunner("ElectronRunner");
this.process.ProcessExited += this.Process_Exited;
this.process.ProcessExited += Monitor_SocketIO_Failure;
this.process.LineReceived += Read_SocketIO_Parameters;
this.process.Run(startCmd, args, directoriy);
this.process.Run(startCmd, args, directory);
await tcs.Task.ConfigureAwait(false);
@@ -199,11 +221,11 @@
Console.Error.WriteLine("[StartInternal]: Process is not running: " + this.process.StandardOutput);
Task.Run(() => this.TransitionState(LifetimeState.Stopped));
throw new Exception("Failed to launch the Electron process.");
}
this.TransitionState(LifetimeState.Ready);
else
{
this.TransitionState(LifetimeState.Ready);
}
}
catch (Exception ex)
{

View File

@@ -25,6 +25,7 @@
this.CollectProcessData();
this.SetElectronExecutable();
ElectronNetRuntime.StartupMethod = this.DetectAppTypeAndStartup();
Console.WriteLine((string)("Evaluated StartupMethod: " + ElectronNetRuntime.StartupMethod));
@@ -56,11 +57,21 @@
if (isLaunchedByDotNet)
{
return isUnPackaged ? StartupMethod.UnpackedDotnetFirst: StartupMethod.PackagedDotnetFirst;
if (isUnPackaged)
{
return StartupMethod.UnpackedDotnetFirst;
}
return StartupMethod.PackagedDotnetFirst;
}
else
{
return isUnPackaged ? StartupMethod.UnpackedElectronFirst: StartupMethod.PackagedElectronFirst;
if (isUnPackaged)
{
return StartupMethod.UnpackedElectronFirst;
}
return StartupMethod.PackagedElectronFirst;
}
}
@@ -75,10 +86,10 @@
if (portArg != null)
{
var parts = portArg.Split('=', StringSplitOptions.TrimEntries);
if (parts.Length > 1 && int.TryParse(parts[1], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
{
ElectronNetRuntime.ElectronSocketPort = result;
Console.WriteLine("Use Electron Port: " + result);
}
}
@@ -88,10 +99,10 @@
if (pidArg != null)
{
var parts = pidArg.Split('=', StringSplitOptions.TrimEntries);
if (parts.Length > 1 && int.TryParse(parts[1], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
{
ElectronNetRuntime.ElectronProcessId = result;
Console.WriteLine("Electron Process ID: " + result);
}
}
@@ -113,8 +124,7 @@
private void SetElectronExecutable()
{
var executable = ElectronNetRuntime.BuildInfo.ElectronExecutable;
string executable = ElectronNetRuntime.BuildInfo.ElectronExecutable;
if (string.IsNullOrEmpty(executable))
{
throw new Exception("AssemblyMetadataAttribute 'ElectronExecutable' could not be found!");

View File

@@ -44,5 +44,98 @@
return builder;
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="WebApplicationBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process, forwarded to Electron.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="WebApplicationBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// var builder = WebApplication.CreateBuilder(args)
/// .UseElectron(args, async (processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
///
/// var app = builder.Build();
/// app.MapRazorPages();
/// app.Run();
/// </code>
/// </example>
public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func<string[], Task> onAppReadyCallback)
{
builder.WebHost.UseElectron(args, onAppReadyCallback);
return builder;
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="WebApplicationBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process, forwarded to Electron.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="WebApplicationBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// var builder = WebApplication.CreateBuilder(args)
/// .UseElectron(args, async (serviceProvider) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
///
/// var app = builder.Build();
/// app.MapRazorPages();
/// app.Run();
/// </code>
/// </example>
public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func<IServiceProvider, Task> onAppReadyCallback)
{
builder.WebHost.UseElectron(args, onAppReadyCallback);
return builder;
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="WebApplicationBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process, forwarded to Electron.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="WebApplicationBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// var builder = WebApplication.CreateBuilder(args)
/// .UseElectron(args, async (serviceProvider, processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
///
/// var app = builder.Build();
/// app.MapRazorPages();
/// app.Run();
/// </code>
/// </example>
public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func<IServiceProvider, string[], Task> onAppReadyCallback)
{
builder.WebHost.UseElectron(args, onAppReadyCallback);
return builder;
}
}
}

View File

@@ -61,8 +61,154 @@
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<Task> onAppReadyCallback)
{
ElectronNetRuntime.OnAppReadyCallback = onAppReadyCallback;
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(_ => new AppReadyCallbackResolver(onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="IWebHostBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// using Microsoft.AspNetCore.Hosting;
/// using Microsoft.Extensions.Hosting;
/// using ElectronNET.API;
///
/// public class Program
/// {
/// public static void Main(string[] args)
/// {
/// Host.CreateDefaultBuilder(args)
/// .ConfigureWebHostDefaults(webBuilder =>
/// {
/// webBuilder.UseStartup&lt;Startup&gt;();
/// webBuilder.UseElectron(args, async (processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
/// })
/// .Build()
/// .Run();
/// }
/// }
/// </code>
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<string[], Task> onAppReadyCallback)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(_ => new AppReadyCallbackResolver(args, onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="IWebHostBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// using Microsoft.AspNetCore.Hosting;
/// using Microsoft.Extensions.Hosting;
/// using ElectronNET.API;
///
/// public class Program
/// {
/// public static void Main(string[] args)
/// {
/// Host.CreateDefaultBuilder(args)
/// .ConfigureWebHostDefaults(webBuilder =>
/// {
/// webBuilder.UseStartup&lt;Startup&gt;();
/// webBuilder.UseElectron(args, async (serviceProvider) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
/// })
/// .Build()
/// .Run();
/// }
/// }
/// </code>
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<IServiceProvider, Task> onAppReadyCallback)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(provider => new AppReadyCallbackResolver(provider, onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="IWebHostBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// using Microsoft.AspNetCore.Hosting;
/// using Microsoft.Extensions.Hosting;
/// using ElectronNET.API;
///
/// public class Program
/// {
/// public static void Main(string[] args)
/// {
/// Host.CreateDefaultBuilder(args)
/// .ConfigureWebHostDefaults(webBuilder =>
/// {
/// webBuilder.UseStartup&lt;Startup&gt;();
/// webBuilder.UseElectron(args, async (serviceProvider, processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
/// })
/// .Build()
/// .Run();
/// }
/// }
/// </code>
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<IServiceProvider, string[], Task> onAppReadyCallback)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(provider => new AppReadyCallbackResolver(provider, args, onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
private static IWebHostBuilder UseElectronCore(IWebHostBuilder builder, string[] args)
{
// no matter how this is set - let's unset to prevent Electron not starting as expected
// e.g., VS Code sets this env variable, but this will cause `require("electron")` to not
// work as expected, see issue #952
@@ -75,7 +221,7 @@
// now we have implemented the live reload if app is run using /watch then we need to use the default project path.
// For port 0 (dynamic port assignment), Kestrel requires binding to specific IP (127.0.0.1) not localhost
var host = webPort == 0? "127.0.0.1" : "localhost";
var host = webPort == 0 ? "127.0.0.1" : "localhost";
if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
{

View File

@@ -97,4 +97,4 @@ namespace ElectronNET.AspNet.Middleware
await context.Response.WriteAsync("Unauthorized: Authentication required");
}
}
}
}

View File

@@ -7,7 +7,6 @@
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using ElectronNET.API;
using ElectronNET.API.Bridge;
using ElectronNET.AspNet.Services;
using ElectronNET.Common;
using ElectronNET.Runtime.Controllers;
@@ -18,13 +17,15 @@
{
private readonly IServer server;
private readonly AspNetLifetimeAdapter aspNetLifetimeAdapter;
private readonly IAppReadyCallbackResolver callbackResolver;
private readonly IElectronAuthenticationService authenticationService;
private SocketBridgeService socketBridge;
protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null)
protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null)
{
this.server = server;
this.aspNetLifetimeAdapter = aspNetLifetimeAdapter;
this.callbackResolver = callbackResolver;
this.authenticationService = authenticationService;
this.aspNetLifetimeAdapter.Ready += this.AspNetLifetimeAdapter_Ready;
this.aspNetLifetimeAdapter.Stopping += this.AspNetLifetimeAdapter_Stopping;
@@ -131,15 +132,15 @@
private async Task RunReadyCallback()
{
if (ElectronNetRuntime.OnAppReadyCallback == null)
if (!callbackResolver.HasCallback)
{
Console.WriteLine("Warning: Non OnReadyCallback provided in UseElectron() setup.");
Console.WriteLine("Warning: No OnReadyCallback provided in UseElectron() setup.");
return;
}
try
{
await ElectronNetRuntime.OnAppReadyCallback().ConfigureAwait(false);
await callbackResolver.Invoke().ConfigureAwait(false);
}
catch (Exception ex)
{

View File

@@ -2,19 +2,19 @@
{
using System;
using System.Threading.Tasks;
using System.Security.Principal;
using Microsoft.AspNetCore.Hosting.Server;
using ElectronNET.AspNet.Services;
using ElectronNET.Common;
using ElectronNET.Runtime.Data;
using ElectronNET.Runtime.Helpers;
using ElectronNET.Runtime.Services.ElectronProcess;
using System.Security.Principal;
internal class RuntimeControllerAspNetDotnetFirst : RuntimeControllerAspNetBase
{
private ElectronProcessBase electronProcess;
public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService)
public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, callbackResolver, authenticationService)
{
}
@@ -24,8 +24,8 @@
{
var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
var port = ElectronNetRuntime.ElectronSocketPort ?? 0;
var args = Environment.CommandLine;
var port = ElectronNetRuntime.ElectronSocketPort ?? 0;
this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, port);
this.electronProcess.Ready += this.ElectronProcess_Ready;

View File

@@ -11,7 +11,7 @@
{
private ElectronProcessBase electronProcess;
public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService)
public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, callbackResolver, authenticationService)
{
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Threading.Tasks;
namespace ElectronNET.AspNet.Runtime
{
internal class AppReadyCallbackResolver : IAppReadyCallbackResolver
{
private readonly Func<Task> _callback;
public AppReadyCallbackResolver()
{ }
public AppReadyCallbackResolver(Func<Task> callback)
{
_callback = callback;
}
public AppReadyCallbackResolver(string[] args, Func<string[], Task> callback)
{
if (callback != null)
{
_callback = () => callback.Invoke(args);
}
}
public AppReadyCallbackResolver(IServiceProvider serviceProvider, Func<IServiceProvider, Task> callback)
{
if (callback != null)
{
_callback = () => callback.Invoke(serviceProvider);
}
}
public AppReadyCallbackResolver(IServiceProvider serviceProvider, string[] args, Func<IServiceProvider, string[], Task> callback)
{
if (callback != null)
{
_callback = () => callback.Invoke(serviceProvider, args);
}
}
public bool HasCallback => _callback != null;
public Task Invoke() => _callback?.Invoke() ?? Task.CompletedTask;
}
}

View File

@@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace ElectronNET.AspNet.Runtime
{
internal interface IAppReadyCallbackResolver
{
public bool HasCallback { get; }
public Task Invoke();
}
}

View File

@@ -50,4 +50,4 @@ namespace ElectronNET.AspNet.Services
return result == 0;
}
}
}
}

View File

@@ -21,4 +21,4 @@ namespace ElectronNET.AspNet.Services
/// <returns>True if token is valid, false otherwise</returns>
bool ValidateToken(string token);
}
}
}

View File

@@ -70,7 +70,7 @@
<ProjectReference Include="..\ElectronNET.API\ElectronNET.API.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.4.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.5.1" Condition="'$(ElectronNetDevMode)' != 'true'" />
</ItemGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.targets" Condition="$(ElectronNetDevMode)" />

View File

@@ -39,6 +39,15 @@
"undici-types": "~7.14.0"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -101,20 +110,21 @@
}
},
"node_modules/engine.io": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
"version": "6.6.8",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz",
"integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==",
"license": "MIT",
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"@types/ws": "^8.5.12",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
"ws": "~8.20.1"
},
"engines": {
"node": ">=10.2.0"
@@ -129,6 +139,23 @@
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -193,28 +220,62 @@
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz",
"integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
"debug": "~4.4.1",
"ws": "~8.20.1"
}
},
"node_modules/socket.io-adapter/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
"integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -245,9 +306,9 @@
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"

View File

@@ -1,6 +1,14 @@
"use strict";
const electron_updater_1 = require("electron-updater");
let electronSocket;
function normalize(updateInfo) {
if (typeof updateInfo?.releaseNotes === "string") {
updateInfo.releaseNotes = [{ note: updateInfo.releaseNotes }];
}
else if (Array.isArray(updateInfo?.releaseNotes)) {
updateInfo.releaseNotes = updateInfo.releaseNotes.map((entry) => typeof entry === "string" ? { note: entry } : entry);
}
}
module.exports = (socket) => {
electronSocket = socket;
socket.on("register-autoUpdater-error", (id) => {
@@ -15,11 +23,13 @@ module.exports = (socket) => {
});
socket.on("register-autoUpdater-update-available", (id) => {
electron_updater_1.autoUpdater.on("update-available", (updateInfo) => {
normalize(updateInfo);
electronSocket.emit("autoUpdater-update-available" + id, updateInfo);
});
});
socket.on("register-autoUpdater-update-not-available", (id) => {
electron_updater_1.autoUpdater.on("update-not-available", (updateInfo) => {
normalize(updateInfo);
electronSocket.emit("autoUpdater-update-not-available" + id, updateInfo);
});
});
@@ -30,6 +40,7 @@ module.exports = (socket) => {
});
socket.on("register-autoUpdater-update-downloaded", (id) => {
electron_updater_1.autoUpdater.on("update-downloaded", (updateInfo) => {
normalize(updateInfo);
electronSocket.emit("autoUpdater-update-downloaded" + id, updateInfo);
});
});
@@ -89,6 +100,7 @@ module.exports = (socket) => {
electron_updater_1.autoUpdater
.checkForUpdatesAndNotify()
.then((updateCheckResult) => {
normalize(updateCheckResult?.updateInfo);
electronSocket.emit("autoUpdater-checkForUpdatesAndNotify-completed" + guid, updateCheckResult);
})
.catch((error) => {
@@ -99,6 +111,7 @@ module.exports = (socket) => {
electron_updater_1.autoUpdater
.checkForUpdates()
.then((updateCheckResult) => {
normalize(updateCheckResult?.updateInfo);
electronSocket.emit("autoUpdater-checkForUpdates-completed" + guid, updateCheckResult);
})
.catch((error) => {

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,16 @@ import { autoUpdater } from "electron-updater";
let electronSocket: Socket;
function normalize(updateInfo) {
if (typeof updateInfo?.releaseNotes === "string") {
updateInfo.releaseNotes = [{ note: updateInfo.releaseNotes }];
} else if (Array.isArray(updateInfo?.releaseNotes)) {
updateInfo.releaseNotes = updateInfo.releaseNotes.map((entry) =>
typeof entry === "string" ? { note: entry } : entry,
);
}
}
export = (socket: Socket) => {
electronSocket = socket;
@@ -20,12 +30,14 @@ export = (socket: Socket) => {
socket.on("register-autoUpdater-update-available", (id) => {
autoUpdater.on("update-available", (updateInfo) => {
normalize(updateInfo);
electronSocket.emit("autoUpdater-update-available" + id, updateInfo);
});
});
socket.on("register-autoUpdater-update-not-available", (id) => {
autoUpdater.on("update-not-available", (updateInfo) => {
normalize(updateInfo);
electronSocket.emit("autoUpdater-update-not-available" + id, updateInfo);
});
});
@@ -38,6 +50,7 @@ export = (socket: Socket) => {
socket.on("register-autoUpdater-update-downloaded", (id) => {
autoUpdater.on("update-downloaded", (updateInfo) => {
normalize(updateInfo);
electronSocket.emit("autoUpdater-update-downloaded" + id, updateInfo);
});
});
@@ -143,6 +156,7 @@ export = (socket: Socket) => {
autoUpdater
.checkForUpdatesAndNotify()
.then((updateCheckResult) => {
normalize(updateCheckResult?.updateInfo);
electronSocket.emit(
"autoUpdater-checkForUpdatesAndNotify-completed" + guid,
updateCheckResult,
@@ -160,6 +174,7 @@ export = (socket: Socket) => {
autoUpdater
.checkForUpdates()
.then((updateCheckResult) => {
normalize(updateCheckResult?.updateInfo);
electronSocket.emit(
"autoUpdater-checkForUpdates-completed" + guid,
updateCheckResult,

View File

@@ -280,8 +280,20 @@ module.exports = (socket, app) => {
// Append authentication token to initial URL if available
const token = global["authToken"];
if (token) {
const separator = loadUrl.includes("?") ? "&" : "?";
window.loadURL(`${loadUrl}${separator}token=${token}`);
try {
const url = new URL(loadUrl);
const isLocal = url.hostname === "localhost" ||
url.hostname === "127.0.0.1" ||
url.hostname === "::1";
if (isLocal) {
url.searchParams.set("token", token);
}
window.loadURL(url.toString());
}
catch {
// Handle invalid URLs or file:// URLs if needed
window.loadURL(loadUrl);
}
}
else {
window.loadURL(loadUrl);

File diff suppressed because one or more lines are too long

View File

@@ -313,8 +313,23 @@ export = (socket: Socket, app: Electron.App) => {
const token = global["authToken"];
if (token) {
const separator = loadUrl.includes("?") ? "&" : "?";
window.loadURL(`${loadUrl}${separator}token=${token}`);
try {
const url = new URL(loadUrl);
const isLocal =
url.hostname === "localhost" ||
url.hostname === "127.0.0.1" ||
url.hostname === "::1";
if (isLocal) {
url.searchParams.set("token", token);
}
window.loadURL(url.toString());
} catch {
// Handle invalid URLs or file:// URLs if needed
window.loadURL(loadUrl);
}
} else {
window.loadURL(loadUrl);
}

View File

@@ -57,6 +57,7 @@ if (app.commandLine.hasSwitch('electronurl')) {
// Custom startup hook: look for custom_main.js and invoke its onStartup(host) if present.
// If the hook returns false, abort Electron startup.
try {
const fs = require('fs');
const customMainPath = path.join(__dirname, 'custom_main.js');
if (fs.existsSync(customMainPath)) {
const customMain = require(customMainPath);
@@ -82,7 +83,7 @@ let manifestJsonFilePath = path.join(currentPath, manifestJsonFileName);
// if running unpackedelectron, lets change the path
if (unpackedelectron || unpackeddotnet) {
console.debug('Running in unpacked mode, dir: ' + currentPath);
console.debug('Running in unpackaged mode, dir: ' + currentPath);
manifestJsonFilePath = path.join(currentPath, manifestJsonFileName);
currentBinPath = path.join(currentPath, '../'); // go to project directory
@@ -132,7 +133,8 @@ if (manifestJsonFile.singleInstance) {
// Collect user supplied command line args (excluding those handled by Electron host itself)
function getForwardedArgs() {
const skipSwitches = new Set(['unpackedelectron', 'unpackeddotnet', 'dotnetpacked']);
return process.argv.slice(2).filter(arg => {
const sliceIndex = app.isPackaged ? 1 : 2;
return process.argv.slice(sliceIndex).filter(arg => {
if (!arg) return false;
// Node/Electron internal or we already process them
if (arg.startsWith('--manifest')) return false;
@@ -149,7 +151,7 @@ function getForwardedArgs() {
const forwardedArgs = getForwardedArgs();
app.on('ready', () => {
app.on('ready', () => {
// Fix ERR_UNKNOWN_URL_SCHEME using file protocol
// https://github.com/electron/electron/issues/23757
////protocol.registerFileProtocol('file', (request, callback) => {
@@ -171,31 +173,28 @@ app.on('ready', () => {
});
app.on('quit', async (event, exitCode) => {
// Clean up Socket.IO resources (legacy mode only)
if (server) {
try {
server.close();
server.closeAllConnections();
} catch (e) {
console.error('Error closing Socket.IO server:', e);
console.error(e);
}
}
// Clean up API process (Socket.IO mode only)
if (typeof apiProcess !== 'undefined' && apiProcess) {
if (apiProcess) {
try {
apiProcess.kill();
} catch (e) {
console.error('Error killing API process:', e);
console.error(e);
}
}
// Clean up Socket.IO connection
if (typeof io !== 'undefined' && io && typeof io.close === 'function') {
if (io && io.close) {
try {
io.close();
} catch (e) {
console.error('Error closing Socket.IO connection:', e);
console.error(e);
}
}
});

View File

@@ -9,12 +9,10 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@microsoft/signalr": "^8.0.7",
"dasherize": "^2.0.0",
"electron-host-hook": "file:./ElectronHostHook",
"electron-updater": "^6.6.2",
"image-size": "^1.2.1",
"portscanner": "^2.2.0",
"socket.io": "^4.8.1"
},
"devDependencies": {
@@ -253,40 +251,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@microsoft/signalr": {
"version": "8.0.17",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.17.tgz",
"integrity": "sha512-5pM6xPtKZNJLO0Tq5nQasVyPFwi/WBY3QB5uc/v3dIPTpS1JXQbaXAQAPxFoQ5rTBFE094w8bbqkp17F9ReQvA==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"eventsource": "^2.0.2",
"fetch-cookie": "^2.0.3",
"node-fetch": "^2.6.7",
"ws": "^7.5.10"
}
},
"node_modules/@microsoft/signalr/node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
@@ -391,6 +355,15 @@
"@types/node": "*"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -402,18 +375,6 @@
"@types/node": "*"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -489,15 +450,6 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/async": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"license": "MIT",
"dependencies": {
"lodash": "^4.17.14"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -916,20 +868,21 @@
}
},
"node_modules/engine.io": {
"version": "6.6.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz",
"integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==",
"version": "6.6.9",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz",
"integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==",
"license": "MIT",
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"@types/ws": "^8.5.12",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.18.3"
"ws": "~8.21.0"
},
"engines": {
"node": ">=10.2.0"
@@ -1151,24 +1104,6 @@
"node": ">=0.10.0"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/eventsource": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -1221,16 +1156,6 @@
"pend": "~1.2.0"
}
},
"node_modules/fetch-cookie": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
"integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
"license": "Unlicense",
"dependencies": {
"set-cookie-parser": "^2.4.8",
"tough-cookie": "^4.0.0"
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1276,9 +1201,9 @@
}
},
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
@@ -1562,15 +1487,6 @@
"node": ">=0.10.0"
}
},
"node_modules/is-number-like": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz",
"integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==",
"license": "ISC",
"dependencies": {
"lodash.isfinite": "^3.3.2"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1675,12 +1591,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
@@ -1694,12 +1604,6 @@
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lodash.isfinite": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
"integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -1797,26 +1701,6 @@
"node": ">= 0.6"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/normalize-url": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
@@ -1960,20 +1844,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/portscanner": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz",
"integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==",
"license": "MIT",
"dependencies": {
"async": "^2.6.0",
"is-number-like": "^1.0.3"
},
"engines": {
"node": ">=0.4",
"npm": ">=1.0.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -1994,18 +1864,6 @@
"node": ">=0.4.0"
}
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"funding": {
"url": "https://github.com/sponsors/lupomontero"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -2021,17 +1879,12 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"license": "MIT"
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
@@ -2054,12 +1907,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
@@ -2153,12 +2000,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2201,13 +2042,13 @@
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
"integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
"version": "2.5.8",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz",
"integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==",
"license": "MIT",
"dependencies": {
"debug": "~4.4.1",
"ws": "~8.18.3"
"ws": "~8.21.0"
}
},
"node_modules/socket.io-parser": {
@@ -2276,36 +2117,6 @@
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
"license": "MIT"
},
"node_modules/tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tough-cookie/node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -2373,16 +2184,6 @@
"punycode": "^2.1.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"license": "MIT",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -2392,22 +2193,6 @@
"node": ">= 0.8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -2442,9 +2227,9 @@
"license": "ISC"
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<!-- When this is enabled, the project will be switched from nuget packages to consuming the ElectronNet orchestration directly -->
<ElectronNetDevMode>true</ElectronNetDevMode>
<IsTest>True</IsTest>
</PropertyGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.props" Condition="$(ElectronNetDevMode)" />

View File

@@ -0,0 +1,193 @@
using System.Diagnostics;
namespace ElectronNET.IntegrationTests.Tests;
/// <summary>
/// Unit tests for ElectronNET.MigrationChecks.targets - no Electron runtime required.
/// Covers GitHub issue #1035: System.IO.File.ReadAllLines is not available as an MSBuild
/// property function on all platforms (e.g. macOS GitHub Actions), causing MSB4185.
/// </summary>
public class MigrationChecksTargetsTests
{
private static readonly string TargetsFilePath = FindTargetsFile();
/// <summary>
/// Walks up the directory tree from <see cref="AppContext.BaseDirectory"/> until it finds
/// the migration checks targets file. This is robust against varying output paths
/// (with or without RID subfolder, debug/release, etc.).
/// </summary>
private static string FindTargetsFile()
{
const string RelativeFromRepoRoot =
"src/ElectronNET/build/ElectronNET.MigrationChecks.targets";
const string RelativeFromSrc =
"ElectronNET/build/ElectronNET.MigrationChecks.targets";
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
var fromRepoRoot = Path.Combine(dir.FullName, RelativeFromRepoRoot);
if (File.Exists(fromRepoRoot))
{
return Path.GetFullPath(fromRepoRoot);
}
var fromSrc = Path.Combine(dir.FullName, RelativeFromSrc);
if (File.Exists(fromSrc))
{
return Path.GetFullPath(fromSrc);
}
dir = dir.Parent;
}
throw new FileNotFoundException(
"Could not locate ElectronNET.MigrationChecks.targets by walking up from " +
$"'{AppContext.BaseDirectory}'.");
}
// -----------------------------------------------------------------------
// Content-level test (RED before fix, GREEN after fix on ALL platforms)
// -----------------------------------------------------------------------
[Fact]
public void MigrationChecksTargets_ShouldNotUseReadAllLines()
{
// The file must exist - if this fails the path constant above is wrong.
File.Exists(TargetsFilePath).Should().BeTrue(
$"targets file must exist at '{TargetsFilePath}'");
var content = File.ReadAllText(TargetsFilePath);
// System.IO.File::ReadAllLines is not in the MSBuild property-function
// whitelist on all platforms (MSB4185 on macOS GitHub Actions, see #1035).
// ReadAllText must be used instead.
content.Should().NotContain(
"::ReadAllLines(",
"because ReadAllLines is not available as an MSBuild property function on all " +
"platforms. Use ReadAllText instead (GitHub issue #1035).");
}
// -----------------------------------------------------------------------
// Functional build test - verifies no MSB4185 at runtime
// (RED on platforms where ReadAllLines is restricted, GREEN after fix)
// -----------------------------------------------------------------------
[Fact]
public async Task MigrationChecksTargets_BuildWithCleanPackageJson_ShouldSucceedWithoutMSB4185()
{
// Positive case: a package.json that does NOT mention electron.
// The migration check must successfully read the file via ReadAllText
// (the code path fixed by issue #1035) without producing MSB4185.
var tempDir = CreateTempProjectDirectory();
try
{
await File.WriteAllTextAsync(
Path.Combine(tempDir, "package.json"),
"""{ "devDependencies": { "vite": "^5.0.0" } }""");
await WriteMinimalCsprojAsync(tempDir);
var (exitCode, output) = await RunDotnetBuildAsync(tempDir);
exitCode.Should().Be(0,
$"the build must succeed when the package.json contains no electron references. " +
$"Full build output:\n{output}");
output.Should().NotContain(
"MSB4185",
$"ReadAllLines must not be used as an MSBuild property function. " +
$"Full build output:\n{output}");
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public async Task MigrationChecksTargets_BuildWithPackageJsonContainingElectron_ShouldEmitELECTRON008WarningWithoutMSB4185()
{
// Negative case: a package.json that DOES contain "electron".
// The migration check must still read the file successfully (no MSB4185)
// and must emit the expected ELECTRON008 warning. ELECTRON008 is a
// <Warning>, not an <Error>, so the build itself still succeeds.
var tempDir = CreateTempProjectDirectory();
try
{
await File.WriteAllTextAsync(
Path.Combine(tempDir, "package.json"),
"""{ "devDependencies": { "electron": "^30.0.0" } }""");
await WriteMinimalCsprojAsync(tempDir);
var (exitCode, output) = await RunDotnetBuildAsync(tempDir);
exitCode.Should().Be(0,
$"ELECTRON008 is a Warning (not an Error) so the build itself must still " +
$"succeed. Full build output:\n{output}");
output.Should().NotContain(
"MSB4185",
$"ReadAllLines must not be used as an MSBuild property function. " +
$"Full build output:\n{output}");
output.Should().Contain(
"ELECTRON008",
$"the migration check must still detect electron references in package.json " +
$"after the ReadAllText migration. Full build output:\n{output}");
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
private static string CreateTempProjectDirectory()
{
var tempDir = Path.Combine(Path.GetTempPath(), $"electron-net-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
return tempDir;
}
private static Task WriteMinimalCsprojAsync(string tempDir)
{
// A minimal csproj that only imports the migration checks targets to keep the
// build fast. Note: MSBuildProjectDirectory is a reserved MSBuild property and
// must not be redefined manually; MSBuild sets it automatically to the folder
// of the csproj (which is tempDir here).
var targetsPathEscaped = TargetsFilePath.Replace("'", "&apos;");
return File.WriteAllTextAsync(
Path.Combine(tempDir, "TestApp.csproj"),
$"""
<Project>
<Import Project="{targetsPathEscaped}" />
<Target Name="Build" DependsOnTargets="ElectronMigrationChecks" />
</Project>
""");
}
private static async Task<(int ExitCode, string Output)> RunDotnetBuildAsync(string workingDirectory)
{
var psi = new ProcessStartInfo("dotnet", "build --nologo -v:minimal")
{
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
};
using var process = Process.Start(psi)!;
var stdOut = await process.StandardOutput.ReadToEndAsync();
var stdErr = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
return (process.ExitCode, stdOut + stdErr);
}
}

View File

@@ -0,0 +1,115 @@
namespace ElectronNET.IntegrationTests.Tests
{
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using ElectronNET.API.Entities;
/// <summary>
/// Unit tests for UpdateInfo JSON deserialization.
/// Tests the fix for issue #1039: releaseNotes arrives as string or string[] from electron-builder
/// when FullChangelog is false (default), but the C# model expects ReleaseNoteInfo[].
/// No Electron runtime is required for these tests.
/// </summary>
public class UpdateInfoSerializationTests
{
// camelCase + ignore null — mirrors ElectronJson.Options used in production
private static readonly JsonSerializerOptions Options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
// electron-builder sends a plain string when FullChangelog = false (default)
[Fact]
public void Deserialize_WithStringReleaseNotes_ShouldConvertToSingleEntry()
{
var json = """{"version":"1.2.3","releaseNotes":"Some release notes"}""";
var result = JsonSerializer.Deserialize<UpdateInfo>(json, Options);
result.Should().NotBeNull();
result.ReleaseNotes.Should().HaveCount(1);
result.ReleaseNotes[0].Note.Should().Be("Some release notes");
}
// After the (incorrect) TypeScript normalize: string → ["string"] which is an array of strings,
// not an array of ReleaseNoteInfo objects. The C# model must handle this too.
[Fact]
public void Deserialize_WithArrayOfStringReleaseNotes_ShouldConvertToEntries()
{
var json = """{"version":"1.2.3","releaseNotes":["Note A","Note B"]}""";
var result = JsonSerializer.Deserialize<UpdateInfo>(json, Options);
result.Should().NotBeNull();
result.ReleaseNotes.Should().HaveCount(2);
result.ReleaseNotes[0].Note.Should().Be("Note A");
result.ReleaseNotes[1].Note.Should().Be("Note B");
}
// When FullChangelog = true, electron-builder sends proper objects; this must keep working.
[Fact]
public void Deserialize_WithProperReleaseNoteObjects_ShouldDeserializeNormally()
{
var json = """{"version":"1.2.3","releaseNotes":[{"version":"1.2.3","note":"Proper note"}]}""";
var result = JsonSerializer.Deserialize<UpdateInfo>(json, Options);
result.Should().NotBeNull();
result.ReleaseNotes.Should().HaveCount(1);
result.ReleaseNotes[0].Version.Should().Be("1.2.3");
result.ReleaseNotes[0].Note.Should().Be("Proper note");
}
// Null releaseNotes should result in an empty array (matching the default value).
[Fact]
public void Deserialize_WithNullReleaseNotes_ShouldReturnEmptyArray()
{
var json = """{"version":"1.2.3","releaseNotes":null}""";
var result = JsonSerializer.Deserialize<UpdateInfo>(json, Options);
result.Should().NotBeNull();
result.ReleaseNotes.Should().NotBeNull();
result.ReleaseNotes.Should().BeEmpty();
}
// Absent releaseNotes field should keep the default empty array.
[Fact]
public void Deserialize_WithMissingReleaseNotes_ShouldReturnEmptyArray()
{
var json = """{"version":"1.2.3"}""";
var result = JsonSerializer.Deserialize<UpdateInfo>(json, Options);
result.Should().NotBeNull();
result.ReleaseNotes.Should().NotBeNull();
result.ReleaseNotes.Should().BeEmpty();
}
// Empty array must serialize as [] not null, so round-trips and downstream
// consumers don't receive unexpected null for a non-null array value.
[Fact]
public void Serialize_WithEmptyReleaseNotes_ShouldProduceEmptyArray()
{
var updateInfo = new UpdateInfo { Version = "1.2.3", ReleaseNotes = Array.Empty<ReleaseNoteInfo>() };
var json = JsonSerializer.Serialize(updateInfo, Options);
json.Should().Contain("\"releaseNotes\":[]");
}
// Null value: with DefaultIgnoreCondition.WhenWritingNull the property is
// omitted entirely at the serializer level (before Write() is called).
[Fact]
public void Serialize_WithNullReleaseNotes_ShouldOmitProperty()
{
var updateInfo = new UpdateInfo { Version = "1.2.3", ReleaseNotes = null };
var json = JsonSerializer.Serialize(updateInfo, Options);
json.Should().NotContain("releaseNotes");
}
}
}

View File

@@ -8,7 +8,7 @@
<ResourcePreloader />
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["electronnet-samples-blazorsignalr.styles.css"]" />
<link rel="stylesheet" href="@Assets["electronnet-samples-authmiddleware.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />

View File

@@ -1,6 +1,6 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">ElectronNET.Samples.BlazorSignalR</a>
<a class="navbar-brand" href="">ElectronNET.Samples.AuthMiddleware</a>
</div>
</div>

View File

@@ -6,6 +6,6 @@
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using ElectronNET.Samples.BlazorSignalR
@using ElectronNET.Samples.BlazorSignalR.Components
@using ElectronNET.Samples.BlazorSignalR.Components.Layout
@using ElectronNET.Samples.AuthMiddleware
@using ElectronNET.Samples.AuthMiddleware.Components
@using ElectronNET.Samples.AuthMiddleware.Components.Layout

View File

@@ -12,10 +12,10 @@
<Import Project="..\ElectronNET\build\ElectronNET.Core.props" Condition="$(ElectronNetDevMode)" />
<PropertyGroup Label="ElectronNetCommon">
<Title>Electron.NET Blazor SignalR Sample</Title>
<Title>Electron.NET Auth Middleware Sample</Title>
<Version>1.0.0</Version>
<Product>com.electronnet.blazor-signalr-sample</Product>
<Description>Sample Blazor Server application using Electron.NET with SignalR mode</Description>
<Product>com.electronnet.auth-middleware-sample</Product>
<Description>Sample Blazor Server application using Electron.NET with Auth Middleware</Description>
<Company>Electron.NET</Company>
<Copyright>Copyright © 2026, Electron.NET</Copyright>
<ElectronVersion>30.4.0</ElectronVersion>

View File

@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.Samples.AuthMiddleware", "ElectronNET.Samples.AuthMiddleware.csproj", "{1A87C5B5-16EE-5DA0-33B4-17DA71675A87}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1A87C5B5-16EE-5DA0-33B4-17DA71675A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A87C5B5-16EE-5DA0-33B4-17DA71675A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A87C5B5-16EE-5DA0-33B4-17DA71675A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A87C5B5-16EE-5DA0-33B4-17DA71675A87}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5892D89B-BD0D-43B9-9064-0CBAC9A0FF60}
EndGlobalSection
EndGlobal

View File

@@ -86,7 +86,7 @@ app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages:
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<ElectronNET.Samples.BlazorSignalR.Components.App>()
app.MapRazorComponents<ElectronNET.Samples.AuthMiddleware.Components.App>()
.AddInteractiveServerRenderMode();
app.Run();

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<DeleteExistingFiles>true</DeleteExistingFiles>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ProjectGuid>48eff821-2f4d-60cc-aa44-be0f1d6e5f35</ProjectGuid>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

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