diff --git a/ElectronNET.API/App.cs b/ElectronNET.API/App.cs
index 194dd4b..c59eb28 100644
--- a/ElectronNET.API/App.cs
+++ b/ElectronNET.API/App.cs
@@ -407,6 +407,27 @@ namespace ElectronNET.API
/// which will be preferred over name by Electron.
///
public string Name
+ {
+ [Obsolete("Use the asynchronous version NameAsync instead")]
+ get
+ {
+ return AsyncHelper.RunSync(async () => await NameAsync);
+ }
+ set
+ {
+ BridgeConnector.Socket.Emit("appSetName", value);
+ }
+ }
+
+ ///
+ /// A property that indicates the current application's name, which is the name in the
+ /// application's package.json file.
+ ///
+ /// Usually the name field of package.json is a short lowercase name, according to the npm modules spec. You
+ /// should usually also specify a productName field, which is your application's full capitalized name, and
+ /// which will be preferred over name by Electron.
+ ///
+ public Task NameAsync
{
get
{
@@ -423,14 +444,34 @@ namespace ElectronNET.API
BridgeConnector.Socket.Emit("appGetName");
return taskCompletionSource.Task;
- }).Result;
- }
- set
- {
- BridgeConnector.Socket.Emit("appSetName", value);
+ });
}
}
+ ///
+ ///
+ ///
+ ///
+ public Task GetName()
+
+ {
+
+ var taskCompletionSource = new TaskCompletionSource();
+
+ BridgeConnector.Socket.On("appGetNameCompleted", (result) =>
+ {
+ BridgeConnector.Socket.Off("appGetNameCompleted");
+ taskCompletionSource.SetResult((string)result);
+ });
+
+ BridgeConnector.Socket.Emit("appGetName");
+
+ return taskCompletionSource.Task;
+
+ }
+
+
+
internal App()
{
CommandLine = new CommandLine();
@@ -1479,6 +1520,27 @@ namespace ElectronNET.API
/// is used.
///
public string UserAgentFallback
+ {
+ [Obsolete("Use the asynchronous version UserAgentFallbackAsync instead")]
+ get
+ {
+ return AsyncHelper.RunSync(async () => await UserAgentFallbackAsync);
+ }
+ set
+ {
+ BridgeConnector.Socket.Emit("appSetUserAgentFallback", value);
+ }
+ }
+
+ ///
+ /// A which is the user agent string Electron will use as a global fallback.
+ ///
+ /// This is the user agent that will be used when no user agent is set at the webContents or
+ /// session level. It is useful for ensuring that your entire app has the same user agent. Set to a
+ /// custom value as early as possible in your app's initialization to ensure that your overridden value
+ /// is used.
+ ///
+ public Task UserAgentFallbackAsync
{
get
{
@@ -1487,19 +1549,15 @@ namespace ElectronNET.API
var taskCompletionSource = new TaskCompletionSource();
BridgeConnector.Socket.On("appGetUserAgentFallbackCompleted", (result) =>
- {
- BridgeConnector.Socket.Off("appGetUserAgentFallbackCompleted");
- taskCompletionSource.SetResult((string)result);
- });
+ {
+ BridgeConnector.Socket.Off("appGetUserAgentFallbackCompleted");
+ taskCompletionSource.SetResult((string)result);
+ });
BridgeConnector.Socket.Emit("appGetUserAgentFallback");
return taskCompletionSource.Task;
- }).Result;
- }
- set
- {
- BridgeConnector.Socket.Emit("appSetUserAgentFallback", value);
+ });
}
}
diff --git a/ElectronNET.API/AsyncHelper.cs b/ElectronNET.API/AsyncHelper.cs
new file mode 100644
index 0000000..8feeea6
--- /dev/null
+++ b/ElectronNET.API/AsyncHelper.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ElectronNET.API
+{
+ internal static class AsyncHelper
+ {
+ private static readonly TaskFactory _taskFactory = new
+ TaskFactory(CancellationToken.None,
+ TaskCreationOptions.None,
+ TaskContinuationOptions.None,
+ TaskScheduler.Default);
+
+ public static TResult RunSync(Func> func)
+ => _taskFactory
+ .StartNew(func)
+ .Unwrap()
+ .GetAwaiter()
+ .GetResult();
+
+ public static void RunSync(Func func)
+ => _taskFactory
+ .StartNew(func)
+ .Unwrap()
+ .GetAwaiter()
+ .GetResult();
+
+ public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
+ {
+ using (var timeoutCancellationTokenSource = new CancellationTokenSource())
+ {
+ var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
+ if (completedTask == task)
+ {
+ timeoutCancellationTokenSource.Cancel();
+ return await task; // Very important in order to propagate exceptions
+ }
+ else
+ {
+ throw new TimeoutException($"{nameof(TimeoutAfter)}: The operation has timed out after {timeout:mm\\:ss}");
+ }
+ }
+ }
+ }
+}
diff --git a/ElectronNET.API/AutoUpdater.cs b/ElectronNET.API/AutoUpdater.cs
index ccf2590..57ab1c6 100644
--- a/ElectronNET.API/AutoUpdater.cs
+++ b/ElectronNET.API/AutoUpdater.cs
@@ -1,6 +1,12 @@
using ElectronNET.API.Entities;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
using System.Threading.Tasks;
namespace ElectronNET.API
@@ -182,11 +188,48 @@ namespace ElectronNET.API
}
}
+ ///
+ /// The current application version
+ ///
+ public Task CurrentVersionAsync
+ {
+ get
+ {
+ return Task.Run(() =>
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+
+ BridgeConnector.Socket.On("autoUpdater-currentVersion-get-reply", (result) =>
+ {
+ BridgeConnector.Socket.Off("autoUpdater-currentVersion-get-reply");
+ SemVer version = ((JObject)result).ToObject();
+ taskCompletionSource.SetResult(version);
+ });
+ BridgeConnector.Socket.Emit("autoUpdater-currentVersion-get");
+
+ return taskCompletionSource.Task;
+ });
+ }
+ }
+
///
/// Get the update channel. Not applicable for GitHub.
/// Doesn’t return channel from the update configuration, only if was previously set.
///
+ [Obsolete("Use the asynchronous version ChannelAsync instead")]
public string Channel
+ {
+ get
+ {
+ return AsyncHelper.RunSync(async () => await ChannelAsync);
+ }
+ }
+
+ ///
+ /// Get the update channel. Not applicable for GitHub.
+ /// Doesn’t return channel from the update configuration, only if was previously set.
+ ///
+ public Task ChannelAsync
{
get
{
@@ -199,18 +242,52 @@ namespace ElectronNET.API
BridgeConnector.Socket.Off("autoUpdater-channel-get-reply");
taskCompletionSource.SetResult(result.ToString());
});
-
BridgeConnector.Socket.Emit("autoUpdater-channel-get");
return taskCompletionSource.Task;
- }).Result;
+ });
+ }
+ }
+
+
+
+ ///
+ /// The request headers.
+ ///
+ public Task> RequestHeadersAsync
+ {
+ get
+ {
+ return Task.Run(() =>
+ {
+ var taskCompletionSource = new TaskCompletionSource>();
+ BridgeConnector.Socket.On("autoUpdater-requestHeaders-get-reply", (headers) =>
+ {
+ BridgeConnector.Socket.Off("autoUpdater-requestHeaders-get-reply");
+ Dictionary result = ((JObject)headers).ToObject>();
+ taskCompletionSource.SetResult(result);
+ });
+ BridgeConnector.Socket.Emit("autoUpdater-requestHeaders-get");
+ return taskCompletionSource.Task;
+ });
}
}
///
- /// Emitted when there is an error while updating.
+ /// The request headers.
///
- public event Action OnError
+ public Dictionary RequestHeaders
+ {
+ set
+ {
+ BridgeConnector.Socket.Emit("autoUpdater-requestHeaders-set", JObject.FromObject(value, _jsonSerializer));
+ }
+ }
+
+ ///
+ /// Emitted when there is an error while updating.
+ ///
+ public event Action OnError
{
add
{
@@ -416,9 +493,26 @@ namespace ElectronNET.API
string guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.On("autoUpdaterCheckForUpdatesComplete" + guid, (updateCheckResult) =>
+ {
+ try
+ {
+ BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesComplete" + guid);
+ BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesError" + guid);
+ taskCompletionSource.SetResult(JObject.Parse(updateCheckResult.ToString()).ToObject());
+ }
+ catch (Exception ex)
+ {
+ taskCompletionSource.SetException(ex);
+ }
+ });
+ BridgeConnector.Socket.On("autoUpdaterCheckForUpdatesError" + guid, (error) =>
{
BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesComplete" + guid);
- taskCompletionSource.SetResult(JObject.Parse(updateCheckResult.ToString()).ToObject());
+ BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesError" + guid);
+ string message = "An error occurred in CheckForUpdatesAsync";
+ if (error != null && !string.IsNullOrEmpty(error.ToString()))
+ message = JsonConvert.SerializeObject(error);
+ taskCompletionSource.SetException(new ElectronException(message));
});
BridgeConnector.Socket.Emit("autoUpdaterCheckForUpdates", guid);
@@ -438,9 +532,29 @@ namespace ElectronNET.API
string guid = Guid.NewGuid().ToString();
BridgeConnector.Socket.On("autoUpdaterCheckForUpdatesAndNotifyComplete" + guid, (updateCheckResult) =>
+ {
+ try
+ {
+ BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesAndNotifyComplete" + guid);
+ BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesAndNotifyError" + guid);
+ if (updateCheckResult == null)
+ taskCompletionSource.SetResult(null);
+ else
+ taskCompletionSource.SetResult(JObject.Parse(updateCheckResult.ToString()).ToObject());
+ }
+ catch (Exception ex)
+ {
+ taskCompletionSource.SetException(ex);
+ }
+ });
+ BridgeConnector.Socket.On("autoUpdaterCheckForUpdatesAndNotifyError" + guid, (error) =>
{
BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesAndNotifyComplete" + guid);
- taskCompletionSource.SetResult(JObject.Parse(updateCheckResult.ToString()).ToObject());
+ BridgeConnector.Socket.Off("autoUpdaterCheckForUpdatesAndNotifyError" + guid);
+ string message = "An error occurred in autoUpdaterCheckForUpdatesAndNotify";
+ if (error != null)
+ message = JsonConvert.SerializeObject(error);
+ taskCompletionSource.SetException(new ElectronException(message));
});
BridgeConnector.Socket.Emit("autoUpdaterCheckForUpdatesAndNotify", guid);
@@ -501,5 +615,10 @@ namespace ElectronNET.API
return taskCompletionSource.Task;
}
+
+ private readonly JsonSerializer _jsonSerializer = new JsonSerializer()
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ };
}
}
diff --git a/ElectronNET.API/BridgeConnector.cs b/ElectronNET.API/BridgeConnector.cs
index 08a8474..1f0bcd9 100644
--- a/ElectronNET.API/BridgeConnector.cs
+++ b/ElectronNET.API/BridgeConnector.cs
@@ -23,6 +23,14 @@ namespace ElectronNET.API
{
Console.WriteLine("BridgeConnector connected!");
});
+ _socket.On(Socket.EVENT_CONNECT_ERROR, (args) =>
+ {
+ Console.WriteLine("Socket error! {0}", args??"no args");
+ });
+ _socket.On(Socket.EVENT_DISCONNECT, (args) =>
+ {
+ Console.WriteLine("Socket Disconnect! {0}", args ?? "no args");
+ });
}
}
}
diff --git a/ElectronNET.API/ElectronNET.API.csproj b/ElectronNET.API/ElectronNET.API.csproj
index 21a5ae1..76e2c52 100644
--- a/ElectronNET.API/ElectronNET.API.csproj
+++ b/ElectronNET.API/ElectronNET.API.csproj
@@ -40,6 +40,7 @@ This package contains the API to access the "native" electron API.
all
runtime; build; native; contentfiles; analyzers
+
diff --git a/ElectronNET.API/Entities/ElectronException.cs b/ElectronNET.API/Entities/ElectronException.cs
new file mode 100644
index 0000000..21e4555
--- /dev/null
+++ b/ElectronNET.API/Entities/ElectronException.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace ElectronNET.API.Entities
+{
+ ///
+ /// Electron Exception
+ ///
+ [Serializable]
+ public class ElectronException : Exception
+ {
+ ///
+ ///
+ ///
+ public ElectronException()
+ {
+
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public ElectronException(string error) : base(error)
+ {
+
+ }
+ }
+}
diff --git a/ElectronNET.API/Entities/SemVer.cs b/ElectronNET.API/Entities/SemVer.cs
new file mode 100644
index 0000000..e1a08f1
--- /dev/null
+++ b/ElectronNET.API/Entities/SemVer.cs
@@ -0,0 +1,59 @@
+namespace ElectronNET.API.Entities
+{
+ ///
+ ///
+ ///
+ public class SemVer
+ {
+ ///
+ ///
+ ///
+ public string Raw { get; set; }
+ ///
+ ///
+ ///
+ public bool Loose { get; set; }
+ ///
+ ///
+ ///
+ public SemVerOptions Options { get; set; }
+ ///
+ ///
+ ///
+ public int Major { get; set; }
+ ///
+ ///
+ ///
+ public int Minor { get; set; }
+ ///
+ ///
+ ///
+ public int Patch { get; set; }
+ ///
+ ///
+ ///
+ public string Version { get; set; }
+ ///
+ ///
+ ///
+ public string[] Build { get; set; }
+ ///
+ ///
+ ///
+ public string[] Prerelease { get; set; }
+ }
+
+ ///
+ ///
+ ///
+ public class SemVerOptions {
+ ///
+ ///
+ ///
+ public bool? Loose { get; set; }
+ ///
+ ///
+ ///
+ public bool? IncludePrerelease { get; set; }
+ }
+}
diff --git a/ElectronNET.API/HybridSupport.cs b/ElectronNET.API/HybridSupport.cs
index 935ea51..0a3c59f 100644
--- a/ElectronNET.API/HybridSupport.cs
+++ b/ElectronNET.API/HybridSupport.cs
@@ -1,4 +1,6 @@
-namespace ElectronNET.API
+using Quobject.SocketIoClientDotNet.Client;
+
+namespace ElectronNET.API
{
///
///
@@ -18,5 +20,10 @@
return !string.IsNullOrEmpty(BridgeSettings.SocketPort);
}
}
+
+ ///
+ ///
+ ///
+ public static Socket Socket { get { return BridgeConnector.Socket; } }
}
}
\ No newline at end of file
diff --git a/ElectronNET.CLI/done.txt b/ElectronNET.CLI/done.txt
new file mode 100644
index 0000000..1efa930
--- /dev/null
+++ b/ElectronNET.CLI/done.txt
@@ -0,0 +1,5 @@
+added npm package serialize-error to CLI api as a dependency
+It's used because node errors aren't stringified completely. ("message" gets lost in normal stringify while sending over socket)
+
+catch() clauses in electron-updater
+
diff --git a/ElectronNET.CLI/updatetool.ps1 b/ElectronNET.CLI/updatetool.ps1
new file mode 100644
index 0000000..ac8e9e7
--- /dev/null
+++ b/ElectronNET.CLI/updatetool.ps1
@@ -0,0 +1,2 @@
+dotnet tool uninstall -g electronnet.cli
+dotnet tool install -g --add-source ../artifacts electronnet.cli
\ No newline at end of file
diff --git a/ElectronNET.Host/api/autoUpdater.ts b/ElectronNET.Host/api/autoUpdater.ts
index f5493e0..4e26d2b 100644
--- a/ElectronNET.Host/api/autoUpdater.ts
+++ b/ElectronNET.Host/api/autoUpdater.ts
@@ -93,6 +93,11 @@ export = (socket: SocketIO.Socket) => {
autoUpdater.updateConfigPath = value;
});
+ socket.on('autoUpdater-currentVersion-get', () =>
+ {
+ electronSocket.emit('autoUpdater-currentVersion-get-reply', autoUpdater.currentVersion);
+ });
+
socket.on('autoUpdater-channel-get', () => {
electronSocket.emit('autoUpdater-channel-get-reply', autoUpdater.channel || '');
});
@@ -101,19 +106,42 @@ export = (socket: SocketIO.Socket) => {
autoUpdater.channel = value;
});
+ socket.on('autoUpdater-requestHeaders-get', () =>
+ {
+ electronSocket.emit('autoUpdater-requestHeaders-get-reply', autoUpdater.requestHeaders);
+ });
+
+ socket.on('autoUpdater-requestHeaders-set', (value) =>
+ {
+ autoUpdater.requestHeaders = value;
+ });
+
// Methods ********
- socket.on('autoUpdaterCheckForUpdatesAndNotify', async (guid) => {
- const updateCheckResult = await autoUpdater.checkForUpdatesAndNotify();
- electronSocket.emit('autoUpdaterCheckForUpdatesAndNotifyComplete' + guid, updateCheckResult);
+ socket.on('autoUpdaterCheckForUpdatesAndNotify', async (guid) =>
+ {
+ autoUpdater.checkForUpdatesAndNotify().then((updateCheckResult) =>
+ {
+ electronSocket.emit('autoUpdaterCheckForUpdatesAndNotifyComplete' + guid, updateCheckResult);
+ }).catch((error) =>
+ {
+ electronSocket.emit('autoUpdaterCheckForUpdatesAndNotifyError' + guid, error);
+ });
});
- socket.on('autoUpdaterCheckForUpdates', async (guid) => {
+ socket.on('autoUpdaterCheckForUpdates', async (guid) =>
+ {
// autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml');
- const updateCheckResult = await autoUpdater.checkForUpdates();
- electronSocket.emit('autoUpdaterCheckForUpdatesComplete' + guid, updateCheckResult);
+ autoUpdater.checkForUpdates().then((updateCheckResult) =>
+ {
+ electronSocket.emit('autoUpdaterCheckForUpdatesComplete' + guid, updateCheckResult);
+ }).catch((error) =>
+ {
+ electronSocket.emit('autoUpdaterCheckForUpdatesError' + guid, error);
+ });
});
+
socket.on('autoUpdaterQuitAndInstall', async (isSilent, isForceRunAfter) => {
autoUpdater.quitAndInstall(isSilent, isForceRunAfter);
});
diff --git a/ElectronNET.Host/main.js b/ElectronNET.Host/main.js
index d382fdf..2068e00 100644
--- a/ElectronNET.Host/main.js
+++ b/ElectronNET.Host/main.js
@@ -88,6 +88,7 @@ function startSplashScreen() {
throw new Error(error.message);
}
+ console.log("splashscreen ", dimensions.width, dimensions.height);
splashScreen = new BrowserWindow({
width: dimensions.width,
height: dimensions.height,
@@ -144,6 +145,7 @@ function startSocketApiBridge(port) {
// live reload watch happen.
socket.on('disconnect', function (reason) {
console.log('Got disconnect! Reason: ' + reason);
+ socket.removeAllListeners();
delete require.cache[require.resolve('./api/app')];
delete require.cache[require.resolve('./api/browserWindows')];
delete require.cache[require.resolve('./api/commandLine')];
@@ -232,11 +234,17 @@ function startAspCoreBackend(electronPort) {
let binFilePath = path.join(currentBinPath, binaryFile);
var options = { cwd: currentBinPath };
+ console.log("Starting child process", binFilePath, parameters, options);
apiProcess = cProcess(binFilePath, parameters, options);
-
+ console.log("Started");
apiProcess.stdout.on('data', (data) => {
console.log(`stdout: ${data.toString()}`);
});
+ apiProcess.on('exit', (code) =>
+ {
+ console.log(`child process exit: ${code}`);
+ app.exit(code);
+ });
}
}
@@ -259,11 +267,17 @@ function startAspCoreBackendWithWatch(electronPort) {
cwd: currentBinPath,
env: process.env,
};
+ console.log("Starting child process", binFilePath, parameters, options);
apiProcess = cProcess('dotnet', parameters, options);
-
+ console.log("Started");
apiProcess.stdout.on('data', (data) => {
console.log(`stdout: ${data.toString()}`);
});
+ apiProcess.on('exit', (code) =>
+ {
+ console.log(`child process exit: ${code}`);
+ app.exit(code);
+ });
}
}