Compare commits

..

52 Commits

Author SHA1 Message Date
Florian Rappl
d22532488e Updated to 23.6.2 2024-02-15 01:12:33 +01:00
Florian Rappl
30558dea5f Updated changelog 2024-02-15 01:10:48 +01:00
Florian Rappl
464eaca593 Merge branch 'main' of https://github.com/ElectronNET/Electron.NET into develop 2024-02-15 01:04:11 +01:00
Florian Rappl
df3bd122df Merge pull request #813 from softworkz/submit_execjs_domready
WebContents: add executeJavaScript and dom-ready event
2024-02-15 01:01:59 +01:00
Florian Rappl
eabcc3a6b6 Merge pull request #798 from dlanorok/fixes/appSetAccessibilitySupportEnabled
Fixes/app set accessibility support enabled
2024-02-15 01:01:38 +01:00
Florian Rappl
2b5435381f Merge pull request #800 from franpersanchez/main
fix: typo in README.md
2024-02-15 00:59:08 +01:00
Florian Rappl
0d63383899 Merge pull request #833 from Yuvix25/ipc-once-memory
Fix huge memory waste in IpcMain.Once [IMPORTANT]
2024-02-15 00:55:40 +01:00
Florian Rappl
f31fe19652 Merge pull request #828 from Yuvix25/yuvix25/update-display-object
Update Display.cs to current spec
2024-02-15 00:55:33 +01:00
Florian Rappl
0a80367e3e Merge pull request #822 from NickRimmer/features/splash-size
Configurable splash screen size
2024-02-15 00:55:13 +01:00
Florian Rappl
34761456ad Merge pull request #821 from sajmonr/feature/osx-architecture-detection
Added OSX architecture detection
2024-02-15 00:55:05 +01:00
Florian Rappl
e4e96bbcc4 Merge pull request #819 from NickRimmer/features/webContent-additional-events
WebContent additional events
2024-02-15 00:54:42 +01:00
Yuval Rosen
1253df3d9a Fix huge memory waste in IpcMain.Once 2024-02-15 01:06:08 +02:00
Yuval Rosen
b8b634beb3 update display.cs to current spec 2024-01-10 11:10:49 +02:00
Nick Rimmer
94dc82ec62 configurable splash screen size 2023-11-13 01:29:17 +01:00
Adam Simonicek
53ccf4d302 Added OSX architecture detection 2023-11-04 14:09:07 -04:00
Nick Rimmer
bf0bdc8386 did-start-navigation, did-navigate, will-redirect, did-fail-load, did-redirect-navigation events added for WebContents 2023-11-03 00:23:37 +01:00
softworkz
73a3e331dc Add executeJavaScript method to WebContents 2023-09-24 16:37:36 +02:00
softworkz
a15db713ad Add dom-ready event for WebContents 2023-09-24 16:37:36 +02:00
Fran Pérez
e2d03d6818 fix: typo in README.md 2023-08-24 18:24:15 +02:00
Ronald Ramirez Moran
0dc8369fb3 Method SetAccessibilitySupportEnabled is incorrect. 2023-08-23 18:39:37 -05:00
Florian Rappl
77b7141513 Use explicit version 2023-04-03 14:17:32 +02:00
Florian Rappl
dbf85c6f14 Improved version 2023-04-03 14:02:35 +02:00
Florian Rappl
c67f117bc7 Updated release notes format 2023-04-03 13:59:04 +02:00
Florian Rappl
9746edb936 Updated changelog 2023-04-03 12:19:05 +02:00
Florian Rappl
23f4d39a30 Merge pull request #757 from ElectronNET/feature/gh-actions
Migrated to NUKE
2023-04-03 11:35:20 +02:00
Florian Rappl
05ac4a1886 Merge branch 'main' of https://github.com/ElectronNET/Electron.NET into feature/gh-actions 2023-04-03 11:29:48 +02:00
Gregor Biswanger
33ac4edbe3 Merge pull request #758 from cosmo0/patch-1
Fixes issue #735 - paths are URL escaped
2023-04-03 11:29:26 +02:00
Florian Rappl
e5f9bae64f Merge branch 'main' of https://github.com/ElectronNET/Electron.NET into feature/gh-actions 2023-04-03 11:29:22 +02:00
Gregor Biswanger
437404d6cc Merge pull request #755 from Yuvix25/main
Change WebPreferences.ZoomFactor from int to double
2023-04-03 11:25:21 +02:00
cosmo0
1ae2f1de93 Fixes issue #735 - paths are URL escaped 2023-04-03 10:14:43 +02:00
Florian Rappl
6bfd0c33af Changed permission to execute on Linux 2023-04-03 10:03:59 +02:00
Florian Rappl
6311d55a75 Final cleanup 2023-04-03 09:53:56 +02:00
Florian Rappl
7b522c1779 Adjusted for NUKE 2023-04-03 08:43:42 +02:00
Florian Rappl
b1c08f5865 Prepare for NUKE 2023-04-03 07:47:34 +02:00
Yuval Rosen
a3f19055b9 Change WebPreferences.ZoomFactor from int to double 2023-04-02 15:12:23 +03:00
Florian Rappl
ef9a95d9e9 Removed legacy scripts 2023-04-01 23:44:33 +02:00
Florian Rappl
2367035acd Moved into src folder 2023-04-01 23:44:25 +02:00
Florian Rappl
3470a70572 Removed Travis 2023-04-01 23:44:03 +02:00
Gregor Biswanger
1365918efd Merge pull request #749 from ElectronNET/feature/readme
README Improvements
2023-03-30 12:01:07 +02:00
Florian Rappl
e909de54af Some README cleanup and improvements 2023-03-30 09:38:19 +02:00
Robert Muehsig
a2514ed5bc build script updated 2023-03-27 20:04:37 +02:00
Gregor Biswanger
b453278803 Update to new Electron.NET 23.6.1 2023-03-24 14:16:14 +01:00
Gregor Biswanger
a244382383 Add workaround for web-socket communication 2023-03-24 14:15:55 +01:00
Gregor Biswanger
a82e714ef8 Update Changelog für Electron.NET 23.6.1 2023-03-24 13:27:21 +01:00
Gregor Biswanger
b339485fdc Change signature of PrintToPDFOptions 2023-03-24 13:26:15 +01:00
Gregor Biswanger
551635867d Replace deprecated scroll-touch-events with input-event 2023-03-24 01:50:13 +01:00
Gregor Biswanger
941b8cf5c2 Add vscode dev profiles 2023-03-24 01:47:11 +01:00
Gregor Biswanger
06b01f75da Update Demo App to 23.6.1 2023-03-23 20:53:58 +01:00
Gregor Biswanger
e4b1f6586e Update NPM packages 2023-03-23 20:42:42 +01:00
Gregor Biswanger
0657a274d4 Add socket.io client csharp package 2023-03-23 20:29:08 +01:00
Gregor Biswanger
e3acc79c4f Change to .NET 6 build 2023-03-15 23:26:09 +01:00
Gregor Biswanger
73c1d1cd46 Upgrade to .NET 7 2023-02-24 14:56:40 +01:00
514 changed files with 15265 additions and 14820 deletions

View File

@@ -1,84 +0,0 @@
variables:
PackageVersion: 19.0.9.$(Build.BuildId)
projectAPI: './ElectronNET.API/ElectronNET.API.csproj'
projectCLI: './ElectronNET.CLI/ElectronNET.CLI.csproj'
trigger:
- master
pool:
vmImage: windows-latest
steps:
- checkout: self
submodules: true
fetchDepth: 10
- task: NuGetToolInstaller@1
- task: UseDotNet@2
displayName: 'Use .NET Core sdk'
inputs:
packageType: sdk
version: 6.0.100
installationPath: $(Agent.ToolsDirectory)/dotnet
- task: DotNetCoreCLI@2
displayName: 'restore nuget'
inputs:
command: 'restore'
projects: '$(projectAPI)'
- task: DotNetCoreCLI@2
displayName: 'restore nuget'
inputs:
command: 'restore'
projects: '$(projectCLI)'
- task: DotNetCoreCLI@2
inputs:
command: 'build'
projects: '$(projectAPI)'
arguments: '--configuration Release --force /property:Version=$(PackageVersion)'
- task: DotNetCoreCLI@2
inputs:
command: 'build'
projects: '$(projectCLI)'
arguments: '--configuration Release --force /property:Version=$(PackageVersion)'
- task: DotNetCoreCLI@2
inputs:
command: 'pack'
packagesToPack: '$(projectAPI)'
configuration: 'Release'
versioningScheme: 'off'
buildProperties: 'Version=$(PackageVersion)'
arguments: -IncludeReferencedProjects
- task: DotNetCoreCLI@2
inputs:
command: 'pack'
packagesToPack: '$(projectCLI)'
configuration: 'Release'
versioningScheme: 'off'
buildProperties: 'Version=$(PackageVersion)'
- task: NuGetCommand@2
displayName: 'push API to nuget'
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/h5.ElectronNET.API.$(PackageVersion).nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'nuget-curiosity'
- task: NuGetCommand@2
displayName: 'push CLI to nuget'
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/h5.ElectronNET.CLI.$(PackageVersion).nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'nuget-curiosity'

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [GregorBiswanger, FlorianRappl]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username

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

@@ -0,0 +1,47 @@
name: CI
on: [push, pull_request]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
6.0.x
7.0.x
- name: Build
run: ./build.sh
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
6.0.x
7.0.x
- name: Build
run: |
if ($env:GITHUB_REF -eq "refs/heads/main") {
.\build.ps1 -Target Publish
} elseif ($env:GITHUB_REF -eq "refs/heads/develop") {
.\build.ps1 -Target PrePublish
} else {
.\build.ps1
}

5
.gitignore vendored
View File

@@ -44,7 +44,7 @@ dlldata.c
project.lock.json
project.fragment.lock.json
artifacts/
!/artifacts/readme.md
!/artifacts/.gitkeep
*_i.c
*_p.c
@@ -263,3 +263,6 @@ __pycache__/
# Mac Only settings file
.DS_Store
# Nuke build tool
.nuke/temp

144
.nuke/build.schema.json Normal file
View File

@@ -0,0 +1,144 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Build Schema",
"$ref": "#/definitions/build",
"definitions": {
"build": {
"type": "object",
"properties": {
"Configuration": {
"type": "string",
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
"enum": [
"Debug",
"Release"
]
},
"Continue": {
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
"Help": {
"type": "boolean",
"description": "Shows the help text for this build assembly"
},
"Host": {
"type": "string",
"description": "Host for execution. Default is 'automatic'",
"enum": [
"AppVeyor",
"AzurePipelines",
"Bamboo",
"Bitbucket",
"Bitrise",
"GitHubActions",
"GitLab",
"Jenkins",
"Rider",
"SpaceAutomation",
"TeamCity",
"Terminal",
"TravisCI",
"VisualStudio",
"VSCode"
]
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
},
"Partition": {
"type": "string",
"description": "Partition to use on CI"
},
"Plan": {
"type": "boolean",
"description": "Shows the execution plan (HTML)"
},
"Profile": {
"type": "array",
"description": "Defines the profiles to load",
"items": {
"type": "string"
}
},
"ReleaseNotesFilePath": {
"type": "string",
"description": "ReleaseNotesFilePath - To determine the SemanticVersion"
},
"Root": {
"type": "string",
"description": "Root directory during build execution"
},
"Skip": {
"type": "array",
"description": "List of targets to be skipped. Empty list skips all dependencies",
"items": {
"type": "string",
"enum": [
"Clean",
"Compile",
"CompileSample",
"CreatePackages",
"Default",
"ElectronizeCustomWin7TargetSample",
"ElectronizeGenericTargetSample",
"ElectronizeLinuxTargetSample",
"ElectronizeMacOsTargetSample",
"ElectronizeWindowsTargetSample",
"Package",
"PrePublish",
"Publish",
"PublishPackages",
"PublishPreRelease",
"PublishRelease",
"Restore",
"RunUnitTests"
]
}
},
"Solution": {
"type": "string",
"description": "Path to a solution file that is automatically loaded"
},
"Target": {
"type": "array",
"description": "List of targets to be invoked. Default is '{default_target}'",
"items": {
"type": "string",
"enum": [
"Clean",
"Compile",
"CompileSample",
"CreatePackages",
"Default",
"ElectronizeCustomWin7TargetSample",
"ElectronizeGenericTargetSample",
"ElectronizeLinuxTargetSample",
"ElectronizeMacOsTargetSample",
"ElectronizeWindowsTargetSample",
"Package",
"PrePublish",
"Publish",
"PublishPackages",
"PublishPreRelease",
"PublishRelease",
"Restore",
"RunUnitTests"
]
}
},
"Verbosity": {
"type": "string",
"description": "Logging verbosity during build execution. Default is 'Normal'",
"enum": [
"Minimal",
"Normal",
"Quiet",
"Verbose"
]
}
}
}
}
}

4
.nuke/parameters.json Normal file
View File

@@ -0,0 +1,4 @@
{
"$schema": "./build.schema.json",
"Solution": "src/ElectronNET.sln"
}

View File

@@ -1,8 +0,0 @@
language: csharp
mono: none
dist: xenial
dotnet: 3.1
before_script:
- export PATH="$PATH:/home/travis/.dotnet/tools"
script:
- ./buildAll.sh

37
.vscode/tasks.json vendored
View File

@@ -1,21 +1,18 @@
{
"version": "2.0.0",
"command": "dotnet",
"args": [],
"tasks": [
{
"label": "build",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"${workspaceRoot}/ElectronNET.CLI/ElectronNET.CLI.csproj"
],
"problemMatcher": "$msCompile",
"group": {
"_id": "build",
"isDefault": false
}
}
]
}
"version": "2.0.0",
"command": "dotnet",
"args": [],
"tasks": [
{
"label": "build",
"type": "shell",
"command": "dotnet",
"args": ["build", "${workspaceRoot}/ElectronNET.CLI/ElectronNET.CLI.csproj"],
"problemMatcher": "$msCompile",
"group": {
"_id": "build",
"isDefault": false
}
}
]
}

View File

@@ -1,16 +0,0 @@
{
"version": "0.1.0",
"command": "dotnet",
"isShellCommand": true,
"args": [],
"tasks": [
{
"taskName": "build",
"args": [
"${workspaceRoot}/ElectronNET.CLI/ElectronNET.CLI.csproj"
],
"isBuildCommand": true,
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -1,28 +1,43 @@
# Not released
# 23.6.2
# 18.6.1
## ElectronNET.API
ElectronNET.CLI:
* Fixed escaping of URL (#735) @cosmo0
* Fixed huge memory waste in IpcMain.Once (#833) @Yuvix25
* Fixed enabling of accessibility support (#798) @dlanorok
* Updated `Display` to current spec (#828) @Yuvix25
* Changed `ZoomFactor` type from `int` to `double` (#754) @Yuvix25
* Added splash screen size config (#822) @NickRimmer
* Added OSX ARM architecture detection (#821) @sajmonr
* Added support for `did-navigate` event (#819) @NickRimmer
* Added support for `will-redirect` event (#819) @NickRimmer
* Added support for `did-fail-load` event (#819) @NickRimmer
* Added support for `did-start-navigation` event (#819) @NickRimmer
* Added support for `did-redirect-navigation` event (#819) @NickRimmer
* Added support for `dom-ready` event (#813) @softworkz
* New Feature: Support for additional dotnet publish flags (thanks [danatcofo](https://github.com/danatcofo)) [\#655](https://github.com/ElectronNET/Electron.NET/pull/655)
* New Feature: Support Apple Silicon Natively (thanks [bman46](https://github.com/bman46)) [\#624](https://github.com/ElectronNET/Electron.NET/pull/624)
## ElectronNET.CLI
ElectronNET.API:
* (none)
* New Feature: Support for .NET 6 (thanks [danatcofo](https://github.com/danatcofo)) [\#636](https://github.com/ElectronNET/Electron.NET/pull/636)
* New Feature: Switch to async socket lib (thanks [theolivenbaum](https://github.com/theolivenbaum)) [\#595](https://github.com/ElectronNET/Electron.NET/pull/595)
* New Feature: Conversion to use ImageSharp rather than System.Drawing.Common (thanks [danatcofo](https://github.com/danatcofo)) [\#658](https://github.com/ElectronNET/Electron.NET/pull/658)
* New Feature: Support DI and Mocking better + Support launching app with file for win and linux (thanks [danatcofo](https://github.com/danatcofo)) [\#656](https://github.com/ElectronNET/Electron.NET/pull/656)
* New Feature: Support launching app with file for win and linux (thanks [schaveyt](https://github.com/schaveyt)) [\#648](https://github.com/ElectronNET/Electron.NET/pull/648)
* New Feature: Add ability to set a window's parent using BrowserWindowOptions (thanks [MutatedGamer](https://github.com/MutatedGamer)) [\#673](https://github.com/ElectronNET/Electron.NET/pull/673)
* New Feature: changed the processing of loadUrl at CreateWindowAsync (thanks [yannikHoeflich](https://github.com/yannikHoeflich)) [\#631](https://github.com/ElectronNET/Electron.NET/pull/631)
* New Feature: Recent Document Support for MacOS (thanks [danatcofo](https://github.com/danatcofo)) [\#634](https://github.com/ElectronNET/Electron.NET/pull/634)
* New Feature: Support DI and Mocking better (thanks [danatcofo](https://github.com/danatcofo)) [\#633](https://github.com/ElectronNET/Electron.NET/pull/633)
* New Feature: Allow ignoring certificate errors (thanks [javierlarota](https://github.com/javierlarota)) [\#626](https://github.com/ElectronNET/Electron.NET/pull/626)
* New Feature: Log errors in the dotnet process (thanks [Meberem](https://github.com/Meberem)) [\#592](https://github.com/ElectronNET/Electron.NET/pull/592)
* Fixed bug: Error on reloading a window after a second window is closed #664 (thanks [danatcofo](https://github.com/danatcofo)) [\#668](https://github.com/ElectronNET/Electron.NET/pull/668)
## Infrastructure
# Released
* Changed build system to NUKE (#757) @FlorianRappl
* Updated target framework for host project (#753) @r-pankevicius
* Fixed typo in README (#800) @franpersanchez
# 23.6.1
## ElectronNET.CLI
* New Feature: Upgrade to .NET 6 support
## ElectronNET.API
* New Feature: Native Electron 23.2.0 support, but not all new API features included (we search contributors)
* New Feature: Upgrade to .NET 6 support
* New Feature: Changed Web-Socket .NET Library to [SocketIOClient](https://github.com/doghappy/socket.io-client-csharp)
* Breaking Changes: We removed deprecated API events/methods from ElectronNET.API [(More Details)](https://www.electronjs.org/docs/latest/breaking-changes)
# 13.5.1

View File

@@ -1,474 +0,0 @@
using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectronNET.API.Interfaces;
using ElectronNET.API;
namespace ElectronNET.API
{
/// <summary>
/// Enable apps to automatically update themselves. Based on electron-updater.
/// </summary>
public sealed class AutoUpdater : IAutoUpdater
{
/// <summary>
/// Whether to automatically download an update when it is found. (Default is true)
/// </summary>
public Task<bool> IsAutoDownloadEnabledAsync => BridgeConnector.OnResult<bool>("autoUpdater-autoDownload-get", "autoUpdater-autoDownload-get-reply");
/// <summary>
/// Whether to automatically install a downloaded update on app quit (if `QuitAndInstall` was not called before).
///
/// Applicable only on Windows and Linux.
/// </summary>
public Task<bool> IsAutoInstallOnAppQuitEnabledAsync => BridgeConnector.OnResult<bool>("autoUpdater-autoInstallOnAppQuit-get", "autoUpdater-autoInstallOnAppQuit-get-reply");
/// <summary>
/// *GitHub provider only.* Whether to allow update to pre-release versions.
/// Defaults to "true" if application version contains prerelease components (e.g. "0.12.1-alpha.1", here "alpha" is a prerelease component), otherwise "false".
///
/// If "true", downgrade will be allowed("allowDowngrade" will be set to "true").
/// </summary>
public Task<bool> IsAllowPrereleaseEnabledAsync => BridgeConnector.OnResult<bool>("autoUpdater-allowPrerelease-get", "autoUpdater-allowPrerelease-get-reply");
/// <summary>
/// *GitHub provider only.*
/// Get all release notes (from current version to latest), not just the latest (Default is false).
/// </summary>
public Task<bool> IsFullChangeLogEnabledAsync => BridgeConnector.OnResult<bool>("autoUpdater-fullChangelog-get", "autoUpdater-fullChangelog-get-reply");
public Task<bool> IsAllowDowngradeEnabledAsync => BridgeConnector.OnResult<bool>("autoUpdater-allowDowngrade-get", "autoUpdater-allowDowngrade-get-reply");
/// <summary>
/// Whether to automatically download an update when it is found. (Default is true)
/// </summary>
public bool AutoDownload
{
set
{
BridgeConnector.Emit("autoUpdater-autoDownload-set", value);
}
}
/// <summary>
/// Whether to automatically install a downloaded update on app quit (if `QuitAndInstall` was not called before).
///
/// Applicable only on Windows and Linux.
/// </summary>
public bool AutoInstallOnAppQuit
{
set
{
BridgeConnector.Emit("autoUpdater-autoInstallOnAppQuit-set", value);
}
}
/// <summary>
/// *GitHub provider only.* Whether to allow update to pre-release versions.
/// Defaults to "true" if application version contains prerelease components (e.g. "0.12.1-alpha.1", here "alpha" is a prerelease component), otherwise "false".
///
/// If "true", downgrade will be allowed("allowDowngrade" will be set to "true").
/// </summary>
public bool AllowPrerelease
{
set
{
BridgeConnector.Emit("autoUpdater-allowPrerelease-set", value);
}
}
/// <summary>
/// *GitHub provider only.*
/// Get all release notes (from current version to latest), not just the latest (Default is false).
/// </summary>
public bool FullChangelog
{
set
{
BridgeConnector.Emit("autoUpdater-fullChangelog-set", value);
}
}
/// <summary>
/// Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel).
/// Taken in account only if channel differs (pre-release version component in terms of semantic versioning).
/// Default is false.
/// </summary>
public bool AllowDowngrade
{
set
{
BridgeConnector.Emit("autoUpdater-allowDowngrade-set", value);
}
}
/// <summary>
/// For test only.
/// </summary>
public Task<string> GetUpdateConfigPathAsync => BridgeConnector.OnResult<string>("autoUpdater-updateConfigPath-get", "autoUpdater-updateConfigPath-get-reply");
/// <summary>
/// The current application version
/// </summary>
public Task<SemVer> GetCurrentVersionAsync => BridgeConnector.OnResult<SemVer>("autoUpdater-updateConcurrentVersionfigPath-get", "autoUpdater-currentVersion-get-reply");
/// <summary>
/// Get the update channel. Not applicable for GitHub.
/// Doesnt return channel from the update configuration, only if was previously set.
/// </summary>
public Task<string> GetChannelAsync => BridgeConnector.OnResult<string>("autoUpdater-channel-get", "autoUpdater-channel-get-reply");
/// <summary>
/// The request headers.
/// </summary>
public Task<Dictionary<string, string>> GetRequestHeadersAsync => BridgeConnector.OnResult<Dictionary<string, string>>("autoUpdater-requestHeaders-get", "autoUpdater-requestHeaders-get-reply");
/// <summary>
/// The request headers.
/// </summary>
public Dictionary<string, string> RequestHeaders
{
set
{
BridgeConnector.Emit("autoUpdater-requestHeaders-set", value);
}
}
/// <summary>
/// Emitted when there is an error while updating.
/// </summary>
public event Action<string> OnError
{
add
{
if (_error == null)
{
BridgeConnector.On<string>("autoUpdater-error" + GetHashCode(), (message) =>
{
_error(message.ToString());
});
BridgeConnector.Emit("register-autoUpdater-error-event", GetHashCode());
}
_error += value;
}
remove
{
_error -= value;
if (_error == null)
BridgeConnector.Off("autoUpdater-error" + GetHashCode());
}
}
private event Action<string> _error;
/// <summary>
/// Emitted when checking if an update has started.
/// </summary>
public event Action OnCheckingForUpdate
{
add
{
if (_checkingForUpdate == null)
{
BridgeConnector.On("autoUpdater-checking-for-update" + GetHashCode(), () =>
{
_checkingForUpdate();
});
BridgeConnector.Emit("register-autoUpdater-checking-for-update-event", GetHashCode());
}
_checkingForUpdate += value;
}
remove
{
_checkingForUpdate -= value;
if (_checkingForUpdate == null)
BridgeConnector.Off("autoUpdater-checking-for-update" + GetHashCode());
}
}
private event Action _checkingForUpdate;
/// <summary>
/// Emitted when there is an available update.
/// The update is downloaded automatically if AutoDownload is true.
/// </summary>
public event Action<UpdateInfo> OnUpdateAvailable
{
add
{
if (_updateAvailable == null)
{
BridgeConnector.On<UpdateInfo>("autoUpdater-update-available" + GetHashCode(), (updateInfo) =>
{
_updateAvailable(updateInfo);
});
BridgeConnector.Emit("register-autoUpdater-update-available-event", GetHashCode());
}
_updateAvailable += value;
}
remove
{
_updateAvailable -= value;
if (_updateAvailable == null)
BridgeConnector.Off("autoUpdater-update-available" + GetHashCode());
}
}
private event Action<UpdateInfo> _updateAvailable;
/// <summary>
/// Emitted when there is no available update.
/// </summary>
public event Action<UpdateInfo> OnUpdateNotAvailable
{
add
{
if (_updateNotAvailable == null)
{
BridgeConnector.On<UpdateInfo>("autoUpdater-update-not-available" + GetHashCode(), (updateInfo) =>
{
_updateNotAvailable(updateInfo);
});
BridgeConnector.Emit("register-autoUpdater-update-not-available-event", GetHashCode());
}
_updateNotAvailable += value;
}
remove
{
_updateNotAvailable -= value;
if (_updateNotAvailable == null)
BridgeConnector.Off("autoUpdater-update-not-available" + GetHashCode());
}
}
private event Action<UpdateInfo> _updateNotAvailable;
/// <summary>
/// Emitted on download progress.
/// </summary>
public event Action<ProgressInfo> OnDownloadProgress
{
add
{
if (_downloadProgress == null)
{
BridgeConnector.On<ProgressInfo>("autoUpdater-download-progress" + GetHashCode(), (progressInfo) =>
{
_downloadProgress(progressInfo);
});
BridgeConnector.Emit("register-autoUpdater-download-progress-event", GetHashCode());
}
_downloadProgress += value;
}
remove
{
_downloadProgress -= value;
if (_downloadProgress == null)
BridgeConnector.Off("autoUpdater-download-progress" + GetHashCode());
}
}
private event Action<ProgressInfo> _downloadProgress;
/// <summary>
/// Emitted on download complete.
/// </summary>
public event Action<UpdateInfo> OnUpdateDownloaded
{
add
{
if (_updateDownloaded == null)
{
BridgeConnector.On<UpdateInfo>("autoUpdater-update-downloaded" + GetHashCode(), (updateInfo) =>
{
_updateDownloaded(updateInfo);
});
BridgeConnector.Emit("register-autoUpdater-update-downloaded-event", GetHashCode());
}
_updateDownloaded += value;
}
remove
{
_updateDownloaded -= value;
if (_updateDownloaded == null)
BridgeConnector.Off("autoUpdater-update-downloaded" + GetHashCode());
}
}
private event Action<UpdateInfo> _updateDownloaded;
private static AutoUpdater _autoUpdater;
private static readonly object _syncRoot = new();
internal AutoUpdater() { }
internal static AutoUpdater Instance
{
get
{
if (_autoUpdater == null)
{
lock (_syncRoot)
{
if (_autoUpdater == null)
{
_autoUpdater = new AutoUpdater();
}
}
}
return _autoUpdater;
}
}
/// <summary>
/// Asks the server whether there is an update.
/// </summary>
/// <returns></returns>
public Task<UpdateCheckResult> CheckForUpdatesAsync()
{
var taskCompletionSource = new TaskCompletionSource<UpdateCheckResult>(TaskCreationOptions.RunContinuationsAsynchronously);
string guid = Guid.NewGuid().ToString();
BridgeConnector.On<UpdateCheckResult>("autoUpdaterCheckForUpdatesComplete" + guid, (updateCheckResult) =>
{
try
{
BridgeConnector.Off("autoUpdaterCheckForUpdatesComplete" + guid);
BridgeConnector.Off("autoUpdaterCheckForUpdatesError" + guid);
taskCompletionSource.SetResult(updateCheckResult);
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
});
BridgeConnector.On<string>("autoUpdaterCheckForUpdatesError" + guid, (error) =>
{
BridgeConnector.Off("autoUpdaterCheckForUpdatesComplete" + guid);
BridgeConnector.Off("autoUpdaterCheckForUpdatesError" + guid);
string message = "An error occurred in CheckForUpdatesAsync";
if (error != null && !string.IsNullOrEmpty(error.ToString()))
message = JsonConvert.SerializeObject(error);
taskCompletionSource.SetException(new Exception(message));
});
BridgeConnector.Emit("autoUpdaterCheckForUpdates", guid);
return taskCompletionSource.Task;
}
/// <summary>
/// Asks the server whether there is an update.
///
/// This will immediately download an update, then install when the app quits.
/// </summary>
/// <returns></returns>
public Task<UpdateCheckResult> CheckForUpdatesAndNotifyAsync()
{
var taskCompletionSource = new TaskCompletionSource<UpdateCheckResult>(TaskCreationOptions.RunContinuationsAsynchronously);
string guid = Guid.NewGuid().ToString();
BridgeConnector.On<UpdateCheckResult>("autoUpdaterCheckForUpdatesAndNotifyComplete" + guid, (updateCheckResult) =>
{
try
{
BridgeConnector.Off("autoUpdaterCheckForUpdatesAndNotifyComplete" + guid);
BridgeConnector.Off("autoUpdaterCheckForUpdatesAndNotifyError" + guid);
if (updateCheckResult == null)
taskCompletionSource.SetResult(null);
else
taskCompletionSource.SetResult(updateCheckResult);
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
});
BridgeConnector.On<string>("autoUpdaterCheckForUpdatesAndNotifyError" + guid, (error) =>
{
BridgeConnector.Off("autoUpdaterCheckForUpdatesAndNotifyComplete" + guid);
BridgeConnector.Off("autoUpdaterCheckForUpdatesAndNotifyError" + guid);
string message = "An error occurred in autoUpdaterCheckForUpdatesAndNotify";
if (error != null)
message = JsonConvert.SerializeObject(error);
taskCompletionSource.SetException(new Exception(message));
});
BridgeConnector.Emit("autoUpdaterCheckForUpdatesAndNotify", guid);
return taskCompletionSource.Task;
}
/// <summary>
/// Restarts the app and installs the update after it has been downloaded.
/// It should only be called after `update-downloaded` has been emitted.
///
/// Note: QuitAndInstall() will close all application windows first and only emit `before-quit` event on `app` after that.
/// This is different from the normal quit event sequence.
/// </summary>
/// <param name="isSilent">*windows-only* Runs the installer in silent mode. Defaults to `false`.</param>
/// <param name="isForceRunAfter">Run the app after finish even on silent install. Not applicable for macOS. Ignored if `isSilent` is set to `false`.</param>
public void QuitAndInstall(bool isSilent = false, bool isForceRunAfter = false)
{
BridgeConnector.EmitSync("prepare-for-update");
BridgeConnector.EmitSync("autoUpdaterQuitAndInstall", isSilent, isForceRunAfter);
}
/// <summary>
/// Start downloading update manually. You can use this method if "AutoDownload" option is set to "false".
/// </summary>
/// <returns>Path to downloaded file.</returns>
public Task<string> DownloadUpdateAsync()
{
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
string guid = Guid.NewGuid().ToString();
BridgeConnector.On<string>("autoUpdaterDownloadUpdateComplete" + guid, (downloadedPath) =>
{
BridgeConnector.Off("autoUpdaterDownloadUpdateComplete" + guid);
taskCompletionSource.SetResult(downloadedPath.ToString());
});
BridgeConnector.Emit("autoUpdaterDownloadUpdate", guid);
return taskCompletionSource.Task;
}
/// <summary>
/// Feed URL.
/// </summary>
/// <returns>Feed URL.</returns>
public Task<string> GetFeedURLAsync()
{
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
string guid = Guid.NewGuid().ToString();
BridgeConnector.On<string>("autoUpdaterGetFeedURLComplete" + guid, (downloadedPath) =>
{
BridgeConnector.Off("autoUpdaterGetFeedURLComplete" + guid);
taskCompletionSource.SetResult(downloadedPath.ToString());
});
BridgeConnector.Emit("autoUpdaterGetFeedURL", guid);
return taskCompletionSource.Task;
}
}
}

View File

@@ -1,551 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Nito.AsyncEx;
using SocketIOClient;
using SocketIOClient.Newtonsoft.Json;
namespace ElectronNET.API
{
internal static class BridgeConnector
{
internal static class EventTasks<T>
{
//Although SocketIO already manage event handlers, we need to manage this here as well for the OnResult calls,
//because SocketIO will simply replace the existing event handler on every call to On(key, ...) , which means there is
//a race condition between On / Off calls that can lead to tasks deadlocking forever without ever triggering their On handler
private static readonly Dictionary<string, TaskCompletionSource<T>> _taskCompletionSources = new();
private static readonly Dictionary<string, string> _eventKeys = new();
private static readonly object _lock = new();
/// <summary>
/// Get or add a new TaskCompletionSource<typeparamref name="T"/> for a given event key
/// </summary>
/// <param name="key"></param>
/// <param name="eventKey"></param>
/// <param name="taskCompletionSource"></param>
/// <param name="waitThisFirstAndThenTryAgain"></param>
/// <returns>Returns true if a new TaskCompletionSource<typeparamref name="T"/> was added to the dictionary</returns>
internal static bool TryGetOrAdd(string key, string eventKey, out TaskCompletionSource<T> taskCompletionSource, out Task waitThisFirstAndThenTryAgain)
{
lock (_lock)
{
if (!_taskCompletionSources.TryGetValue(key, out taskCompletionSource))
{
taskCompletionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
_taskCompletionSources[key] = taskCompletionSource;
_eventKeys[key] = eventKey;
waitThisFirstAndThenTryAgain = null;
return true; //Was added, so we need to also register the socket events
}
if (_eventKeys.TryGetValue(key, out var existingEventKey) && existingEventKey == eventKey)
{
waitThisFirstAndThenTryAgain = null;
return false; //No need to register the socket events twice
}
waitThisFirstAndThenTryAgain = taskCompletionSource.Task; //Will need to try again after the previous existing one is done
taskCompletionSource = null;
return true; //Need to register the socket events, but must first await the previous task to complete
}
}
/// <summary>
/// Clean up the TaskCompletionSource<typeparamref name="T"/> from the dictionary if and only if it is the same as the passed argument
/// </summary>
/// <param name="key"></param>
/// <param name="eventKey"></param>
/// <param name="taskCompletionSource"></param>
internal static void DoneWith(string key, string eventKey, TaskCompletionSource<T> taskCompletionSource)
{
lock (_lock)
{
if (_taskCompletionSources.TryGetValue(key, out var existingTaskCompletionSource)
&& ReferenceEquals(existingTaskCompletionSource, taskCompletionSource))
{
_taskCompletionSources.Remove(key);
}
if (_eventKeys.TryGetValue(key, out var existingEventKey) && existingEventKey == eventKey)
{
_eventKeys.Remove(key);
}
}
}
}
private static SocketIO _socket;
private static readonly object _syncRoot = new();
private static readonly SemaphoreSlim _socketSemaphoreEmit = new(1, 1);
private static readonly SemaphoreSlim _socketSemaphoreHandlers = new(1, 1);
private static AsyncManualResetEvent _connectedSocketEvent = new AsyncManualResetEvent();
private static Dictionary<string, Action<SocketIOResponse>> _eventHandlers = new();
private static Task<SocketIO> _waitForConnection
{
get
{
EnsureSocketTaskIsCreated();
return GetSocket();
}
}
private static async Task<SocketIO> GetSocket()
{
await _connectedSocketEvent.WaitAsync();
return _socket;
}
public static bool IsConnected => _waitForConnection is Task task && task.IsCompletedSuccessfully;
public static void Emit(string eventString, params object[] args)
{
//We don't care about waiting for the event to be emitted, so this doesn't need to be async
Task.Run(() => EmitAsync(eventString, args));
}
private static async Task EmitAsync(string eventString, object[] args)
{
if (App.SocketDebug)
{
Log("Sending event {0}", eventString);
}
var socket = await _waitForConnection;
await _socketSemaphoreEmit.WaitAsync();
try
{
await socket.EmitAsync(eventString, args);
}
finally
{
_socketSemaphoreEmit.Release();
}
if (App.SocketDebug)
{
Log($"Sent event {eventString}");
}
}
/// <summary>
/// This method is only used on places where we need to be sure the event was sent on the socket, such as Quit, Exit, Relaunch and QuitAndInstall methods
/// </summary>
/// <param name="eventString"></param>
/// <param name="args"></param>
internal static void EmitSync(string eventString, params object[] args)
{
if (App.SocketDebug)
{
Log("Sending event {0}", eventString);
}
Task.Run(async () =>
{
var socket = await _waitForConnection;
try
{
await _socketSemaphoreEmit.WaitAsync();
await socket.EmitAsync(eventString, args);
}
finally
{
_socketSemaphoreEmit.Release();
}
}).Wait();
if (App.SocketDebug)
{
Log("Sent event {0}", eventString);
}
}
public static void Off(string eventString)
{
EnsureSocketTaskIsCreated();
_socketSemaphoreHandlers.Wait();
try
{
if (_eventHandlers.ContainsKey(eventString))
{
_eventHandlers.Remove(eventString);
}
_socket.Off(eventString);
}
finally
{
_socketSemaphoreHandlers.Release();
}
}
public static void On(string eventString, Action fn)
{
EnsureSocketTaskIsCreated();
_socketSemaphoreHandlers.Wait();
try
{
if (_eventHandlers.ContainsKey(eventString))
{
_eventHandlers.Remove(eventString);
}
_eventHandlers.Add(eventString, _ =>
{
try
{
fn();
}
catch (Exception E)
{
LogError(E, "Error running handler for event {0}", eventString);
}
});
_socket.On(eventString, _eventHandlers[eventString]);
}
finally
{
_socketSemaphoreHandlers.Release();
}
}
public static void On<T>(string eventString, Action<T> fn)
{
EnsureSocketTaskIsCreated();
_socketSemaphoreHandlers.Wait();
try
{
if (_eventHandlers.ContainsKey(eventString))
{
_eventHandlers.Remove(eventString);
}
_eventHandlers.Add(eventString, o =>
{
try
{
fn(o.GetValue<T>(0));
}
catch (Exception E)
{
LogError(E, "Error running handler for event {0}", eventString);
}
});
_socket.On(eventString, _eventHandlers[eventString]);
}
finally
{
_socketSemaphoreHandlers.Release();
}
}
private static void RehookHandlers(SocketIO newSocket)
{
_socketSemaphoreHandlers.Wait();
try
{
foreach (var kv in _eventHandlers)
{
newSocket.On(kv.Key, kv.Value);
}
}
finally
{
_socketSemaphoreHandlers.Release();
}
}
public static void Once<T>(string eventString, Action<T> fn)
{
On<T>(eventString, (o) =>
{
Off(eventString);
fn(o);
});
}
public static async Task<T> OnResult<T>(string triggerEvent, string completedEvent, params object[] args)
{
string eventKey = completedEvent;
if (args is object && args.Length > 0) // If there are arguments passed, we generate a unique event key with the arguments
// this allow us to wait for previous events first before registering new ones
{
var hash = new HashCode();
foreach (var obj in args)
{
hash.Add(obj);
}
eventKey = $"{eventKey}-{(uint)hash.ToHashCode()}";
}
if (EventTasks<T>.TryGetOrAdd(completedEvent, eventKey, out var taskCompletionSource, out var waitThisFirstAndThenTryAgain))
{
if (waitThisFirstAndThenTryAgain is object)
{
//There was a pending call with different parameters, so we need to wait that first and then call here again
try
{
await waitThisFirstAndThenTryAgain;
}
catch
{
//Ignore any exceptions here so we can set a new event below
//The exception will also be visible to the original first caller due to taskCompletionSource.Task
}
//Try again to set the event
return await OnResult<T>(triggerEvent, completedEvent, args);
}
else
{
//A new TaskCompletionSource was added, so we need to register the completed event here
On<T>(completedEvent, (result) =>
{
Off(completedEvent);
taskCompletionSource.SetResult(result);
EventTasks<T>.DoneWith(completedEvent, eventKey, taskCompletionSource);
});
await EmitAsync(triggerEvent, args);
}
}
return await taskCompletionSource.Task;
}
public static async Task<T> OnResult<T>(string triggerEvent, string completedEvent, CancellationToken cancellationToken, params object[] args)
{
string eventKey = completedEvent;
if (args is object && args.Length > 0) // If there are arguments passed, we generate a unique event key with the arguments
// this allow us to wait for previous events first before registering new ones
{
var hash = new HashCode();
foreach (var obj in args)
{
hash.Add(obj);
}
eventKey = $"{eventKey}-{(uint)hash.ToHashCode()}";
}
if (EventTasks<T>.TryGetOrAdd(completedEvent, eventKey, out var taskCompletionSource, out var waitThisFirstAndThenTryAgain))
{
if (waitThisFirstAndThenTryAgain is object)
{
//There was a pending call with different parameters, so we need to wait that first and then call here again
try
{
await Task.Run(() => waitThisFirstAndThenTryAgain, cancellationToken);
}
catch
{
//Ignore any exceptions here so we can set a new event below
//The exception will also be visible to the original first caller due to taskCompletionSource.Task
}
//Try again to set the event
return await OnResult<T>(triggerEvent, completedEvent, cancellationToken, args);
}
else
{
using (cancellationToken.Register(() => taskCompletionSource.TrySetCanceled()))
{
//A new TaskCompletionSource was added, so we need to register the completed event here
On<T>(completedEvent, (result) =>
{
Off(completedEvent);
taskCompletionSource.SetResult(result);
EventTasks<T>.DoneWith(completedEvent, eventKey, taskCompletionSource);
});
Emit(triggerEvent, args);
}
}
}
return await taskCompletionSource.Task;
}
internal static void Log(string formatString, params object[] args)
{
if (Logger is object)
{
Logger.LogInformation(formatString, args);
}
else
{
Console.WriteLine(formatString, args);
}
}
internal static void LogError(Exception E, string formatString, params object[] args)
{
if (Logger is object)
{
Logger.LogError(E, formatString, args);
}
else
{
Console.WriteLine(formatString, args);
Console.WriteLine(E.ToString());
}
}
private static Thread _backgroundMonitorThread;
private static void EnsureSocketTaskIsCreated()
{
if (_socket is null)
{
if (string.IsNullOrWhiteSpace(AuthKey))
{
throw new Exception("You must call Electron.ReadAuth() first thing on your main entry point.");
}
if (HybridSupport.IsElectronActive)
{
lock (_syncRoot)
{
if (_socket is null)
{
if (HybridSupport.IsElectronActive)
{
var socket = new SocketIO($"http://localhost:{BridgeSettings.SocketPort}", new SocketIOOptions()
{
EIO = 4,
Reconnection = true,
ReconnectionAttempts = int.MaxValue,
ReconnectionDelay = 500,
ReconnectionDelayMax = 2000,
RandomizationFactor = 0.5,
ConnectionTimeout = TimeSpan.FromSeconds(10),
Transport = SocketIOClient.Transport.TransportProtocol.WebSocket
});
socket.JsonSerializer = new CamelCaseNewtonsoftJsonSerializer();
_connectedSocketEvent.Reset();
socket.OnConnected += (_, __) =>
{
Task.Run(async () =>
{
await socket.EmitAsync("auth", AuthKey);
_connectedSocketEvent.Set();
Log("ElectronNET socket {1} connected on port {0}!", BridgeSettings.SocketPort, socket.Id);
});
};
socket.OnReconnectAttempt += (_, __) =>
{
_connectedSocketEvent.Reset();
Log("ElectronNET socket {1} is trying to reconnect on port {0}...", BridgeSettings.SocketPort, socket.Id);
};
socket.OnReconnectError += (_, ex) =>
{
_connectedSocketEvent.Reset();
Log("ElectronNET socket {1} failed to connect {0}", ex, socket.Id);
};
socket.OnReconnectFailed += (_, ex) =>
{
_connectedSocketEvent.Reset();
Log("ElectronNET socket {1} failed to reconnect {0}", ex, socket.Id);
};
socket.OnReconnected += (_, __) =>
{
_connectedSocketEvent.Set();
Log("ElectronNET socket {1} reconnected on port {0}...", BridgeSettings.SocketPort, socket.Id);
};
socket.OnDisconnected += (_, reason) =>
{
_connectedSocketEvent.Reset();
Log("ElectronNET socket {2} disconnected with reason {0}, trying to reconnect on port {1}!", reason, BridgeSettings.SocketPort, socket.Id);
};
socket.OnError += (_, msg) =>
{
//_connectedSocketEvent.Reset();
Log("ElectronNET socket {1} error: {0}...", msg, socket.Id);
};
_socket = socket;
Task.Run(async () =>
{
try
{
await socket.ConnectAsync();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
if (!App.TryRaiseOnSocketConnectFail())
{
Environment.Exit(0xDEAD);
}
}
});
RehookHandlers(socket);
}
else
{
throw new Exception("Missing Socket Port");
}
}
}
}
else
{
throw new Exception("Missing Socket Port");
}
}
}
internal static ILogger<App> Logger { private get; set; }
internal static string AuthKey { get; set; } = null;
private class CamelCaseNewtonsoftJsonSerializer : NewtonsoftJsonSerializer
{
public CamelCaseNewtonsoftJsonSerializer() : base()
{
OptionsProvider = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
};
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +0,0 @@
using System.Threading.Tasks;
using ElectronNET.API.Entities;
using Newtonsoft.Json;
namespace ElectronNET.API
{
public sealed class DesktopCapturer
{
private static readonly object _syncRoot = new();
private static DesktopCapturer _desktopCapturer;
internal DesktopCapturer() { }
internal static DesktopCapturer Instance
{
get
{
if (_desktopCapturer == null)
{
lock (_syncRoot)
{
if (_desktopCapturer == null)
{
_desktopCapturer = new DesktopCapturer();
}
}
}
return _desktopCapturer;
}
}
public async Task<DesktopCapturerSource[]> GetSourcesAsync(SourcesOption option)
{
return await BridgeConnector.OnResult<DesktopCapturerSource[]>("desktop-capturer-get-sources", "desktop-capturer-get-sources-result", option);
}
}
}

View File

@@ -1,144 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ElectronNET.API
{
public static partial class Electron
{
/// <summary>
/// Experimental code, use with care
/// </summary>
public static class Experimental
{
/// <summary>
/// Starts electron from C#, use during development to avoid having to fully publish / build your app on every compile cycle
/// You will need to run the CLI at least once (and once per update) to bootstrap all required files
/// </summary>
/// <param name="webPort"></param>
/// <param name="projectPath"></param>
/// <param name="extraElectronArguments"></param>
/// <param name="clearCache"></param>
/// <exception cref="DirectoryNotFoundException"></exception>
/// <exception cref="Exception"></exception>
public static async Task<int> StartElectronForDevelopment(int webPort, string projectPath = null, string[] extraElectronArguments = null, bool clearCache = false)
{
string aspCoreProjectPath;
if (!string.IsNullOrEmpty(projectPath))
{
if (Directory.Exists(projectPath))
{
aspCoreProjectPath = projectPath;
}
else
{
throw new DirectoryNotFoundException(projectPath);
}
}
else
{
aspCoreProjectPath = Directory.GetCurrentDirectory();
}
string tempPath = Path.Combine(aspCoreProjectPath, "obj", "Host");
if (!Directory.Exists(tempPath))
{
Directory.CreateDirectory(tempPath);
}
var mainFileJs = Path.Combine(tempPath, "main.js");
if (!File.Exists(mainFileJs))
{
throw new Exception("You need to run once the electronize-h5 start command to bootstrap the necessary files");
}
var nodeModulesDirPath = Path.Combine(tempPath, "node_modules");
bool runNpmInstall = false;
if (!Directory.Exists(nodeModulesDirPath))
{
runNpmInstall = true;
}
var packagesJson = Path.Combine(tempPath, "package.json");
var packagesPrevious = Path.Combine(tempPath, "package.json.previous");
if (!runNpmInstall)
{
if (File.Exists(packagesPrevious))
{
if (File.ReadAllText(packagesPrevious) != File.ReadAllText(packagesJson))
{
runNpmInstall = true;
}
}
else
{
runNpmInstall = true;
}
}
if (runNpmInstall)
{
throw new Exception("You need to run once the electronize-h5 start command to bootstrap the necessary files");
}
string arguments = "";
if (extraElectronArguments is object)
{
arguments = string.Join(' ', extraElectronArguments);
}
if (clearCache)
{
arguments += " --clear-cache=true";
}
BridgeConnector.AuthKey = Guid.NewGuid().ToString().Replace("-", "");
var socketPort = FreeTcpPort();
arguments += $" --development=true --devauth={BridgeConnector.AuthKey} --devport={socketPort}";
string path = Path.Combine(tempPath, "node_modules", ".bin");
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
ProcessHelper.Execute(@"electron.cmd ""..\..\main.js"" " + arguments, path);
}
else
{
ProcessHelper.Execute(@"./electron ""../../main.js"" " + arguments, path);
}
BridgeSettings.InitializePorts(socketPort, webPort);
await Task.Delay(500);
return socketPort;
}
/// <summary>
/// Return a free local TCP port
/// </summary>
/// <returns></returns>
public static int FreeTcpPort()
{
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
int port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
}
}
}

View File

@@ -1,20 +0,0 @@
using System;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
[Obsolete("Use ImageOptions instead.")]
public class BitmapOptions
{
/// <summary>
/// Gets or sets the scale factor
/// </summary>
public float ScaleFactor { get; set; } = NativeImage.DefaultScaleFactor;
/// <summary>
/// Utility conversion for obsolete class
/// </summary>
public static implicit operator ImageOptions(BitmapOptions o) => new() {ScaleFactor = o.ScaleFactor};
}
}

View File

@@ -1,10 +0,0 @@
namespace ElectronNET.API.Entities
{
public class CookieRemovedResponse
{
public Cookie cookie {get;set;}
public CookieChangedCause cause { get; set; }
public bool removed { get; set; }
}
}

View File

@@ -1,32 +0,0 @@
using System;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
[Obsolete("Use CreateOptions instead")]
public class CreateFromBitmapOptions
{
/// <summary>
/// Gets or sets the width
/// </summary>
public int? Width { get; set; }
/// <summary>
/// Gets or sets the height
/// </summary>
public int? Height { get; set; }
/// <summary>
/// Gets or sets the scalefactor
/// </summary>
public float ScaleFactor { get; set; } = NativeImage.DefaultScaleFactor;
/// <summary>
/// Utility conversion for obsolete class
/// </summary>
public static implicit operator CreateOptions(CreateFromBitmapOptions o) => new()
{Width = o.Width, Height = o.Height, ScaleFactor = o.ScaleFactor};
}
}

View File

@@ -1,31 +0,0 @@
using System;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
[Obsolete("Use CreateOptions instead")]
public class CreateFromBufferOptions
{
/// <summary>
/// Gets or sets the width
/// </summary>
public int? Width { get; set; }
/// <summary>
/// Gets or sets the height
/// </summary>
public int? Height { get; set; }
/// <summary>
/// Gets or sets the scalefactor
/// </summary>
public float ScaleFactor { get; set; } = NativeImage.DefaultScaleFactor;
/// <summary>
/// Utility conversion for obsolete class
/// </summary>
public static implicit operator CreateOptions(CreateFromBufferOptions o) => new()
{Width = o.Width, Height = o.Height, ScaleFactor = o.ScaleFactor};
}
}

View File

@@ -1,15 +0,0 @@
using Newtonsoft.Json;
namespace ElectronNET.API.Entities
{
public sealed class DesktopCapturerSource
{
public string Id { get; set; }
public string Name { get; set; }
public NativeImage Thumbnail { get; set; }
[JsonProperty("display_id")]
public string DisplayId { get; set; }
public NativeImage AppIcon { get; set; }
}
}

View File

@@ -1,65 +0,0 @@
namespace ElectronNET.API.Entities
{
public class DisplayChanged
{
public Display display { get; set; }
public string[] metrics { get; set; }
}
/// <summary>
///
/// </summary>
public class Display
{
/// <summary>
/// Gets or sets the bounds.
/// </summary>
/// <value>
/// The bounds.
/// </value>
public Rectangle Bounds { get; set; }
/// <summary>
/// Unique identifier associated with the display.
/// </summary>
public string Id { get; set; }
/// <summary>
/// Can be 0, 90, 180, 270, represents screen rotation in clock-wise degrees.
/// </summary>
public float Rotation { get; set; }
/// <summary>
/// Output device's pixel scale factor.
/// </summary>
public float ScaleFactor { get; set; }
/// <summary>
/// Gets or sets the size.
/// </summary>
/// <value>
/// The size.
/// </value>
public Size Size { get; set; }
/// <summary>
/// Can be available, unavailable, unknown.
/// </summary>
public string TouchSupport { get; set; }
/// <summary>
/// Gets or sets the work area.
/// </summary>
/// <value>
/// The work area.
/// </value>
public Rectangle WorkArea { get; set; }
/// <summary>
/// Gets or sets the size of the work area.
/// </summary>
/// <value>
/// The size of the work area.
/// </value>
public Size WorkAreaSize { get; set; }
}
}

View File

@@ -1,95 +0,0 @@
using System.Runtime.Versioning;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
public enum OpenDialogProperty
{
/// <summary>
/// The open file
/// </summary>
openFile,
/// <summary>
/// The open directory
/// </summary>
openDirectory,
/// <summary>
/// The multi selections
/// </summary>
multiSelections,
/// <summary>
/// The show hidden files
/// </summary>
showHiddenFiles,
/// <summary>
/// The create directory
/// </summary>
[SupportedOSPlatform("macos")]
createDirectory,
/// <summary>
/// The prompt to create
/// </summary>
[SupportedOSPlatform("windows")]
promptToCreate,
/// <summary>
/// The no resolve aliases
/// </summary>
[SupportedOSPlatform("macos")]
noResolveAliases,
/// <summary>
/// Treat packages, such as .app folders, as a directory instead of a file.
/// </summary>
[SupportedOSPlatform("macos")]
treatPackageAsDirectory,
/// <summary>
/// Don't add the item being opened to recent documents list
/// </summary>
[SupportedOSPlatform("windows")]
dontAddToRecent
}
/// <summary>
///
/// </summary>
public enum SaveDialogProperty
{
/// <summary>
/// The show hidden files
/// </summary>
showHiddenFiles,
/// <summary>
/// The create directory
/// </summary>
[SupportedOSPlatform("macos")]
createDirectory,
/// <summary>
/// Treat packages, such as .app folders, as a directory instead of a file.
/// </summary>
[SupportedOSPlatform("macos")]
treatPackageAsDirectory,
/// <summary>
/// Sets whether the user will be presented a confirmation dialog if the user types a file name that already exists.
/// </summary>
[SupportedOSPlatform("linux")]
showOverwriteConfirmation,
/// <summary>
/// Don't add the item being opened to recent documents list
/// </summary>
[SupportedOSPlatform("windows")]
dontAddToRecent
}
}

View File

@@ -1,35 +0,0 @@
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
public class PrintToPDFOptions
{
/// <summary>
/// Specifies the type of margins to use. Uses 0 for default margin, 1 for no
/// margin, and 2 for minimum margin.
/// </summary>
public int MarginsType { get; set; }
/// <summary>
/// Specify page size of the generated PDF. Can be A3, A4, A5, Legal, Letter,
/// Tabloid or an Object containing height and width in microns.
/// </summary>
public string PageSize { get; set; }
/// <summary>
/// Whether to print CSS backgrounds.
/// </summary>
public bool PrintBackground { get; set; }
/// <summary>
/// Whether to print selection only.
/// </summary>
public bool PrintSelectionOnly { get; set; }
/// <summary>
/// true for landscape, false for portrait.
/// </summary>
public bool Landscape { get; set; }
}
}

View File

@@ -1,10 +0,0 @@
namespace ElectronNET.API
{
/// <summary>
/// An object listing the version strings specific to Electron
/// </summary>
/// <param name="Chrome">Value representing Chrome's version string</param>
/// <param name="Electron">Value representing Electron's version string</param>
/// <returns></returns>
public record ProcessVersions(string Chrome, string Electron);
}

View File

@@ -1,9 +0,0 @@
namespace ElectronNET.API.Entities
{
public class SecondInstanceResponse
{
public string[] args { get; set; }
public string workingDirectory { get;set;}
}
}

View File

@@ -1,9 +0,0 @@
namespace ElectronNET.API.Entities
{
public sealed class SourcesOption
{
public string[] Types { get; set; }
public Size ThumbnailSize { get; set; }
public bool FetchWindowIcons { get; set; }
}
}

View File

@@ -1,20 +0,0 @@
using System;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
[Obsolete("Use ImageOptions instead.")]
public class ToBitmapOptions
{
/// <summary>
/// Gets or sets the scalefactor
/// </summary>
public float ScaleFactor { get; set; } = NativeImage.DefaultScaleFactor;
/// <summary>
/// Utility conversion for obsolete class
/// </summary>
public static implicit operator ImageOptions(ToBitmapOptions o) => new () {ScaleFactor = o.ScaleFactor};
}
}

View File

@@ -1,20 +0,0 @@
using System;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
[Obsolete("Use ImageOptions instead.")]
public class ToDataUrlOptions
{
/// <summary>
/// Gets or sets the scalefactor
/// </summary>
public float ScaleFactor { get; set; } = NativeImage.DefaultScaleFactor;
/// <summary>
/// Utility conversion for obsolete class
/// </summary>
public static implicit operator ImageOptions(ToDataUrlOptions o) => new () {ScaleFactor = o.ScaleFactor};
}
}

View File

@@ -1,20 +0,0 @@
using System;
namespace ElectronNET.API.Entities
{
/// <summary>
///
/// </summary>
[Obsolete("Use ImageOptions instead.")]
public class ToPNGOptions
{
/// <summary>
/// Gets or sets the scalefactor
/// </summary>
public float ScaleFactor { get; set; } = 1.0f;
/// <summary>
/// Utility conversion for obsolete class
/// </summary>
public static implicit operator ImageOptions(ToPNGOptions o) => new () {ScaleFactor = o.ScaleFactor};
}
}

View File

@@ -1,8 +0,0 @@
namespace ElectronNET.API.Entities
{
public class TrayClickEventResponse
{
public TrayClickEventArgs eventArgs { get; set; }
public Rectangle bounds { get; set; }
}
}

View File

@@ -1,91 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Threading.Tasks;
using ElectronNET.API.Interfaces;
namespace ElectronNET.API
{
/// <summary>
/// Allows you to execute native JavaScript/TypeScript code from the host process.
///
/// It is only possible if the Electron.NET CLI has previously added an
/// ElectronHostHook directory:
/// <c>electronize add HostHook</c>
/// </summary>
public sealed class HostHook : IHostHook
{
private static HostHook _electronHostHook;
private static readonly object _syncRoot = new();
readonly string oneCallguid = Guid.NewGuid().ToString();
internal HostHook() { }
internal static HostHook Instance
{
get
{
if (_electronHostHook == null)
{
lock (_syncRoot)
{
if (_electronHostHook == null)
{
_electronHostHook = new HostHook();
}
}
}
return _electronHostHook;
}
}
/// <summary>
/// Execute native JavaScript/TypeScript code.
/// </summary>
/// <param name="socketEventName">Socket name registered on the host.</param>
/// <param name="arguments">Optional parameters.</param>
public void Call(string socketEventName, params dynamic[] arguments)
{
BridgeConnector.On<string>(socketEventName + "Error" + oneCallguid, (result) =>
{
BridgeConnector.Off(socketEventName + "Error" + oneCallguid);
Electron.Dialog.ShowErrorBox("Host Hook Exception", result);
});
BridgeConnector.Emit(socketEventName, arguments, oneCallguid);
}
/// <summary>
/// Execute native JavaScript/TypeScript code.
/// </summary>
/// <typeparam name="T">Results from the executed host code.</typeparam>
/// <param name="socketEventName">Socket name registered on the host.</param>
/// <param name="arguments">Optional parameters.</param>
/// <returns></returns>
public Task<T> CallAsync<T>(string socketEventName, params dynamic[] arguments)
{
var taskCompletionSource = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
string guid = Guid.NewGuid().ToString();
BridgeConnector.On<string>(socketEventName + "Error" + guid, (result) =>
{
BridgeConnector.Off(socketEventName + "Error" + guid);
Electron.Dialog.ShowErrorBox("Host Hook Exception", result);
taskCompletionSource.SetException(new Exception($"Host Hook Exception {result}"));
});
BridgeConnector.On<T>(socketEventName + "Complete" + guid, (result) =>
{
BridgeConnector.Off(socketEventName + "Error" + guid);
BridgeConnector.Off(socketEventName + "Complete" + guid);
taskCompletionSource.SetResult(result);
});
BridgeConnector.Emit(socketEventName, arguments, guid);
return taskCompletionSource.Task;
}
}
}

View File

@@ -1,709 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Control your application's event lifecycle.
/// </summary>
public interface IApp
{
/// <summary>
/// Emitted when all windows have been closed.
/// <para/>
/// If you do not subscribe to this event and all windows are closed, the default behavior is to quit
/// the app; however, if you subscribe, you control whether the app quits or not.If the user pressed
/// Cmd + Q, or the developer called <see cref="Quit"/>, Electron will first try to close all the windows
/// and then emit the <see cref="WillQuit"/> event, and in this case the <see cref="WindowAllClosed"/> event
/// would not be emitted.
/// </summary>
event Action WindowAllClosed;
/// <summary>
/// Emitted before the application starts closing its windows.
/// <para/>
/// Note: If application quit was initiated by <see cref="AutoUpdater.QuitAndInstall"/> then <see cref="BeforeQuit"/>
/// is emitted after emitting close event on all windows and closing them.
/// <para/>
/// Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
/// </summary>
event Func<QuitEventArgs, Task> BeforeQuit;
/// <summary>
/// Emitted when all windows have been closed and the application will quit.
/// <para/>
/// See the description of the <see cref="WindowAllClosed"/> event for the differences between the <see cref="WillQuit"/>
/// and <see cref="WindowAllClosed"/> events.
/// <para/>
/// Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
/// </summary>
event Func<QuitEventArgs, Task> WillQuit;
/// <summary>
/// Emitted when the application is quitting.
/// <para/>
/// Note: On Windows, this event will not be emitted if the app is closed due to a shutdown/restart of the system or a user logout.
/// </summary>
event Func<Task> Quitting;
/// <summary>
/// Emitted when a <see cref="BrowserWindow"/> blurred.
/// </summary>
event Action BrowserWindowBlur;
/// <summary>
/// Emitted when a <see cref="BrowserWindow"/> gets focused.
/// </summary>
event Action BrowserWindowFocus;
/// <summary>
/// Emitted when a new <see cref="BrowserWindow"/> is created.
/// </summary>
event Action BrowserWindowCreated;
/// <summary>
/// Emitted when a new <see cref="WebContents"/> is created.
/// </summary>
event Action WebContentsCreated;
/// <summary>
/// Emitted when Chromes accessibility support changes. This event fires when assistive technologies, such as
/// screen readers, are enabled or disabled. See https://www.chromium.org/developers/design-documents/accessibility for more details.
/// </summary>
/// <returns><see langword="true"/> when Chrome's accessibility support is enabled, <see langword="false"/> otherwise.</returns>
event Action<bool> AccessibilitySupportChanged;
/// <summary>
/// Emitted when the application has finished basic startup.
/// </summary>
event Action Ready;
/// <summary>
/// Application host fully started.
/// </summary>
bool IsReady { get; }
/// <summary>
/// A <see cref="string"/> 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.
/// </summary>
string Name
{
set;
}
/// <summary>
/// A <see cref="string"/> 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.
/// </summary>
Task<string> GetNameAsync { get; }
/// <summary>
/// A <see cref="CommandLine"/> object that allows you to read and manipulate the command line arguments that Chromium uses.
/// </summary>
CommandLine CommandLine { get; }
/// <summary>
/// A <see cref="string"/> which is the user agent string Electron will use as a global fallback.
/// <para/>
/// 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.
/// </summary>
string UserAgentFallback
{
set;
}
/// <summary>
/// A <see cref="string"/> which is the user agent string Electron will use as a global fallback.
/// <para/>
/// 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.
/// </summary>
Task<string> GetUserAgentFallbackAsync { get; }
/// <summary>
/// Emitted when a MacOS user wants to open a file with the application. The open-file event is usually emitted
/// when the application is already open and the OS wants to reuse the application to open the file.
/// open-file is also emitted when a file is dropped onto the dock and the application is not yet running.
/// <para/>
/// On Windows, you have to parse the arguments using App.CommandLine to get the filepath.
/// </summary>
event Action<string> OpenFile;
/// <summary>
/// Emitted when a MacOS user wants to open a URL with the application. Your application's Info.plist file must
/// define the URL scheme within the CFBundleURLTypes key, and set NSPrincipalClass to AtomApplication.
/// </summary>
event Action<string> OpenUrl;
/// <summary>
/// Try to close all windows. The <see cref="BeforeQuit"/> event will be emitted first. If all windows are successfully
/// closed, the <see cref="WillQuit"/> event will be emitted and by default the application will terminate. This method
/// guarantees that all beforeunload and unload event handlers are correctly executed. It is possible
/// that a window cancels the quitting by returning <see langword="false"/> in the beforeunload event handler.
/// </summary>
void Quit();
/// <summary>
/// All windows will be closed immediately without asking user and the <see cref="BeforeQuit"/> and <see cref="WillQuit"/>
/// events will not be emitted.
/// </summary>
/// <param name="exitCode">Exits immediately with exitCode. exitCode defaults to 0.</param>
void Exit(int exitCode = 0);
/// <summary>
/// Relaunches the app when current instance exits. By default the new instance will use the same working directory
/// and command line arguments with current instance.
/// <para/>
/// Note that this method does not quit the app when executed, you have to call <see cref="Quit"/> or <see cref="Exit"/>
/// after calling <see cref="Relaunch()"/> to make the app restart.
/// <para/>
/// When <see cref="Relaunch()"/> is called for multiple times, multiple instances will be started after current instance
/// exited.
/// </summary>
void Relaunch();
/// <summary>
/// Relaunches the app when current instance exits. By default the new instance will use the same working directory
/// and command line arguments with current instance. When <see cref="RelaunchOptions.Args"/> is specified, the
/// <see cref="RelaunchOptions.Args"/> will be passed as command line arguments instead. When <see cref="RelaunchOptions.ExecPath"/>
/// is specified, the <see cref="RelaunchOptions.ExecPath"/> will be executed for relaunch instead of current app.
/// <para/>
/// Note that this method does not quit the app when executed, you have to call <see cref="Quit"/> or <see cref="Exit"/>
/// after calling <see cref="Relaunch()"/> to make the app restart.
/// <para/>
/// When <see cref="Relaunch()"/> is called for multiple times, multiple instances will be started after current instance
/// exited.
/// </summary>
/// <param name="relaunchOptions">Options for the relaunch.</param>
void Relaunch(RelaunchOptions relaunchOptions);
/// <summary>
/// On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses
/// on the application's first window.
/// </summary>
void Focus();
/// <summary>
/// On Linux, focuses on the first visible window. On macOS, makes the application the active app. On Windows, focuses
/// on the application's first window.
/// <para/>
/// You should seek to use the <see cref="FocusOptions.Steal"/> option as sparingly as possible.
/// </summary>
void Focus(FocusOptions focusOptions);
/// <summary>
/// Hides all application windows without minimizing them.
/// </summary>
void Hide();
/// <summary>
/// Shows application windows after they were hidden. Does not automatically focus them.
/// </summary>
void Show();
/// <summary>
/// The current application directory.
/// </summary>
Task<string> GetAppPathAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Sets or creates a directory your app's logs which can then be manipulated with <see cref="GetPathAsync"/>
/// or <see cref="SetPath"/>.
/// <para/>
/// Calling <see cref="SetAppLogsPath"/> without a path parameter will result in this directory being set to
/// ~/Library/Logs/YourAppName on macOS, and inside the userData directory on Linux and Windows.
/// </summary>
/// <param name="path">A custom path for your logs. Must be absolute.</param>
void SetAppLogsPath(string path);
/// <summary>
/// The path to a special directory. If <see cref="GetPathAsync"/> is called without called
/// <see cref="SetAppLogsPath"/> being called first, a default directory will be created equivalent
/// to calling <see cref="SetAppLogsPath"/> without a path parameter.
/// </summary>
/// <param name="pathName">Special directory.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A path to a special directory or file associated with name.</returns>
Task<string> GetPathAsync(PathName pathName, CancellationToken cancellationToken = default);
/// <summary>
/// Overrides the path to a special directory or file associated with name. If the path specifies a directory
/// that does not exist, an Error is thrown. In that case, the directory should be created with fs.mkdirSync or similar.
/// <para/>
/// You can only override paths of a name defined in <see cref="GetPathAsync"/>.
/// <para/>
/// By default, web pages' cookies and caches will be stored under the <see cref="PathName.UserData"/> directory. If you
/// want to change this location, you have to override the <see cref="PathName.UserData"/> path before the <see cref="Ready"/>
/// event of the <see cref="App"/> module is emitted.
/// <param name="name">Special directory.</param>
/// <param name="path">New path to a special directory.</param>
/// </summary>
void SetPath(PathName name, string path);
/// <summary>
/// The version of the loaded application. If no version is found in the applications package.json file,
/// the version of the current bundle or executable is returned.
/// </summary>
/// <returns>The version of the loaded application.</returns>
Task<string> GetVersionAsync(CancellationToken cancellationToken = default);
/// <summary>
/// The current application locale. Possible return values are documented <see href="https://www.electronjs.org/docs/api/locales">here</see>.
/// <para/>
/// Note: When distributing your packaged app, you have to also ship the locales folder.
/// <para/>
/// Note: On Windows, you have to call it after the <see cref="Ready"/> events gets emitted.
/// </summary>
/// <returns>The current application locale.</returns>
Task<string> GetLocaleAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Adds path to the recent documents list. This list is managed by the OS. On Windows you can visit the
/// list from the task bar, and on macOS you can visit it from dock menu.
/// </summary>
/// <param name="path">Path to add.</param>
void AddRecentDocument(string path);
/// <summary>
/// Clears the recent documents list.
/// </summary>
void ClearRecentDocuments();
/// <summary>
/// Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to
/// integrate your app deeper into the operating system. Once registered, all links with your-protocol://
/// will be opened with the current executable. The whole link, including protocol, will be passed to your
/// application as a parameter.
/// <para/>
/// Note: On macOS, you can only register protocols that have been added to your app's info.plist, which
/// cannot be modified at runtime. However, you can change the file during build time via
/// <see href="https://www.electronforge.io/">Electron Forge</see>,
/// <see href="https://github.com/electron/electron-packager">Electron Packager</see>, or by editing info.plist
/// with a text editor. Please refer to
/// <see href="https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115">Apple's documentation</see>
/// for details.
/// <para/>
/// Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but
/// the registry key it sets won't be accessible by other applications. In order to register your Windows Store
/// application as a default protocol handler you <see href="https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol">must declare the protocol in your manifest</see>.
/// <para/>
/// The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
/// </summary>
/// <param name="protocol">
/// The name of your protocol, without ://. For example, if you want your app to handle electron:// links,
/// call this method with electron as the parameter.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> SetAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default);
/// <summary>
/// Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to
/// integrate your app deeper into the operating system. Once registered, all links with your-protocol://
/// will be opened with the current executable. The whole link, including protocol, will be passed to your
/// application as a parameter.
/// <para/>
/// Note: On macOS, you can only register protocols that have been added to your app's info.plist, which
/// cannot be modified at runtime. However, you can change the file during build time via
/// <see href="https://www.electronforge.io/">Electron Forge</see>,
/// <see href="https://github.com/electron/electron-packager">Electron Packager</see>, or by editing info.plist
/// with a text editor. Please refer to
/// <see href="https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115">Apple's documentation</see>
/// for details.
/// <para/>
/// Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but
/// the registry key it sets won't be accessible by other applications. In order to register your Windows Store
/// application as a default protocol handler you <see href="https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol">must declare the protocol in your manifest</see>.
/// <para/>
/// The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
/// </summary>
/// <param name="protocol">
/// The name of your protocol, without ://. For example, if you want your app to handle electron:// links,
/// call this method with electron as the parameter.</param>
/// <param name="path">The path to the Electron executable. Defaults to process.execPath</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> SetAsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default);
/// <summary>
/// Sets the current executable as the default handler for a protocol (aka URI scheme). It allows you to
/// integrate your app deeper into the operating system. Once registered, all links with your-protocol://
/// will be opened with the current executable. The whole link, including protocol, will be passed to your
/// application as a parameter.
/// <para/>
/// Note: On macOS, you can only register protocols that have been added to your app's info.plist, which
/// cannot be modified at runtime. However, you can change the file during build time via
/// <see href="https://www.electronforge.io/">Electron Forge</see>,
/// <see href="https://github.com/electron/electron-packager">Electron Packager</see>, or by editing info.plist
/// with a text editor. Please refer to
/// <see href="https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115">Apple's documentation</see>
/// for details.
/// <para/>
/// Note: In a Windows Store environment (when packaged as an appx) this API will return true for all calls but
/// the registry key it sets won't be accessible by other applications. In order to register your Windows Store
/// application as a default protocol handler you <see href="https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol">must declare the protocol in your manifest</see>.
/// <para/>
/// The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
/// </summary>
/// <param name="protocol">
/// The name of your protocol, without ://. For example, if you want your app to handle electron:// links,
/// call this method with electron as the parameter.</param>
/// <param name="path">The path to the Electron executable. Defaults to process.execPath</param>
/// <param name="args">Arguments passed to the executable. Defaults to an empty array.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> SetAsDefaultProtocolClientAsync(string protocol, string path, string[] args, CancellationToken cancellationToken = default);
/// <summary>
/// This method checks if the current executable as the default handler for a protocol (aka URI scheme).
/// If so, it will remove the app as the default handler.
/// </summary>
/// <param name="protocol">The name of your protocol, without ://.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> RemoveAsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default);
/// <summary>
/// This method checks if the current executable as the default handler for a protocol (aka URI scheme).
/// If so, it will remove the app as the default handler.
/// </summary>
/// <param name="protocol">The name of your protocol, without ://.</param>
/// <param name="path">Defaults to process.execPath.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> RemoveAsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default);
/// <summary>
/// This method checks if the current executable as the default handler for a protocol (aka URI scheme).
/// If so, it will remove the app as the default handler.
/// </summary>
/// <param name="protocol">The name of your protocol, without ://.</param>
/// <param name="path">Defaults to process.execPath.</param>
/// <param name="args">Defaults to an empty array.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> RemoveAsDefaultProtocolClientAsync(string protocol, string path, string[] args, CancellationToken cancellationToken = default);
/// <summary>
/// This method checks if the current executable is the default handler for a protocol (aka URI scheme).
/// <para/>
/// Note: On macOS, you can use this method to check if the app has been registered as the default protocol
/// handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist
/// on the macOS machine. Please refer to <see href="https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme">Apple's documentation</see>
/// for details.
/// <para/>
/// The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
/// </summary>
/// <param name="protocol">The name of your protocol, without ://.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the current executable is the default handler for a protocol (aka URI scheme).</returns>
Task<bool> IsDefaultProtocolClientAsync(string protocol, CancellationToken cancellationToken = default);
/// <summary>
/// This method checks if the current executable is the default handler for a protocol (aka URI scheme).
/// <para/>
/// Note: On macOS, you can use this method to check if the app has been registered as the default protocol
/// handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist
/// on the macOS machine. Please refer to <see href="https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme">Apple's documentation</see>
/// for details.
/// <para/>
/// The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
/// </summary>
/// <param name="protocol">The name of your protocol, without ://.</param>
/// <param name="path">Defaults to process.execPath.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the current executable is the default handler for a protocol (aka URI scheme).</returns>
Task<bool> IsDefaultProtocolClientAsync(string protocol, string path, CancellationToken cancellationToken = default);
/// <summary>
/// This method checks if the current executable is the default handler for a protocol (aka URI scheme).
/// <para/>
/// Note: On macOS, you can use this method to check if the app has been registered as the default protocol
/// handler for a protocol. You can also verify this by checking ~/Library/Preferences/com.apple.LaunchServices.plist
/// on the macOS machine. Please refer to <see href="https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme">Apple's documentation</see>
/// for details.
/// <para/>
/// The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
/// </summary>
/// <param name="protocol">The name of your protocol, without ://.</param>
/// <param name="path">Defaults to process.execPath.</param>
/// <param name="args">Defaults to an empty array.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the current executable is the default handler for a protocol (aka URI scheme).</returns>
Task<bool> IsDefaultProtocolClientAsync(string protocol, string path, string[] args, CancellationToken cancellationToken = default);
/// <summary>
/// Adds tasks to the <see cref="UserTask"/> category of the JumpList on Windows.
/// <para/>
/// Note: If you'd like to customize the Jump List even more use <see cref="SetJumpList"/> instead.
/// </summary>
/// <param name="userTasks">Array of <see cref="UserTask"/> objects.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> SetUserTasksAsync(UserTask[] userTasks, CancellationToken cancellationToken = default);
/// <summary>
/// Jump List settings for the application.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Jump List settings.</returns>
Task<JumpListSettings> GetJumpListSettingsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Sets or removes a custom Jump List for the application. If categories is null the previously set custom
/// Jump List (if any) will be replaced by the standard Jump List for the app (managed by Windows).
/// <para/>
/// Note: If a <see cref="JumpListCategory"/> object has neither the <see cref="JumpListCategory.Type"/> nor
/// the <see cref="JumpListCategory.Name"/> property set then its <see cref="JumpListCategory.Type"/> is assumed
/// to be <see cref="JumpListCategoryType.tasks"/>. If the <see cref="JumpListCategory.Name"/> property is set but
/// the <see cref="JumpListCategory.Type"/> property is omitted then the <see cref="JumpListCategory.Type"/> is
/// assumed to be <see cref="JumpListCategoryType.custom"/>.
/// <para/>
/// Note: Users can remove items from custom categories, and Windows will not allow a removed item to be added
/// back into a custom category until after the next successful call to <see cref="SetJumpList"/>. Any attempt
/// to re-add a removed item to a custom category earlier than that will result in the entire custom category being
/// omitted from the Jump List. The list of removed items can be obtained using <see cref="GetJumpListSettingsAsync"/>.
/// </summary>
/// <param name="categories">Array of <see cref="JumpListCategory"/> objects.</param>
void SetJumpList(JumpListCategory[] categories);
/// <summary>
/// The return value of this method indicates whether or not this instance of your application successfully obtained
/// the lock. If it failed to obtain the lock, you can assume that another instance of your application is already
/// running with the lock and exit immediately.
/// <para/>
/// I.e.This method returns <see langword="true"/> if your process is the primary instance of your application and your
/// app should continue loading. It returns <see langword="false"/> if your process should immediately quit as it has
/// sent its parameters to another instance that has already acquired the lock.
/// <para/>
/// On macOS, the system enforces single instance automatically when users try to open a second instance of your app
/// in Finder, and the open-file and open-url events will be emitted for that.However when users start your app in
/// command line, the system's single instance mechanism will be bypassed, and you have to use this method to ensure
/// single instance.
/// </summary>
/// <param name="newInstanceOpened">Lambda with an array of the second instances command line arguments.
/// The second parameter is the working directory path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>This method returns false if your process is the primary instance of the application and your app
/// should continue loading. And returns true if your process has sent its parameters to another instance, and
/// you should immediately quit.
/// </returns>
Task<bool> RequestSingleInstanceLockAsync(Action<string[], string> newInstanceOpened, CancellationToken cancellationToken = default);
/// <summary>
/// Releases all locks that were created by makeSingleInstance. This will allow
/// multiple instances of the application to once again run side by side.
/// </summary>
void ReleaseSingleInstanceLock();
/// <summary>
/// This method returns whether or not this instance of your app is currently holding the single instance lock.
/// You can request the lock with <see cref="RequestSingleInstanceLockAsync"/> and release with
/// <see cref="ReleaseSingleInstanceLock"/>.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
Task<bool> HasSingleInstanceLockAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Creates an NSUserActivity and sets it as the current activity. The activity is
/// eligible for <see href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html">Handoff</see>
/// to another device afterward.
/// </summary>
/// <param name="type">Uniquely identifies the activity. Maps to <see href="https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/instp/NSUserActivity/activityType">NSUserActivity.activityType</see>.</param>
/// <param name="userInfo">App-specific state to store for use by another device.</param>
void SetUserActivity(string type, object userInfo);
/// <summary>
/// Creates an NSUserActivity and sets it as the current activity. The activity is
/// eligible for <see href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html">Handoff</see>
/// to another device afterward.
/// </summary>
/// <param name="type">
/// Uniquely identifies the activity. Maps to <see href="https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUserActivity_Class/index.html#//apple_ref/occ/instp/NSUserActivity/activityType">NSUserActivity.activityType</see>.
/// </param>
/// <param name="userInfo">App-specific state to store for use by another device.</param>
/// <param name="webpageUrl">
/// The webpage to load in a browser if no suitable app is installed on the resuming device. The scheme must be http or https.
/// </param>
void SetUserActivity(string type, object userInfo, string webpageUrl);
/// <summary>
/// The type of the currently running activity.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
Task<string> GetCurrentActivityTypeAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Invalidates the current <see href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html">Handoff</see> user activity.
/// </summary>
void InvalidateCurrentActivity();
/// <summary>
/// Marks the current <see href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html">Handoff</see> user activity as inactive without invalidating it.
/// </summary>
void ResignCurrentActivity();
/// <summary>
/// Changes the <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx">Application User Model ID</see> to id.
/// </summary>
/// <param name="id">Model Id.</param>
void SetAppUserModelId(string id);
/// TODO: Check new parameter which is a function [App.ImportCertificate]
/// <summary>
/// Imports the certificate in pkcs12 format into the platform certificate store.
/// callback is called with the result of import operation, a value of 0 indicates
/// success while any other value indicates failure according to chromium net_error_list.
/// </summary>
/// <param name="options"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Result of import. Value of 0 indicates success.</returns>
Task<int> ImportCertificateAsync(ImportCertificateOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Memory and cpu usage statistics of all the processes associated with the app.
/// </summary>
/// <returns>
/// Array of ProcessMetric objects that correspond to memory and cpu usage
/// statistics of all the processes associated with the app.
/// <param name="cancellationToken">The cancellation token.</param>
/// </returns>
Task<ProcessMetric[]> GetAppMetricsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// The Graphics Feature Status from chrome://gpu/.
/// <para/>
/// Note: This information is only usable after the gpu-info-update event is emitted.
/// <param name="cancellationToken">The cancellation token.</param>
/// </summary>
Task<GPUFeatureStatus> GetGpuFeatureStatusAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Sets the counter badge for current app. Setting the count to 0 will hide the badge.
/// On macOS it shows on the dock icon. On Linux it only works for Unity launcher.
/// <para/>
/// Note: Unity launcher requires the existence of a .desktop file to work, for more
/// information please read <see href="https://www.electronjs.org/docs/tutorial/desktop-environment-integration#unity-launcher">Desktop Environment Integration</see>.
/// </summary>
/// <param name="count">Counter badge.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the call succeeded.</returns>
Task<bool> SetBadgeCountAsync(int count, CancellationToken cancellationToken = default);
/// <summary>
/// The current value displayed in the counter badge.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
Task<int> GetBadgeCountAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Whether the current desktop environment is Unity launcher.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
Task<bool> IsUnityRunningAsync(CancellationToken cancellationToken = default);
/// <summary>
/// If you provided path and args options to <see cref="SetLoginItemSettings"/> then you need to pass the same
/// arguments here for <see cref="LoginItemSettings.OpenAtLogin"/> to be set correctly.
/// </summary>
Task<LoginItemSettings> GetLoginItemSettingsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// If you provided path and args options to <see cref="SetLoginItemSettings"/> then you need to pass the same
/// arguments here for <see cref="LoginItemSettings.OpenAtLogin"/> to be set correctly.
/// </summary>
/// <param name="options"></param>
/// <param name="cancellationToken">The cancellation token.</param>
Task<LoginItemSettings> GetLoginItemSettingsAsync(LoginItemSettingsOptions options, CancellationToken cancellationToken = default);
/// <summary>
/// Set the app's login item settings.
/// To work with Electron's autoUpdater on Windows, which uses <see href="https://github.com/Squirrel/Squirrel.Windows">Squirrel</see>,
/// you'll want to set the launch path to Update.exe, and pass arguments that specify your application name.
/// </summary>
/// <param name="loginSettings"></param>
void SetLoginItemSettings(LoginSettings loginSettings);
/// <summary>
/// <see langword="true"/> if Chrome's accessibility support is enabled, <see langword="false"/> otherwise. This API will
/// return <see langword="true"/> if the use of assistive technologies, such as screen readers, has been detected.
/// See <see href="chromium.org/developers/design-documents/accessibility">Chromium's accessibility docs</see> for more details.
/// </summary>
/// <returns><see langword="true"/> if Chromes accessibility support is enabled, <see langword="false"/> otherwise.</returns>
Task<bool> IsAccessibilitySupportEnabledAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Manually enables Chrome's accessibility support, allowing to expose accessibility switch to users in application settings.
/// See <see href="chromium.org/developers/design-documents/accessibility">Chromium's accessibility docs</see> for more details.
/// Disabled (<see langword="false"/>) by default.
/// <para/>
/// This API must be called after the <see cref="Ready"/> event is emitted.
/// <para/>
/// Note: Rendering accessibility tree can significantly affect the performance of your app. It should not be enabled by default.
/// </summary>
/// <param name="enabled">Enable or disable <see href="https://developers.google.com/web/fundamentals/accessibility/semantics-builtin/the-accessibility-tree">accessibility tree</see> rendering.</param>
void SetAccessibilitySupportEnabled(bool enabled);
/// <summary>
/// Show the app's about panel options. These options can be overridden with
/// <see cref="SetAboutPanelOptions"/>.
/// </summary>
void ShowAboutPanel();
/// <summary>
/// Set the about panel options. This will override the values defined in the app's .plist file on macOS. See the
/// <see href="https://developer.apple.com/reference/appkit/nsapplication/1428479-orderfrontstandardaboutpanelwith?language=objc">Apple docs</see>
/// for more details. On Linux, values must be set in order to be shown; there are no defaults.
/// <para/>
/// If you do not set credits but still wish to surface them in your app, AppKit will look for a file named "Credits.html",
/// "Credits.rtf", and "Credits.rtfd", in that order, in the bundle returned by the NSBundle class method main. The first file
/// found is used, and if none is found, the info area is left blank. See Apple
/// <see href="https://developer.apple.com/documentation/appkit/nsaboutpaneloptioncredits?language=objc">documentation</see> for more information.
/// </summary>
/// <param name="options">About panel options.</param>
void SetAboutPanelOptions(AboutPanelOptions options);
/// <summary>
/// Subscribe to an unmapped event on the <see cref="App"/> module.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void On(string eventName, Action fn);
/// <summary>
/// Subscribe to an unmapped event on the <see cref="App"/> module.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void On(string eventName, Action<object> fn);
/// <summary>
/// Subscribe to an unmapped event on the <see cref="App"/> module once.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void Once(string eventName, Action fn);
/// <summary>
/// Subscribe to an unmapped event on the <see cref="App"/> module once.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void Once(string eventName, Action<object> fn);
}
}

View File

@@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Enable apps to automatically update themselves. Based on electron-updater.
/// </summary>
public interface IAutoUpdater
{
/// <summary>
/// Whether to automatically download an update when it is found. (Default is true)
/// </summary>
bool AutoDownload { set; }
/// <summary>
/// Whether to automatically download an update when it is found. (Default is true)
/// </summary>
Task<bool> IsAutoDownloadEnabledAsync { get; }
/// <summary>
/// Whether to automatically install a downloaded update on app quit (if `QuitAndInstall` was not called before).
///
/// Applicable only on Windows and Linux.
/// </summary>
bool AutoInstallOnAppQuit { set; }
/// <summary>
/// Whether to automatically install a downloaded update on app quit (if `QuitAndInstall` was not called before).
///
/// Applicable only on Windows and Linux.
/// </summary>
Task<bool> IsAutoInstallOnAppQuitEnabledAsync { get; }
/// <summary>
/// *GitHub provider only.* Whether to allow update to pre-release versions.
/// Defaults to "true" if application version contains prerelease components (e.g. "0.12.1-alpha.1", here "alpha" is a prerelease component), otherwise "false".
///
/// If "true", downgrade will be allowed("allowDowngrade" will be set to "true").
/// </summary>
bool AllowPrerelease { set; }
/// <summary>
/// *GitHub provider only.* Whether to allow update to pre-release versions.
/// Defaults to "true" if application version contains prerelease components (e.g. "0.12.1-alpha.1", here "alpha" is a prerelease component), otherwise "false".
///
/// If "true", downgrade will be allowed("allowDowngrade" will be set to "true").
/// </summary>
Task<bool> IsAllowPrereleaseEnabledAsync { get; }
/// <summary>
/// *GitHub provider only.*
/// Get all release notes (from current version to latest), not just the latest (Default is false).
/// </summary>
bool FullChangelog { set; }
/// <summary>
/// *GitHub provider only.*
/// Get all release notes (from current version to latest), not just the latest (Default is false).
/// </summary>
Task<bool> IsFullChangeLogEnabledAsync { get; }
/// <summary>
/// Whether to allow version downgrade (when a user from the beta channel wants to go back to the stable channel).
/// Taken in account only if channel differs (pre-release version component in terms of semantic versioning).
/// Default is false.
/// </summary>
bool AllowDowngrade { set; }
Task<bool> IsAllowDowngradeEnabledAsync { get; }
/// <summary>
/// For test only.
/// </summary>
Task<string> GetUpdateConfigPathAsync { get; }
/// <summary>
/// The current application version
/// </summary>
Task<SemVer> GetCurrentVersionAsync { get; }
/// <summary>
/// Get the update channel. Not applicable for GitHub.
/// Doesnt return channel from the update configuration, only if was previously set.
/// </summary>
Task<string> GetChannelAsync { get; }
/// <summary>
/// The request headers.
/// </summary>
Task<Dictionary<string, string>> GetRequestHeadersAsync { get; }
/// <summary>
/// The request headers.
/// </summary>
Dictionary<string, string> RequestHeaders { set; }
/// <summary>
/// Emitted when there is an error while updating.
/// </summary>
event Action<string> OnError;
/// <summary>
/// Emitted when checking if an update has started.
/// </summary>
event Action OnCheckingForUpdate;
/// <summary>
/// Emitted when there is an available update.
/// The update is downloaded automatically if AutoDownload is true.
/// </summary>
event Action<UpdateInfo> OnUpdateAvailable;
/// <summary>
/// Emitted when there is no available update.
/// </summary>
event Action<UpdateInfo> OnUpdateNotAvailable;
/// <summary>
/// Emitted on download progress.
/// </summary>
event Action<ProgressInfo> OnDownloadProgress;
/// <summary>
/// Emitted on download complete.
/// </summary>
event Action<UpdateInfo> OnUpdateDownloaded;
/// <summary>
/// Asks the server whether there is an update.
/// </summary>
/// <returns></returns>
Task<UpdateCheckResult> CheckForUpdatesAsync();
/// <summary>
/// Asks the server whether there is an update.
///
/// This will immediately download an update, then install when the app quits.
/// </summary>
/// <returns></returns>
Task<UpdateCheckResult> CheckForUpdatesAndNotifyAsync();
/// <summary>
/// Restarts the app and installs the update after it has been downloaded.
/// It should only be called after `update-downloaded` has been emitted.
///
/// Note: QuitAndInstall() will close all application windows first and only emit `before-quit` event on `app` after that.
/// This is different from the normal quit event sequence.
/// </summary>
/// <param name="isSilent">*windows-only* Runs the installer in silent mode. Defaults to `false`.</param>
/// <param name="isForceRunAfter">Run the app after finish even on silent install. Not applicable for macOS. Ignored if `isSilent` is set to `false`.</param>
void QuitAndInstall(bool isSilent = false, bool isForceRunAfter = false);
/// <summary>
/// Start downloading update manually. You can use this method if "AutoDownload" option is set to "false".
/// </summary>
/// <returns>Path to downloaded file.</returns>
Task<string> DownloadUpdateAsync();
/// <summary>
/// Feed URL.
/// </summary>
/// <returns>Feed URL.</returns>
Task<string> GetFeedURLAsync();
}
}

View File

@@ -1,122 +0,0 @@
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Perform copy and paste operations on the system clipboard.
/// </summary>
public interface IClipboard
{
/// <summary>
/// Read the content in the clipboard as plain text.
/// </summary>
/// <param name="type"></param>
/// <returns>The content in the clipboard as plain text.</returns>
Task<string> ReadTextAsync(string type = "");
/// <summary>
/// Writes the text into the clipboard as plain text.
/// </summary>
/// <param name="text"></param>
/// <param name="type"></param>
void WriteText(string text, string type = "");
/// <summary>
/// The content in the clipboard as markup.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
Task<string> ReadHTMLAsync(string type = "");
/// <summary>
/// Writes markup to the clipboard.
/// </summary>
/// <param name="markup"></param>
/// <param name="type"></param>
void WriteHTML(string markup, string type = "");
/// <summary>
/// The content in the clipboard as RTF.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
Task<string> ReadRTFAsync(string type = "");
/// <summary>
/// Writes the text into the clipboard in RTF.
/// </summary>
/// <param name="text"></param>
/// <param name="type"></param>
void WriteRTF(string text, string type = "");
/// <summary>
/// Returns an Object containing title and url keys representing
/// the bookmark in the clipboard. The title and url values will
/// be empty strings when the bookmark is unavailable.
/// </summary>
/// <returns></returns>
Task<ReadBookmark> ReadBookmarkAsync();
/// <summary>
/// Writes the title and url into the clipboard as a bookmark.
///
/// Note: Most apps on Windows dont support pasting bookmarks
/// into them so you can use clipboard.write to write both a
/// bookmark and fallback text to the clipboard.
/// </summary>
/// <param name="title"></param>
/// <param name="url"></param>
/// <param name="type"></param>
void WriteBookmark(string title, string url, string type = "");
/// <summary>
/// macOS: The text on the find pasteboard. This method uses synchronous IPC
/// when called from the renderer process. The cached value is reread from the
/// find pasteboard whenever the application is activated.
/// </summary>
/// <returns></returns>
Task<string> ReadFindTextAsync();
/// <summary>
/// macOS: Writes the text into the find pasteboard as plain text. This method uses
/// synchronous IPC when called from the renderer process.
/// </summary>
/// <param name="text"></param>
void WriteFindText(string text);
/// <summary>
/// Clears the clipboard content.
/// </summary>
/// <param name="type"></param>
void Clear(string type = "");
/// <summary>
/// An array of supported formats for the clipboard type.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
Task<string[]> AvailableFormatsAsync(string type = "");
/// <summary>
/// Writes data to the clipboard.
/// </summary>
/// <param name="data"></param>
/// <param name="type"></param>
void Write(Data data, string type = "");
/// <summary>
/// Reads an image from the clipboard.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
Task<NativeImage> ReadImageAsync(string type = "");
/// <summary>
/// Writes an image to the clipboard.
/// </summary>
/// <param name="image"></param>
/// <param name="type"></param>
void WriteImage(NativeImage image, string type = "");
}
}

View File

@@ -1,102 +0,0 @@
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Display native system dialogs for opening and saving files, alerting, etc.
/// </summary>
public interface IDialog
{
/// <summary>
/// Note: On Windows and Linux an open dialog can not be both a file selector
/// and a directory selector, so if you set properties to ['openFile', 'openDirectory']
/// on these platforms, a directory selector will be shown.
/// </summary>
/// <param name="browserWindow">The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.</param>
/// <param name="options"></param>
/// <returns>An array of file paths chosen by the user</returns>
Task<string[]> ShowOpenDialogAsync(BrowserWindow browserWindow, OpenDialogOptions options);
/// <summary>
/// Dialog for save files.
/// </summary>
/// <param name="browserWindow">The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.</param>
/// <param name="options"></param>
/// <returns>Returns String, the path of the file chosen by the user, if a callback is provided it returns an empty string.</returns>
Task<string> ShowSaveDialogAsync(BrowserWindow browserWindow, SaveDialogOptions options);
/// <summary>
/// Shows a message box, it will block the process until the message box is closed.
/// It returns the index of the clicked button. The browserWindow argument allows
/// the dialog to attach itself to a parent window, making it modal. If a callback
/// is passed, the dialog will not block the process.The API call will be
/// asynchronous and the result will be passed via callback(response).
/// </summary>
/// <param name="message"></param>
/// <returns>The API call will be asynchronous and the result will be passed via MessageBoxResult.</returns>
Task<MessageBoxResult> ShowMessageBoxAsync(string message);
/// <summary>
/// Shows a message box, it will block the process until the message box is closed.
/// It returns the index of the clicked button. The browserWindow argument allows
/// the dialog to attach itself to a parent window, making it modal. If a callback
/// is passed, the dialog will not block the process.The API call will be
/// asynchronous and the result will be passed via callback(response).
/// </summary>
/// <param name="messageBoxOptions"></param>
/// <returns>The API call will be asynchronous and the result will be passed via MessageBoxResult.</returns>
Task<MessageBoxResult> ShowMessageBoxAsync(MessageBoxOptions messageBoxOptions);
/// <summary>
/// Shows a message box, it will block the process until the message box is closed.
/// It returns the index of the clicked button. If a callback
/// is passed, the dialog will not block the process.
/// </summary>
/// <param name="browserWindow">The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.</param>
/// <param name="message"></param>
/// <returns>The API call will be asynchronous and the result will be passed via MessageBoxResult.</returns>
Task<MessageBoxResult> ShowMessageBoxAsync(BrowserWindow browserWindow, string message);
/// <summary>
/// Shows a message box, it will block the process until the message box is closed.
/// It returns the index of the clicked button. If a callback
/// is passed, the dialog will not block the process.
/// </summary>
/// <param name="browserWindow">The browserWindow argument allows the dialog to attach itself to a parent window, making it modal.</param>
/// <param name="messageBoxOptions"></param>
/// <returns>The API call will be asynchronous and the result will be passed via MessageBoxResult.</returns>
Task<MessageBoxResult> ShowMessageBoxAsync(BrowserWindow browserWindow, MessageBoxOptions messageBoxOptions);
/// <summary>
/// Displays a modal dialog that shows an error message.
///
/// This API can be called safely before the ready event the app module emits,
/// it is usually used to report errors in early stage of startup.If called
/// before the app readyevent on Linux, the message will be emitted to stderr,
/// and no GUI dialog will appear.
/// </summary>
/// <param name="title">The title to display in the error box.</param>
/// <param name="content">The text content to display in the error box.</param>
void ShowErrorBox(string title, string content);
/// <summary>
/// On macOS, this displays a modal dialog that shows a message and certificate information,
/// and gives the user the option of trusting/importing the certificate. If you provide a
/// browserWindow argument the dialog will be attached to the parent window, making it modal.
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
Task ShowCertificateTrustDialogAsync(CertificateTrustDialogOptions options);
/// <summary>
/// On macOS, this displays a modal dialog that shows a message and certificate information,
/// and gives the user the option of trusting/importing the certificate. If you provide a
/// browserWindow argument the dialog will be attached to the parent window, making it modal.
/// </summary>
/// <param name="browserWindow"></param>
/// <param name="options"></param>
/// <returns></returns>
Task ShowCertificateTrustDialogAsync(BrowserWindow browserWindow, CertificateTrustDialogOptions options);
}
}

View File

@@ -1,93 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Control your app in the macOS dock.
/// </summary>
public interface IDock
{
/// <summary>
/// When <see cref="DockBounceType.Critical"/> is passed, the dock icon will bounce until either the application becomes
/// active or the request is canceled. When <see cref="DockBounceType.Informational"/> is passed, the dock icon will bounce
/// for one second. However, the request remains active until either the application becomes active or the request is canceled.
/// <para/>
/// Note: This method can only be used while the app is not focused; when the app is focused it will return -1.
/// </summary>
/// <param name="type">Can be critical or informational. The default is informational.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Return an ID representing the request.</returns>
Task<int> BounceAsync(DockBounceType type, CancellationToken cancellationToken = default);
/// <summary>
/// Cancel the bounce of id.
/// </summary>
/// <param name="id">Id of the request.</param>
void CancelBounce(int id);
/// <summary>
/// Bounces the Downloads stack if the filePath is inside the Downloads folder.
/// </summary>
/// <param name="filePath"></param>
void DownloadFinished(string filePath);
/// <summary>
/// Sets the string to be displayed in the docks badging area.
/// </summary>
/// <param name="text"></param>
void SetBadge(string text);
/// <summary>
/// Gets the string to be displayed in the docks badging area.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The badge string of the dock.</returns>
Task<string> GetBadgeAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Hides the dock icon.
/// </summary>
void Hide();
/// <summary>
/// Shows the dock icon.
/// </summary>
void Show();
/// <summary>
/// Whether the dock icon is visible. The app.dock.show() call is asynchronous
/// so this method might not return true immediately after that call.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Whether the dock icon is visible.</returns>
Task<bool> IsVisibleAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Gets the dock menu items.
/// </summary>
/// <value>
/// The menu items.
/// </value>
IReadOnlyCollection<MenuItem> MenuItems { get; }
/// <summary>
/// Sets the application's dock menu.
/// </summary>
void SetMenu(MenuItem[] menuItems);
/// <summary>
/// TODO: Menu (macOS) still to be implemented
/// Gets the application's dock menu.
/// </summary>
Task<Menu> GetMenu(CancellationToken cancellationToken = default);
/// <summary>
/// Sets the image associated with this dock icon.
/// </summary>
/// <param name="image"></param>
void SetIcon(string image);
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.Threading.Tasks;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Detect keyboard events when the application does not have keyboard focus.
/// </summary>
public interface IGlobalShortcut
{
/// <summary>
/// Registers a global shortcut of accelerator.
/// The callback is called when the registered shortcut is pressed by the user.
///
/// When the accelerator is already taken by other applications, this call will
/// silently fail.This behavior is intended by operating systems, since they dont
/// want applications to fight for global shortcuts.
/// </summary>
void Register(string accelerator, Action function);
/// <summary>
/// When the accelerator is already taken by other applications,
/// this call will still return false. This behavior is intended by operating systems,
/// since they dont want applications to fight for global shortcuts.
/// </summary>
/// <returns>Whether this application has registered accelerator.</returns>
Task<bool> IsRegisteredAsync(string accelerator);
/// <summary>
/// Unregisters the global shortcut of accelerator.
/// </summary>
void Unregister(string accelerator);
/// <summary>
/// Unregisters all of the global shortcuts.
/// </summary>
void UnregisterAll();
}
}

View File

@@ -1,30 +0,0 @@
using System.Threading.Tasks;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Allows you to execute native JavaScript/TypeScript code from the host process.
///
/// It is only possible if the Electron.NET CLI has previously added an
/// ElectronHostHook directory:
/// <c>electronize add HostHook</c>
/// </summary>
public interface IHostHook
{
/// <summary>
/// Execute native JavaScript/TypeScript code.
/// </summary>
/// <param name="socketEventName">Socket name registered on the host.</param>
/// <param name="arguments">Optional parameters.</param>
void Call(string socketEventName, params dynamic[] arguments);
/// <summary>
/// Execute native JavaScript/TypeScript code.
/// </summary>
/// <typeparam name="T">Results from the executed host code.</typeparam>
/// <param name="socketEventName">Socket name registered on the host.</param>
/// <param name="arguments">Optional parameters.</param>
/// <returns></returns>
Task<T> CallAsync<T>(string socketEventName, params dynamic[] arguments);
}
}

View File

@@ -1,65 +0,0 @@
using System;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Communicate asynchronously from the main process to renderer processes.
/// </summary>
public interface IIpcMain
{
/// <summary>
/// Listens to channel, when a new message arrives listener would be called with
/// listener(event, args...).
/// </summary>
/// <param name="channel">Channelname.</param>
/// <param name="listener">Callback Method.</param>
void On(string channel, Action<object> listener);
/// <summary>
/// Send a message to the renderer process synchronously via channel,
/// you can also send arbitrary arguments.
///
/// Note: Sending a synchronous message will block the whole renderer process,
/// unless you know what you are doing you should never use it.
/// </summary>
/// <param name="channel"></param>
/// <param name="listener"></param>
void OnSync(string channel, Func<object, object> listener);
/// <summary>
/// Adds a one time listener method for the event. This listener is invoked only
/// the next time a message is sent to channel, after which it is removed.
/// </summary>
/// <param name="channel">Channelname.</param>
/// <param name="listener">Callback Method.</param>
void Once(string channel, Action<object> listener);
/// <summary>
/// Removes listeners of the specified channel.
/// </summary>
/// <param name="channel">Channelname.</param>
void RemoveAllListeners(string channel);
/// <summary>
/// Send a message to the renderer process asynchronously via channel, you can also send
/// arbitrary arguments. Arguments will be serialized in JSON internally and hence
/// no functions or prototype chain will be included. The renderer process handles it by
/// listening for channel with ipcRenderer module.
/// </summary>
/// <param name="browserWindow">BrowserWindow with channel.</param>
/// <param name="channel">Channelname.</param>
/// <param name="data">Arguments data.</param>
void Send(BrowserWindow browserWindow, string channel, params object[] data);
/// <summary>
/// Send a message to the BrowserView renderer process asynchronously via channel, you can also send
/// arbitrary arguments. Arguments will be serialized in JSON internally and hence
/// no functions or prototype chain will be included. The renderer process handles it by
/// listening for channel with ipcRenderer module.
/// </summary>
/// <param name="browserView">BrowserView with channel.</param>
/// <param name="channel">Channelname.</param>
/// <param name="data">Arguments data.</param>
void Send(BrowserView browserView, string channel, params object[] data);
}
}

View File

@@ -1,47 +0,0 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Create native application menus and context menus.
/// </summary>
public interface IMenu
{
/// <summary>
/// Gets the menu items.
/// </summary>
/// <value>
/// The menu items.
/// </value>
IReadOnlyCollection<MenuItem> MenuItems { get; }
/// <summary>
/// Gets the context menu items.
/// </summary>
/// <value>
/// The context menu items.
/// </value>
IReadOnlyDictionary<int, ReadOnlyCollection<MenuItem>> ContextMenuItems { get; }
/// <summary>
/// Sets the application menu.
/// </summary>
/// <param name="menuItems">The menu items.</param>
void SetApplicationMenu(MenuItem[] menuItems);
/// <summary>
/// Sets the context menu.
/// </summary>
/// <param name="browserWindow">The browser window.</param>
/// <param name="menuItems">The menu items.</param>
void SetContextMenu(BrowserWindow browserWindow, MenuItem[] menuItems);
/// <summary>
/// Contexts the menu popup.
/// </summary>
/// <param name="browserWindow">The browser window.</param>
void ContextMenuPopup(BrowserWindow browserWindow);
}
}

View File

@@ -1,101 +0,0 @@
using System;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Read and respond to changes in Chromium's native color theme.
/// </summary>
public interface INativeTheme
{
/// <summary>
/// Setting this property to <see cref="ThemeSourceMode.System"/> will remove the override and everything will be reset to the OS default. By default 'ThemeSource' is <see cref="ThemeSourceMode.System"/>.
/// <para/>
/// Settings this property to <see cref="ThemeSourceMode.Dark"/> will have the following effects:
/// <list type="bullet">
/// <item>
/// <description><see cref="ShouldUseDarkColorsAsync"/> will be <see langword="true"/> when accessed</description>
/// </item>
/// <item>
/// <description>Any UI Electron renders on Linux and Windows including context menus, devtools, etc. will use the dark UI.</description>
/// </item>
/// <item>
/// <description>Any UI the OS renders on macOS including menus, window frames, etc. will use the dark UI.</description>
/// </item>
/// <item>
/// <description>The 'prefers-color-scheme' CSS query will match 'dark' mode.</description>
/// </item>
/// <item>
/// <description>The 'updated' event will be emitted</description>
/// </item>
/// </list>
/// <para/>
/// Settings this property to <see cref="ThemeSourceMode.Light"/> will have the following effects:
/// <list type="bullet">
/// <item>
/// <description><see cref="ShouldUseDarkColorsAsync"/> will be <see langword="false"/> when accessed</description>
/// </item>
/// <item>
/// <description>Any UI Electron renders on Linux and Windows including context menus, devtools, etc. will use the light UI.</description>
/// </item>
/// <item>
/// <description>Any UI the OS renders on macOS including menus, window frames, etc. will use the light UI.</description>
/// </item>
/// <item>
/// <description>The 'prefers-color-scheme' CSS query will match 'light' mode.</description>
/// </item>
/// <item>
/// <description>The 'updated' event will be emitted</description>
/// </item>
/// </list>
/// The usage of this property should align with a classic "dark mode" state machine in your application where the user has three options.
/// <para/>
/// <list type="bullet">
/// <item>
/// <description>Follow OS: SetThemeSource(ThemeSourceMode.System);</description>
/// </item>
/// <item>
/// <description>Dark Mode: SetThemeSource(ThemeSourceMode.Dark);</description>
/// </item>
/// <item>
/// <description>Light Mode: SetThemeSource(ThemeSourceMode.Light);</description>
/// </item>
/// </list>
/// Your application should then always use <see cref="ShouldUseDarkColorsAsync"/> to determine what CSS to apply.
/// </summary>
/// <param name="themeSourceMode">The new ThemeSource.</param>
void SetThemeSource(ThemeSourceMode themeSourceMode);
/// <summary>
/// A <see cref="ThemeSourceMode"/> property that can be <see cref="ThemeSourceMode.System"/>, <see cref="ThemeSourceMode.Light"/> or <see cref="ThemeSourceMode.Dark"/>. It is used to override (<seealso cref="SetThemeSource"/>) and
/// supercede the value that Chromium has chosen to use internally.
/// </summary>
Task<ThemeSourceMode> GetThemeSourceAsync();
/// <summary>
/// A <see cref="bool"/> for if the OS / Chromium currently has a dark mode enabled or is
/// being instructed to show a dark-style UI. If you want to modify this value you
/// should use <see cref="SetThemeSource"/>.
/// </summary>
Task<bool> ShouldUseDarkColorsAsync();
/// <summary>
/// A <see cref="bool"/> for if the OS / Chromium currently has high-contrast mode enabled or is
/// being instructed to show a high-contrast UI.
/// </summary>
Task<bool> ShouldUseHighContrastColorsAsync();
/// <summary>
/// A <see cref="bool"/> for if the OS / Chromium currently has an inverted color scheme or is
/// being instructed to use an inverted color scheme.
/// </summary>
Task<bool> ShouldUseInvertedColorSchemeAsync();
/// <summary>
/// Emitted when something in the underlying NativeTheme has changed. This normally means that either the value of <see cref="ShouldUseDarkColorsAsync"/>,
/// <see cref="ShouldUseHighContrastColorsAsync"/> or <see cref="ShouldUseInvertedColorSchemeAsync"/> has changed. You will have to check them to determine which one has changed.
/// </summary>
event Action Updated;
}
}

View File

@@ -1,23 +0,0 @@
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Create OS desktop notifications
/// </summary>
public interface INotification
{
/// <summary>
/// Create OS desktop notifications
/// </summary>
/// <param name="notificationOptions"></param>
void Show(NotificationOptions notificationOptions);
/// <summary>
/// Whether or not desktop notifications are supported on the current system.
/// </summary>
/// <returns></returns>
Task<bool> IsSupportedAsync();
}
}

View File

@@ -1,48 +0,0 @@
using System;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Monitor power state changes..
/// </summary>
public interface IPowerMonitor
{
/// <summary>
/// Emitted when the system is about to lock the screen.
/// </summary>
event Action OnLockScreen;
/// <summary>
/// Emitted when the system is about to unlock the screen.
/// </summary>
event Action OnUnLockScreen;
/// <summary>
/// Emitted when the system is suspending.
/// </summary>
event Action OnSuspend;
/// <summary>
/// Emitted when system is resuming.
/// </summary>
event Action OnResume;
/// <summary>
/// Emitted when the system changes to AC power.
/// </summary>
event Action OnAC;
/// <summary>
/// Emitted when system changes to battery power.
/// </summary>
event Action OnBattery;
/// <summary>
/// Emitted when the system is about to reboot or shut down. If the event handler
/// invokes `e.preventDefault()`, Electron will attempt to delay system shutdown in
/// order for the app to exit cleanly.If `e.preventDefault()` is called, the app
/// should exit as soon as possible by calling something like `app.quit()`.
/// </summary>
event Action OnShutdown;
}
}

View File

@@ -1,66 +0,0 @@
using System;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Retrieve information about screen size, displays, cursor position, etc.
/// </summary>
public interface IScreen
{
/// <summary>
/// Emitted when an new Display has been added.
/// </summary>
event Action<Display> OnDisplayAdded;
/// <summary>
/// Emitted when oldDisplay has been removed.
/// </summary>
event Action<Display> OnDisplayRemoved;
/// <summary>
/// Emitted when one or more metrics change in a display.
/// The changedMetrics is an array of strings that describe the changes.
/// Possible changes are bounds, workArea, scaleFactor and rotation.
/// </summary>
event Action<Display, string[]> OnDisplayMetricsChanged;
/// <summary>
/// The current absolute position of the mouse pointer.
/// </summary>
/// <returns></returns>
Task<Point> GetCursorScreenPointAsync();
/// <summary>
/// macOS: The height of the menu bar in pixels.
/// </summary>
/// <returns>The height of the menu bar in pixels.</returns>
Task<int> GetMenuBarHeightAsync();
/// <summary>
/// The primary display.
/// </summary>
/// <returns></returns>
Task<Display> GetPrimaryDisplayAsync();
/// <summary>
/// An array of displays that are currently available.
/// </summary>
/// <returns>An array of displays that are currently available.</returns>
Task<Display[]> GetAllDisplaysAsync();
/// <summary>
/// The display nearest the specified point.
/// </summary>
/// <returns>The display nearest the specified point.</returns>
Task<Display> GetDisplayNearestPointAsync(Point point);
/// <summary>
/// The display that most closely intersects the provided bounds.
/// </summary>
/// <param name="rectangle"></param>
/// <returns>The display that most closely intersects the provided bounds.</returns>
Task<Display> GetDisplayMatchingAsync(Rectangle rectangle);
}
}

View File

@@ -1,70 +0,0 @@
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Manage files and URLs using their default applications.
/// </summary>
public interface IShell
{
/// <summary>
/// Show the given file in a file manager. If possible, select the file.
/// </summary>
/// <param name="fullPath">The full path to the directory / file.</param>
Task ShowItemInFolderAsync(string fullPath);
/// <summary>
/// Open the given file in the desktop's default manner.
/// </summary>
/// <param name="path">The path to the directory / file.</param>
/// <returns>The error message corresponding to the failure if a failure occurred, otherwise <see cref="string.Empty"/>.</returns>
Task<string> OpenPathAsync(string path);
/// <summary>
/// Open the given external protocol URL in the desktops default manner.
/// (For example, mailto: URLs in the users default mail agent).
/// </summary>
/// <param name="url">Max 2081 characters on windows.</param>
/// <returns>The error message corresponding to the failure if a failure occurred, otherwise <see cref="string.Empty"/>.</returns>
Task<string> OpenExternalAsync(string url);
/// <summary>
/// Open the given external protocol URL in the desktops default manner.
/// (For example, mailto: URLs in the users default mail agent).
/// </summary>
/// <param name="url">Max 2081 characters on windows.</param>
/// <param name="options">Controls the behavior of OpenExternal.</param>
/// <returns>The error message corresponding to the failure if a failure occurred, otherwise <see cref="string.Empty"/>.</returns>
Task<string> OpenExternalAsync(string url, OpenExternalOptions options);
/// <summary>
/// Move the given file to trash and returns a <see cref="bool"/> status for the operation.
/// </summary>
/// <param name="fullPath">The full path to the directory / file.</param>
/// <returns> Whether the item was successfully moved to the trash.</returns>
Task<bool> TrashItemAsync(string fullPath);
/// <summary>
/// Play the beep sound.
/// </summary>
void Beep();
/// <summary>
/// Creates or updates a shortcut link at shortcutPath.
/// </summary>
/// <param name="shortcutPath">The path to the shortcut.</param>
/// <param name="operation">Default is <see cref="ShortcutLinkOperation.Create"/></param>
/// <param name="options">Structure of a shortcut.</param>
/// <returns>Whether the shortcut was created successfully.</returns>
Task<bool> WriteShortcutLinkAsync(string shortcutPath, ShortcutLinkOperation operation, ShortcutDetails options);
/// <summary>
/// Resolves the shortcut link at shortcutPath.
/// An exception will be thrown when any error happens.
/// </summary>
/// <param name="shortcutPath">The path tot the shortcut.</param>
/// <returns><see cref="ShortcutDetails"/> of the shortcut.</returns>
Task<ShortcutDetails> ReadShortcutLinkAsync(string shortcutPath);
}
}

View File

@@ -1,141 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Add icons and context menus to the system's notification area.
/// </summary>
public interface ITray
{
/// <summary>
/// Emitted when the tray icon is clicked.
/// </summary>
event Action<TrayClickEventArgs, Rectangle> OnClick;
/// <summary>
/// macOS, Windows: Emitted when the tray icon is right clicked.
/// </summary>
event Action<TrayClickEventArgs, Rectangle> OnRightClick;
/// <summary>
/// macOS, Windows: Emitted when the tray icon is double clicked.
/// </summary>
event Action<TrayClickEventArgs, Rectangle> OnDoubleClick;
/// <summary>
/// Windows: Emitted when the tray balloon shows.
/// </summary>
event Action OnBalloonShow;
/// <summary>
/// Windows: Emitted when the tray balloon is clicked.
/// </summary>
event Action OnBalloonClick;
/// <summary>
/// Windows: Emitted when the tray balloon is closed
/// because of timeout or user manually closes it.
/// </summary>
event Action OnBalloonClosed;
/// <summary>
/// Gets the menu items.
/// </summary>
/// <value>
/// The menu items.
/// </value>
IReadOnlyCollection<MenuItem> MenuItems { get; }
/// <summary>
/// Shows the Traybar.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="menuItem">The menu item.</param>
void Show(string image, MenuItem menuItem);
/// <summary>
/// Shows the Traybar.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="menuItems">The menu items.</param>
void Show(string image, MenuItem[] menuItems);
/// <summary>
/// Shows the Traybar (empty).
/// </summary>
/// <param name="image">The image.</param>
void Show(string image);
/// <summary>
/// Destroys the tray icon immediately.
/// </summary>
void Destroy();
/// <summary>
/// Sets the image associated with this tray icon.
/// </summary>
/// <param name="image"></param>
void SetImage(string image);
/// <summary>
/// Sets the image associated with this tray icon when pressed on macOS.
/// </summary>
/// <param name="image"></param>
void SetPressedImage(string image);
/// <summary>
/// Sets the hover text for this tray icon.
/// </summary>
/// <param name="toolTip"></param>
void SetToolTip(string toolTip);
/// <summary>
/// macOS: Sets the title displayed aside of the tray icon in the status bar.
/// </summary>
/// <param name="title"></param>
void SetTitle(string title);
/// <summary>
/// Windows: Displays a tray balloon.
/// </summary>
/// <param name="options"></param>
void DisplayBalloon(DisplayBalloonOptions options);
/// <summary>
/// Whether the tray icon is destroyed.
/// </summary>
/// <returns></returns>
Task<bool> IsDestroyedAsync();
/// <summary>
/// Subscribe to an unmapped event on the <see cref="Tray"/> module.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void On(string eventName, Action fn);
/// <summary>
/// Subscribe to an unmapped event on the <see cref="Tray"/> module.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void On(string eventName, Action<object> fn);
/// <summary>
/// Subscribe to an unmapped event on the <see cref="Tray"/> module once.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void Once(string eventName, Action fn);
/// <summary>
/// Subscribe to an unmapped event on the <see cref="Tray"/> module once.
/// </summary>
/// <param name="eventName">The event name</param>
/// <param name="fn">The handler</param>
void Once(string eventName, Action<object> fn);
}
}

View File

@@ -1,68 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
namespace ElectronNET.API.Interfaces
{
/// <summary>
/// Manage Browser Windows and Views
/// </summary>
public interface IWindowManager
{
/// <summary>
/// Quit when all windows are closed. (Default is true)
/// </summary>
/// <value>
/// <c>true</c> if [quit window all closed]; otherwise, <c>false</c>.
/// </value>
bool IsQuitOnWindowAllClosed { get; set; }
/// <summary>
/// Gets the browser windows.
/// </summary>
/// <value>
/// The browser windows.
/// </value>
IReadOnlyCollection<BrowserWindow> BrowserWindows { get; }
/// <summary>
/// Gets the browser views.
/// </summary>
/// <value>
/// The browser view.
/// </value>
IReadOnlyCollection<BrowserView> BrowserViews { get; }
/// <summary>
/// Creates the window asynchronous.
/// </summary>
/// <param name="loadUrl">The load URL.</param>
/// <returns></returns>
Task<BrowserWindow> CreateWindowAsync(string loadUrl = "http://localhost");
/// <summary>
/// Creates the window asynchronous.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="loadUrl">The load URL.</param>
/// <returns></returns>
Task<BrowserWindow> CreateWindowAsync(BrowserWindowOptions options, string loadUrl = "http://localhost");
/// <summary>
/// A BrowserView can be used to embed additional web content into a BrowserWindow.
/// It is like a child window, except that it is positioned relative to its owning window.
/// It is meant to be an alternative to the webview tag.
/// </summary>
/// <returns></returns>
Task<BrowserView> CreateBrowserViewAsync();
/// <summary>
/// A BrowserView can be used to embed additional web content into a BrowserWindow.
/// It is like a child window, except that it is positioned relative to its owning window.
/// It is meant to be an alternative to the webview tag.
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
Task<BrowserView> CreateBrowserViewAsync(BrowserViewConstructorOptions options);
}
}

View File

@@ -1,8 +0,0 @@
namespace ElectronNET.API
{
internal class MenuResponse
{
public string id { get; set; }
public int windowId { get; set; }
}
}

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ElectronNET.API
{
internal class ProcessHelper
{
public static void Execute(string command, string workingDirectoryPath)
{
using (Process cmd = new Process())
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
cmd.StartInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
}
else
{
// works for OSX and Linux (at least on Ubuntu)
var escapedArgs = command.Replace("\"", "\\\"");
cmd.StartInfo = new ProcessStartInfo("bash", $"-c \"{escapedArgs}\"");
}
cmd.StartInfo.RedirectStandardInput = false;
cmd.StartInfo.RedirectStandardOutput = false;
cmd.StartInfo.RedirectStandardError = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.WorkingDirectory = workingDirectoryPath;
cmd.Start();
}
}
}
}

View File

@@ -1,167 +0,0 @@
using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Threading.Tasks;
using ElectronNET.API.Interfaces;
namespace ElectronNET.API
{
/// <summary>
/// Retrieve information about screen size, displays, cursor position, etc.
/// </summary>
public sealed class Screen : IScreen
{
/// <summary>
/// Emitted when an new Display has been added.
/// </summary>
public event Action<Display> OnDisplayAdded
{
add
{
if (_onDisplayAdded == null)
{
BridgeConnector.On<Display>("screen-display-added-event" + GetHashCode(), (display) =>
{
_onDisplayAdded(display);
});
BridgeConnector.Emit("register-screen-display-added", GetHashCode());
}
_onDisplayAdded += value;
}
remove
{
_onDisplayAdded -= value;
if (_onDisplayAdded == null)
BridgeConnector.Off("screen-display-added-event" + GetHashCode());
}
}
private event Action<Display> _onDisplayAdded;
/// <summary>
/// Emitted when oldDisplay has been removed.
/// </summary>
public event Action<Display> OnDisplayRemoved
{
add
{
if (_onDisplayRemoved == null)
{
BridgeConnector.On<Display>("screen-display-removed-event" + GetHashCode(), (display) =>
{
_onDisplayRemoved(display);
});
BridgeConnector.Emit("register-screen-display-removed", GetHashCode());
}
_onDisplayRemoved += value;
}
remove
{
_onDisplayRemoved -= value;
if (_onDisplayRemoved == null)
BridgeConnector.Off("screen-display-removed-event" + GetHashCode());
}
}
private event Action<Display> _onDisplayRemoved;
/// <summary>
/// Emitted when one or more metrics change in a display.
/// The changedMetrics is an array of strings that describe the changes.
/// Possible changes are bounds, workArea, scaleFactor and rotation.
/// </summary>
public event Action<Display, string[]> OnDisplayMetricsChanged
{
add
{
if (_onDisplayMetricsChanged == null)
{
BridgeConnector.On<DisplayChanged>("screen-display-metrics-changed-event" + GetHashCode(), (args) =>
{
_onDisplayMetricsChanged(args.display, args.metrics);
});
BridgeConnector.Emit("register-screen-display-metrics-changed", GetHashCode());
}
_onDisplayMetricsChanged += value;
}
remove
{
_onDisplayMetricsChanged -= value;
if (_onDisplayMetricsChanged == null)
BridgeConnector.Off("screen-display-metrics-changed-event" + GetHashCode());
}
}
private event Action<Display, string[]> _onDisplayMetricsChanged;
private static Screen _screen;
private static readonly object _syncRoot = new();
internal Screen() { }
internal static Screen Instance
{
get
{
if (_screen == null)
{
lock (_syncRoot)
{
if (_screen == null)
{
_screen = new Screen();
}
}
}
return _screen;
}
}
/// <summary>
/// The current absolute position of the mouse pointer.
/// </summary>
/// <returns></returns>
public Task<Point> GetCursorScreenPointAsync() => BridgeConnector.OnResult<Point>("screen-getCursorScreenPoint", "screen-getCursorScreenPointCompleted");
/// <summary>
/// macOS: The height of the menu bar in pixels.
/// </summary>
/// <returns>The height of the menu bar in pixels.</returns>
public Task<int> GetMenuBarHeightAsync() => BridgeConnector.OnResult<int>("screen-getMenuBarHeight", "screen-getMenuBarHeightCompleted");
/// <summary>
/// The primary display.
/// </summary>
/// <returns></returns>
public Task<Display> GetPrimaryDisplayAsync() => BridgeConnector.OnResult<Display>("screen-getPrimaryDisplay", "screen-getPrimaryDisplayCompleted");
/// <summary>
/// An array of displays that are currently available.
/// </summary>
/// <returns>An array of displays that are currently available.</returns>
public Task<Display[]> GetAllDisplaysAsync() => BridgeConnector.OnResult<Display[]>("screen-getAllDisplays", "screen-getAllDisplaysCompleted");
/// <summary>
/// The display nearest the specified point.
/// </summary>
/// <returns>The display nearest the specified point.</returns>
public Task<Display> GetDisplayNearestPointAsync(Point point) => BridgeConnector.OnResult<Display>("screen-getDisplayNearestPoint", "screen-getDisplayNearestPointCompleted", point);
/// <summary>
/// The display that most closely intersects the provided bounds.
/// </summary>
/// <param name="rectangle"></param>
/// <returns>The display that most closely intersects the provided bounds.</returns>
public Task<Display> GetDisplayMatchingAsync(Rectangle rectangle) => BridgeConnector.OnResult<Display>("screen-getDisplayMatching", "screen-getDisplayMatchingCompleted", rectangle);
}
}

View File

@@ -1,64 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
namespace SocketIOClient.Newtonsoft.Json
{
class ByteArrayConverter : JsonConverter
{
public ByteArrayConverter()
{
Bytes = new List<byte[]>();
}
internal List<byte[]> Bytes { get; }
public override bool CanConvert(Type objectType)
{
return objectType == typeof(byte[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, global::Newtonsoft.Json.JsonSerializer serializer)
{
byte[] bytes = null;
if (reader.TokenType == JsonToken.StartObject)
{
reader.Read();
if (reader.TokenType == JsonToken.PropertyName && reader.Value?.ToString() == "_placeholder")
{
reader.Read();
if (reader.TokenType == JsonToken.Boolean && (bool)reader.Value)
{
reader.Read();
if (reader.TokenType == JsonToken.PropertyName && reader.Value?.ToString() == "num")
{
reader.Read();
if (reader.Value != null)
{
if (int.TryParse(reader.Value.ToString(), out int num))
{
bytes = Bytes[num];
reader.Read();
}
}
}
}
}
}
return bytes;
}
public override void WriteJson(JsonWriter writer, object value, global::Newtonsoft.Json.JsonSerializer serializer)
{
var source = value as byte[];
Bytes.Add(source.ToArray());
writer.WriteStartObject();
writer.WritePropertyName("_placeholder");
writer.WriteValue(true);
writer.WritePropertyName("num");
writer.WriteValue(Bytes.Count - 1);
writer.WriteEndObject();
}
}
}

View File

@@ -1,11 +0,0 @@
namespace SocketIOClient
{
public class DisconnectReason
{
public static string IOServerDisconnect = "io server disconnect";
public static string IOClientDisconnect = "io client disconnect";
public static string PingTimeout = "ping timeout";
public static string TransportClose = "transport close";
public static string TransportError = "transport error";
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace SocketIOClient
{
public delegate void OnAnyHandler(string eventName, SocketIOResponse response);
public delegate void OnOpenedHandler(string sid, int pingInterval, int pingTimeout);
//public delegate void OnDisconnectedHandler(string sid, int pingInterval, int pingTimeout);
public delegate void OnAck(int packetId, List<JsonElement> array);
public delegate void OnBinaryAck(int packetId, int totalCount, List<JsonElement> array);
public delegate void OnBinaryReceived(int packetId, int totalCount, string eventName, List<JsonElement> array);
public delegate void OnDisconnected();
public delegate void OnError(string error);
public delegate void OnEventReceived(int packetId, string eventName, List<JsonElement> array);
public delegate void OnOpened(string sid, int pingInterval, int pingTimeout);
public delegate void OnPing();
public delegate void OnPong();
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Threading;
namespace SocketIOClient.Extensions
{
internal static class CancellationTokenSourceExtensions
{
public static void TryDispose(this CancellationTokenSource cts)
{
cts?.Dispose();
}
public static void TryCancel(this CancellationTokenSource cts)
{
cts?.Cancel();
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
namespace SocketIOClient.Extensions
{
internal static class DisposableExtensions
{
public static void TryDispose(this IDisposable disposable)
{
disposable?.Dispose();
}
}
}

View File

@@ -1,17 +0,0 @@
using System;
namespace SocketIOClient.Extensions
{
internal static class EventHandlerExtensions
{
public static void TryInvoke<T>(this EventHandler<T> handler, object sender, T args)
{
handler?.Invoke(sender, args);
}
public static void TryInvoke(this EventHandler handler, object sender, EventArgs args)
{
handler?.Invoke(sender, args);
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
namespace SocketIOClient.Extensions
{
internal static class SocketIOEventExtensions
{
public static void TryInvoke(this OnAnyHandler handler, string eventName, SocketIOResponse response)
{
try
{
handler(eventName, response);
}
catch
{
// The exception is thrown by the user code, so it can be swallowed
}
}
public static void TryInvoke(this Action<SocketIOResponse> handler, SocketIOResponse response)
{
try
{
handler(response);
}
catch
{
// The exception is thrown by the user code, so it can be swallowed
}
}
}
}

View File

@@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SocketIOClient.JsonSerializer
{
class ByteArrayConverter : JsonConverter<byte[]>
{
public ByteArrayConverter()
{
Bytes = new List<byte[]>();
}
public List<byte[]> Bytes { get; }
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
byte[] bytes = null;
if (reader.TokenType == JsonTokenType.StartObject)
{
reader.Read();
if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "_placeholder")
{
reader.Read();
if (reader.TokenType == JsonTokenType.True && reader.GetBoolean())
{
reader.Read();
if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "num")
{
reader.Read();
int num = reader.GetInt32();
bytes = Bytes[num];
reader.Read();
}
}
}
}
return bytes;
}
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
{
Bytes.Add(value);
writer.WriteStartObject();
writer.WritePropertyName("_placeholder");
writer.WriteBooleanValue(true);
writer.WritePropertyName("num");
writer.WriteNumberValue(Bytes.Count - 1);
writer.WriteEndObject();
}
}
}

View File

@@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace SocketIOClient.JsonSerializer
{
public interface IJsonSerializer
{
JsonSerializeResult Serialize(object[] data);
T Deserialize<T>(string json);
T Deserialize<T>(string json, IList<byte[]> incomingBytes);
}
}

View File

@@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace SocketIOClient.JsonSerializer
{
public class JsonSerializeResult
{
public string Json { get; set; }
public IList<byte[]> Bytes { get; set; }
}
}

View File

@@ -1,53 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace SocketIOClient.JsonSerializer
{
public class SystemTextJsonSerializer : IJsonSerializer
{
public JsonSerializeResult Serialize(object[] data)
{
var converter = new ByteArrayConverter();
var options = GetOptions();
options.Converters.Add(converter);
string json = System.Text.Json.JsonSerializer.Serialize(data, options);
return new JsonSerializeResult
{
Json = json,
Bytes = converter.Bytes
};
}
public T Deserialize<T>(string json)
{
var options = GetOptions();
return System.Text.Json.JsonSerializer.Deserialize<T>(json, options);
}
public T Deserialize<T>(string json, IList<byte[]> bytes)
{
var options = GetOptions();
var converter = new ByteArrayConverter();
options.Converters.Add(converter);
converter.Bytes.AddRange(bytes);
return System.Text.Json.JsonSerializer.Deserialize<T>(json, options);
}
private JsonSerializerOptions GetOptions()
{
JsonSerializerOptions options = null;
if (OptionsProvider != null)
{
options = OptionsProvider();
}
if (options == null)
{
options = new JsonSerializerOptions();
}
return options;
}
public Func<JsonSerializerOptions> OptionsProvider { get; set; }
}
}

View File

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

View File

@@ -1,100 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace SocketIOClient.Messages
{
public class BinaryMessage : IMessage
{
public MessageType Type => MessageType.BinaryMessage;
public string Namespace { get; set; }
public string Event { get; set; }
public int Id { get; set; }
public List<JsonElement> JsonElements { get; set; }
public string Json { get; set; }
public int BinaryCount { get; set; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public void Read(string msg)
{
int index1 = msg.IndexOf('-');
BinaryCount = int.Parse(msg.Substring(0, index1));
int index2 = msg.IndexOf('[');
int index3 = msg.LastIndexOf(',', index2);
if (index3 > -1)
{
Namespace = msg.Substring(index1 + 1, index3 - index1 - 1);
int idLength = index2 - index3 - 1;
if (idLength > 0)
{
Id = int.Parse(msg.Substring(index3 + 1, idLength));
}
}
else
{
int idLength = index2 - index1 - 1;
if (idLength > 0)
{
Id = int.Parse(msg.Substring(index1 + 1, idLength));
}
}
string json = msg.Substring(index2);
var array = JsonDocument.Parse(json).RootElement.EnumerateArray();
int i = -1;
foreach (var item in array)
{
i++;
if (i == 0)
{
Event = item.GetString();
JsonElements = new List<JsonElement>();
}
else
{
JsonElements.Add(item);
}
}
}
public string Write()
{
var builder = new StringBuilder();
builder
.Append("45")
.Append(OutgoingBytes.Count)
.Append('-');
if (!string.IsNullOrEmpty(Namespace))
{
builder.Append(Namespace).Append(',');
}
if (string.IsNullOrEmpty(Json))
{
builder.Append("[\"").Append(Event).Append("\"]");
}
else
{
string data = Json.Insert(1, $"\"{Event}\",");
builder.Append(data);
}
return builder.ToString();
}
}
}

View File

@@ -1,75 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
namespace SocketIOClient.Messages
{
/// <summary>
/// The server calls the client's callback
/// </summary>
public class ClientAckMessage : IMessage
{
public MessageType Type => MessageType.AckMessage;
public string Namespace { get; set; }
public string Event { get; set; }
public List<JsonElement> JsonElements { get; set; }
public string Json { get; set; }
public int Id { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public void Read(string msg)
{
int index = msg.IndexOf('[');
int lastIndex = msg.LastIndexOf(',', index);
if (lastIndex > -1)
{
string text = msg.Substring(0, index);
Namespace = text.Substring(0, lastIndex);
Id = int.Parse(text.Substring(lastIndex + 1));
}
else
{
Id = int.Parse(msg.Substring(0, index));
}
msg = msg.Substring(index);
JsonElements = JsonDocument.Parse(msg).RootElement.EnumerateArray().ToList();
}
public string Write()
{
var builder = new StringBuilder();
builder.Append("42");
if (!string.IsNullOrEmpty(Namespace))
{
builder.Append(Namespace).Append(',');
}
builder.Append(Id);
if (string.IsNullOrEmpty(Json))
{
builder.Append("[\"").Append(Event).Append("\"]");
}
else
{
string data = Json.Insert(1, $"\"{Event}\",");
builder.Append(data);
}
return builder.ToString();
}
}
}

View File

@@ -1,82 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
namespace SocketIOClient.Messages
{
/// <summary>
/// The server calls the client's callback with binary
/// </summary>
public class ClientBinaryAckMessage : IMessage
{
public MessageType Type => MessageType.BinaryAckMessage;
public string Namespace { get; set; }
public string Event { get; set; }
public List<JsonElement> JsonElements { get; set; }
public string Json { get; set; }
public int Id { get; set; }
public int BinaryCount { get; set; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public void Read(string msg)
{
int index1 = msg.IndexOf('-');
BinaryCount = int.Parse(msg.Substring(0, index1));
int index2 = msg.IndexOf('[');
int index3 = msg.LastIndexOf(',', index2);
if (index3 > -1)
{
Namespace = msg.Substring(index1 + 1, index3 - index1 - 1);
Id = int.Parse(msg.Substring(index3 + 1, index2 - index3 - 1));
}
else
{
Id = int.Parse(msg.Substring(index1 + 1, index2 - index1 - 1));
}
string json = msg.Substring(index2);
JsonElements = JsonDocument.Parse(json).RootElement.EnumerateArray().ToList();
}
public string Write()
{
var builder = new StringBuilder();
builder
.Append("45")
.Append(OutgoingBytes.Count)
.Append('-');
if (!string.IsNullOrEmpty(Namespace))
{
builder.Append(Namespace).Append(',');
}
builder.Append(Id);
if (string.IsNullOrEmpty(Json))
{
builder.Append("[\"").Append(Event).Append("\"]");
}
else
{
string data = Json.Insert(1, $"\"{Event}\",");
builder.Append(data);
}
return builder.ToString();
}
}
}

View File

@@ -1,128 +0,0 @@
using System;
using SocketIOClient.Transport;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace SocketIOClient.Messages
{
public class ConnectedMessage : IMessage
{
public MessageType Type => MessageType.Connected;
public string Namespace { get; set; }
public string Sid { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public IEnumerable<KeyValuePair<string, string>> Query { get; set; }
public string AuthJsonStr { get; set; }
public void Read(string msg)
{
if (Eio == 3)
{
Eio3Read(msg);
}
else
{
Eio4Read(msg);
}
}
public string Write()
{
if (Eio == 3)
{
return Eio3Write();
}
return Eio4Write();
}
public void Eio4Read(string msg)
{
int index = msg.IndexOf('{');
if (index > 0)
{
Namespace = msg.Substring(0, index - 1);
msg = msg.Substring(index);
}
else
{
Namespace = string.Empty;
}
Sid = JsonDocument.Parse(msg).RootElement.GetProperty("sid").GetString();
}
public string Eio4Write()
{
var builder = new StringBuilder("40");
if (!string.IsNullOrEmpty(Namespace))
{
builder.Append(Namespace).Append(',');
}
builder.Append(AuthJsonStr);
return builder.ToString();
}
public void Eio3Read(string msg)
{
if (msg.Length >= 2)
{
int startIndex = msg.IndexOf('/');
if (startIndex == -1)
{
return;
}
int endIndex = msg.IndexOf('?', startIndex);
if (endIndex == -1)
{
endIndex = msg.IndexOf(',', startIndex);
}
if (endIndex == -1)
{
endIndex = msg.Length;
}
Namespace = msg.Substring(startIndex, endIndex);
}
}
public string Eio3Write()
{
if (string.IsNullOrEmpty(Namespace))
{
return string.Empty;
}
var builder = new StringBuilder("40");
builder.Append(Namespace);
if (Query != null)
{
int i = -1;
foreach (var item in Query)
{
i++;
if (i == 0)
{
builder.Append('?');
}
else
{
builder.Append('&');
}
builder.Append(item.Key).Append('=').Append(item.Value);
}
}
builder.Append(',');
return builder.ToString();
}
}
}

View File

@@ -1,36 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
namespace SocketIOClient.Messages
{
public class DisconnectedMessage : IMessage
{
public MessageType Type => MessageType.Disconnected;
public string Namespace { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public void Read(string msg)
{
Namespace = msg.TrimEnd(',');
}
public string Write()
{
if (string.IsNullOrEmpty(Namespace))
{
return "41";
}
return "41" + Namespace + ",";
}
}
}

View File

@@ -1,50 +0,0 @@
using SocketIOClient.Transport;
using System;
using System.Collections.Generic;
using System.Text.Json;
namespace SocketIOClient.Messages
{
public class ErrorMessage : IMessage
{
public MessageType Type => MessageType.ErrorMessage;
public string Message { get; set; }
public string Namespace { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public void Read(string msg)
{
if (Eio == 3)
{
Message = msg.Trim('"');
}
else
{
int index = msg.IndexOf('{');
if (index > 0)
{
Namespace = msg.Substring(0, index - 1);
msg = msg.Substring(index);
}
var doc = JsonDocument.Parse(msg);
Message = doc.RootElement.GetProperty("message").GetString();
}
}
public string Write()
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,97 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace SocketIOClient.Messages
{
public class EventMessage : IMessage
{
public MessageType Type => MessageType.EventMessage;
public string Namespace { get; set; }
public string Event { get; set; }
public int Id { get; set; }
public List<JsonElement> JsonElements { get; set; }
public string Json { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public void Read(string msg)
{
int index = msg.IndexOf('[');
int lastIndex = msg.LastIndexOf(',', index);
if (lastIndex > -1)
{
string text = msg.Substring(0, index);
Namespace = text.Substring(0, lastIndex);
if (index - lastIndex > 1)
{
Id = int.Parse(text.Substring(lastIndex + 1));
}
}
else
{
if (index > 0)
{
Id = int.Parse(msg.Substring(0, index));
}
}
msg = msg.Substring(index);
//int index = msg.IndexOf('[');
//if (index > 0)
//{
// Namespace = msg.Substring(0, index - 1);
// msg = msg.Substring(index);
//}
var array = JsonDocument.Parse(msg).RootElement.EnumerateArray();
int i = -1;
foreach (var item in array)
{
i++;
if (i == 0)
{
Event = item.GetString();
JsonElements = new List<JsonElement>();
}
else
{
JsonElements.Add(item);
}
}
}
public string Write()
{
var builder = new StringBuilder();
builder.Append("42");
if (!string.IsNullOrEmpty(Namespace))
{
builder.Append(Namespace).Append(',');
}
if (string.IsNullOrEmpty(Json))
{
builder.Append("[\"").Append(Event).Append("\"]");
}
else
{
string data = Json.Insert(1, $"\"{Event}\",");
builder.Append(data);
}
return builder.ToString();
}
}
}

View File

@@ -1,30 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
namespace SocketIOClient.Messages
{
public interface IMessage
{
MessageType Type { get; }
List<byte[]> OutgoingBytes { get; set; }
List<byte[]> IncomingBytes { get; set; }
int BinaryCount { get; }
int Eio { get; set; }
TransportProtocol Protocol { get; set; }
void Read(string msg);
//void Eio3WsRead(string msg);
//void Eio3HttpRead(string msg);
string Write();
//string Eio3WsWrite();
}
}

View File

@@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SocketIOClient.Messages
{
public static class MessageFactory
{
private static IMessage CreateMessage(MessageType type)
{
switch (type)
{
case MessageType.Opened:
return new OpenedMessage();
case MessageType.Ping:
return new PingMessage();
case MessageType.Pong:
return new PongMessage();
case MessageType.Connected:
return new ConnectedMessage();
case MessageType.Disconnected:
return new DisconnectedMessage();
case MessageType.EventMessage:
return new EventMessage();
case MessageType.AckMessage:
return new ClientAckMessage();
case MessageType.ErrorMessage:
return new ErrorMessage();
case MessageType.BinaryMessage:
return new BinaryMessage();
case MessageType.BinaryAckMessage:
return new ClientBinaryAckMessage();
}
return null;
}
private static readonly Dictionary<string, MessageType> _messageTypes = Enum.GetValues<MessageType>().ToDictionary(v => ((int)v).ToString(), v => v);
public static IMessage CreateMessage(int eio, string msg)
{
foreach (var (prefix,item) in _messageTypes)
{
if (msg.StartsWith(prefix))
{
IMessage result = CreateMessage(item);
if (result != null)
{
result.Eio = eio;
result.Read(msg.Substring(prefix.Length));
return result;
}
}
}
return null;
}
public static OpenedMessage CreateOpenedMessage(string msg)
{
var openedMessage = new OpenedMessage();
if (msg[0] == '0')
{
openedMessage.Eio = 4;
openedMessage.Read(msg.Substring(1));
}
else
{
openedMessage.Eio = 3;
int index = msg.IndexOf(':');
openedMessage.Read(msg.Substring(index + 2));
}
return openedMessage;
}
}
}

View File

@@ -1,16 +0,0 @@
namespace SocketIOClient.Messages
{
public enum MessageType
{
Opened,
Ping = 2,
Pong,
Connected = 40,
Disconnected,
EventMessage,
AckMessage,
ErrorMessage,
BinaryMessage,
BinaryAckMessage
}
}

View File

@@ -1,79 +0,0 @@
using System;
using System.Text.Json;
using System.Collections.Generic;
using SocketIOClient.Transport;
namespace SocketIOClient.Messages
{
public class OpenedMessage : IMessage
{
public MessageType Type => MessageType.Opened;
public string Sid { get; set; }
public string Namespace { get; set; }
public List<string> Upgrades { get; private set; }
public int PingInterval { get; private set; }
public int PingTimeout { get; private set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
private int GetInt32FromJsonElement(JsonElement element, string msg, string name)
{
var p = element.GetProperty(name);
int val;
switch (p.ValueKind)
{
case JsonValueKind.String:
val = int.Parse(p.GetString());
break;
case JsonValueKind.Number:
val = p.GetInt32();
break;
default:
throw new ArgumentException($"Invalid message: '{msg}'");
}
return val;
}
public void Read(string msg)
{
var doc = JsonDocument.Parse(msg);
var root = doc.RootElement;
Sid = root.GetProperty("sid").GetString();
PingInterval = GetInt32FromJsonElement(root, msg, "pingInterval");
PingTimeout = GetInt32FromJsonElement(root, msg, "pingTimeout");
Upgrades = new List<string>();
var upgrades = root.GetProperty("upgrades").EnumerateArray();
foreach (var item in upgrades)
{
Upgrades.Add(item.GetString());
}
}
public string Write()
{
//var builder = new StringBuilder();
//builder.Append("40");
//if (!string.IsNullOrEmpty(Namespace))
//{
// builder.Append(Namespace).Append(',');
//}
//return builder.ToString();
throw new NotImplementedException();
}
}
}

View File

@@ -1,26 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
namespace SocketIOClient.Messages
{
public class PingMessage : IMessage
{
public MessageType Type => MessageType.Ping;
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public void Read(string msg)
{
}
public string Write() => "2";
}
}

View File

@@ -1,29 +0,0 @@
using SocketIOClient.Transport;
using System;
using System.Collections.Generic;
namespace SocketIOClient.Messages
{
public class PongMessage : IMessage
{
public MessageType Type => MessageType.Pong;
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public TimeSpan Duration { get; set; }
public void Read(string msg)
{
}
public string Write() => "3";
}
}

View File

@@ -1,54 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
using System.Text;
namespace SocketIOClient.Messages
{
/// <summary>
/// The client calls the server's callback
/// </summary>
public class ServerAckMessage : IMessage
{
public MessageType Type => MessageType.AckMessage;
public string Namespace { get; set; }
public string Json { get; set; }
public int Id { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public void Read(string msg)
{
}
public string Write()
{
var builder = new StringBuilder();
builder.Append("43");
if (!string.IsNullOrEmpty(Namespace))
{
builder.Append(Namespace).Append(',');
}
builder.Append(Id);
if (string.IsNullOrEmpty(Json))
{
builder.Append("[]");
}
else
{
builder.Append(Json);
}
return builder.ToString();
}
}
}

View File

@@ -1,60 +0,0 @@
using SocketIOClient.Transport;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
namespace SocketIOClient.Messages
{
/// <summary>
/// The client calls the server's callback with binary
/// </summary>
public class ServerBinaryAckMessage : IMessage
{
public MessageType Type => MessageType.BinaryAckMessage;
public string Namespace { get; set; }
public List<JsonElement> JsonElements { get; set; }
public string Json { get; set; }
public int Id { get; set; }
public int BinaryCount { get; }
public int Eio { get; set; }
public TransportProtocol Protocol { get; set; }
public List<byte[]> OutgoingBytes { get; set; }
public List<byte[]> IncomingBytes { get; set; }
public void Read(string msg)
{
}
public string Write()
{
var builder = new StringBuilder();
builder
.Append("46")
.Append(OutgoingBytes.Count)
.Append('-');
if (!string.IsNullOrEmpty(Namespace))
{
builder.Append(Namespace).Append(',');
}
builder.Append(Id);
if (string.IsNullOrEmpty(Json))
{
builder.Append("[]");
}
else
{
builder.Append(Json);
}
return builder.ToString();
}
}
}

View File

@@ -1,56 +0,0 @@
using System;
using Newtonsoft.Json;
using SocketIOClient.JsonSerializer;
using System.Collections.Generic;
namespace SocketIOClient.Newtonsoft.Json
{
public class NewtonsoftJsonSerializer : IJsonSerializer
{
public Func<JsonSerializerSettings> JsonSerializerOptions { get; }
public JsonSerializeResult Serialize(object[] data)
{
var converter = new ByteArrayConverter();
var settings = GetOptions();
settings.Converters.Add(converter);
string json = JsonConvert.SerializeObject(data, settings);
return new JsonSerializeResult
{
Json = json,
Bytes = converter.Bytes
};
}
public T Deserialize<T>(string json)
{
var settings = GetOptions();
return JsonConvert.DeserializeObject<T>(json, settings);
}
public T Deserialize<T>(string json, IList<byte[]> bytes)
{
var converter = new ByteArrayConverter();
converter.Bytes.AddRange(bytes);
var settings = GetOptions();
settings.Converters.Add(converter);
return JsonConvert.DeserializeObject<T>(json, settings);
}
private JsonSerializerSettings GetOptions()
{
JsonSerializerSettings options = null;
if (OptionsProvider != null)
{
options = OptionsProvider();
}
if (options == null)
{
options = new JsonSerializerSettings();
}
return options;
}
public Func<JsonSerializerSettings> OptionsProvider { get; set; }
}
}

View File

@@ -1,769 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using SocketIOClient.Extensions;
using SocketIOClient.JsonSerializer;
using SocketIOClient.Messages;
using SocketIOClient.Transport;
using SocketIOClient.UriConverters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace SocketIOClient
{
/// <summary>
/// socket.io client class
/// </summary>
public class SocketIO : IDisposable
{
/// <summary>
/// Create SocketIO object with default options
/// </summary>
/// <param name="uri"></param>
public SocketIO(string uri) : this(new Uri(uri)) { }
/// <summary>
/// Create SocketIO object with options
/// </summary>
/// <param name="uri"></param>
public SocketIO(Uri uri) : this(uri, new SocketIOOptions()) { }
/// <summary>
/// Create SocketIO object with options
/// </summary>
/// <param name="uri"></param>
/// <param name="options"></param>
public SocketIO(string uri, SocketIOOptions options) : this(new Uri(uri), options) { }
/// <summary>
/// Create SocketIO object with options
/// </summary>
/// <param name="uri"></param>
/// <param name="options"></param>
public SocketIO(Uri uri, SocketIOOptions options)
{
ServerUri = uri ?? throw new ArgumentNullException("uri");
Options = options ?? throw new ArgumentNullException("options");
Initialize();
}
Uri _serverUri;
private Uri ServerUri
{
get => _serverUri;
set
{
if (_serverUri != value)
{
_serverUri = value;
if (value != null && value.AbsolutePath != "/")
{
_namespace = value.AbsolutePath;
}
}
}
}
/// <summary>
/// An unique identifier for the socket session. Set after the connect event is triggered, and updated after the reconnect event.
/// </summary>
public string Id { get; private set; }
string _namespace;
/// <summary>
/// Whether or not the socket is connected to the server.
/// </summary>
public bool Connected { get; private set; }
int _attempts;
[Obsolete]
/// <summary>
/// Whether or not the socket is disconnected from the server.
/// </summary>
public bool Disconnected => !Connected;
public SocketIOOptions Options { get; }
public IJsonSerializer JsonSerializer { get; set; }
public IUriConverter UriConverter { get; set; }
internal ILogger Logger { get; set; }
ILoggerFactory _loggerFactory;
public ILoggerFactory LoggerFactory
{
get => _loggerFactory;
set
{
_loggerFactory = value ?? throw new ArgumentNullException(nameof(LoggerFactory));
Logger = _loggerFactory.CreateLogger<SocketIO>();
}
}
public HttpClient HttpClient { get; set; }
public Func<IClientWebSocket> ClientWebSocketProvider { get; set; }
private IClientWebSocket _clientWebsocket;
BaseTransport _transport;
List<Type> _expectedExceptions;
int _packetId;
bool _isConnectCoreRunning;
Uri _realServerUri;
Exception _connectCoreException;
Dictionary<int, Action<SocketIOResponse>> _ackHandlers;
List<OnAnyHandler> _onAnyHandlers;
Dictionary<string, Action<SocketIOResponse>> _eventHandlers;
CancellationTokenSource _connectionTokenSource;
double _reconnectionDelay;
bool _hasError;
bool _isFaild;
readonly static object _connectionLock = new object();
#region Socket.IO event
public event EventHandler OnConnected;
//public event EventHandler<string> OnConnectError;
//public event EventHandler<string> OnConnectTimeout;
public event EventHandler<string> OnError;
public event EventHandler<string> OnDisconnected;
/// <summary>
/// Fired upon a successful reconnection.
/// </summary>
public event EventHandler<int> OnReconnected;
/// <summary>
/// Fired upon an attempt to reconnect.
/// </summary>
public event EventHandler<int> OnReconnectAttempt;
/// <summary>
/// Fired upon a reconnection attempt error.
/// </summary>
public event EventHandler<Exception> OnReconnectError;
/// <summary>
/// Fired when couldnt reconnect within reconnectionAttempts
/// </summary>
public event EventHandler OnReconnectFailed;
public event EventHandler OnPing;
public event EventHandler<TimeSpan> OnPong;
#endregion
#region Observable Event
//Subject<Unit> _onConnected;
//public IObservable<Unit> ConnectedObservable { get; private set; }
#endregion
private void Initialize()
{
_packetId = -1;
_ackHandlers = new Dictionary<int, Action<SocketIOResponse>>();
_eventHandlers = new Dictionary<string, Action<SocketIOResponse>>();
_onAnyHandlers = new List<OnAnyHandler>();
JsonSerializer = new SystemTextJsonSerializer();
UriConverter = new UriConverter();
HttpClient = new HttpClient();
ClientWebSocketProvider = () => new SystemNetWebSocketsClientWebSocket(Options.EIO);
_expectedExceptions = new List<Type>
{
typeof(TimeoutException),
typeof(WebSocketException),
typeof(HttpRequestException),
typeof(OperationCanceledException),
typeof(TaskCanceledException)
};
LoggerFactory = NullLoggerFactory.Instance;
}
private async Task CreateTransportAsync()
{
Options.Transport = await GetProtocolAsync();
if (Options.Transport == TransportProtocol.Polling)
{
HttpPollingHandler handler;
if (Options.EIO == 3)
handler = new Eio3HttpPollingHandler(HttpClient);
else
handler = new Eio4HttpPollingHandler(HttpClient);
_transport = new HttpTransport(HttpClient, handler, Options, JsonSerializer, Logger);
}
else
{
_clientWebsocket = ClientWebSocketProvider();
_transport = new WebSocketTransport(_clientWebsocket, Options, JsonSerializer, Logger);
}
_transport.Namespace = _namespace;
SetHeaders();
}
private void SetHeaders()
{
if (Options.ExtraHeaders != null)
{
foreach (var item in Options.ExtraHeaders)
{
_transport.AddHeader(item.Key, item.Value);
}
}
}
private void SyncExceptionToMain(Exception e)
{
_connectCoreException = e;
_isConnectCoreRunning = false;
}
private void ConnectCore()
{
DisposeForReconnect();
_reconnectionDelay = Options.ReconnectionDelay;
_connectionTokenSource = new CancellationTokenSource();
var cct = _connectionTokenSource.Token;
Task.Factory.StartNew(async () =>
{
while (true)
{
_clientWebsocket.TryDispose();
_transport.TryDispose();
CreateTransportAsync().Wait();
_realServerUri = UriConverter.GetServerUri(Options.Transport == TransportProtocol.WebSocket, ServerUri, Options.EIO, Options.Path, Options.Query);
try
{
if (cct.IsCancellationRequested)
break;
if (_attempts > 0)
OnReconnectAttempt.TryInvoke(this, _attempts);
var timeoutCts = new CancellationTokenSource(Options.ConnectionTimeout);
_transport.Subscribe(OnMessageReceived, OnErrorReceived);
await _transport.ConnectAsync(_realServerUri, timeoutCts.Token).ConfigureAwait(false);
break;
}
catch (Exception e)
{
if (_expectedExceptions.Contains(e.GetType()))
{
if (!Options.Reconnection)
{
SyncExceptionToMain(e);
throw;
}
if (_attempts > 0)
{
OnReconnectError.TryInvoke(this, e);
}
_attempts++;
if (_attempts <= Options.ReconnectionAttempts)
{
if (_reconnectionDelay < Options.ReconnectionDelayMax)
{
_reconnectionDelay += 2 * Options.RandomizationFactor;
}
if (_reconnectionDelay > Options.ReconnectionDelayMax)
{
_reconnectionDelay = Options.ReconnectionDelayMax;
}
Thread.Sleep((int)_reconnectionDelay);
}
else
{
_isFaild = true;
OnReconnectFailed.TryInvoke(this, EventArgs.Empty);
break;
}
}
else
{
SyncExceptionToMain(e);
throw;
}
}
}
_isConnectCoreRunning = false;
});
}
private async Task<TransportProtocol> GetProtocolAsync()
{
if (Options.Transport == TransportProtocol.Polling && Options.AutoUpgrade)
{
Uri uri = UriConverter.GetServerUri(false, ServerUri, Options.EIO, Options.Path, Options.Query);
try
{
string text = await HttpClient.GetStringAsync(uri);
if (text.Contains("websocket"))
{
return TransportProtocol.WebSocket;
}
}
catch (Exception e)
{
Logger.LogWarning(e, e.Message);
}
}
return Options.Transport;
}
public async Task ConnectAsync()
{
if (Connected || _isConnectCoreRunning)
return;
lock (_connectionLock)
{
if (_isConnectCoreRunning)
return;
_isConnectCoreRunning = true;
}
ConnectCore();
while (_isConnectCoreRunning)
{
await Task.Delay(100);
}
if (_connectCoreException != null)
{
Logger.LogError(_connectCoreException, _connectCoreException.Message);
throw _connectCoreException;
}
int ms = 0;
while (!Connected)
{
if (_hasError)
{
Logger.LogWarning($"Got a connection error, try to use '{nameof(OnError)}' to detect it.");
break;
}
if (_isFaild)
{
Logger.LogWarning($"Reconnect failed, try to use '{nameof(OnReconnectFailed)}' to detect it.");
break;
}
ms += 100;
if (ms > Options.ConnectionTimeout.TotalMilliseconds)
{
throw new TimeoutException();
}
await Task.Delay(100);
}
}
private void PingHandler()
{
OnPing.TryInvoke(this, EventArgs.Empty);
}
private void PongHandler(PongMessage msg)
{
OnPong.TryInvoke(this, msg.Duration);
}
private void ConnectedHandler(ConnectedMessage msg)
{
Id = msg.Sid;
Connected = true;
OnConnected.TryInvoke(this, EventArgs.Empty);
if (_attempts > 0)
{
OnReconnected.TryInvoke(this, _attempts);
}
_attempts = 0;
}
private void DisconnectedHandler()
{
_ = InvokeDisconnect(DisconnectReason.IOServerDisconnect);
}
private void EventMessageHandler(EventMessage m)
{
var res = new SocketIOResponse(m.JsonElements, this)
{
PacketId = m.Id
};
foreach (var item in _onAnyHandlers)
{
item.TryInvoke(m.Event, res);
}
if (_eventHandlers.ContainsKey(m.Event))
{
_eventHandlers[m.Event].TryInvoke(res);
}
}
private void AckMessageHandler(ClientAckMessage m)
{
if (_ackHandlers.ContainsKey(m.Id))
{
var res = new SocketIOResponse(m.JsonElements, this);
_ackHandlers[m.Id].TryInvoke(res);
_ackHandlers.Remove(m.Id);
}
}
private void ErrorMessageHandler(ErrorMessage msg)
{
_hasError = true;
OnError.TryInvoke(this, msg.Message);
}
private void BinaryMessageHandler(BinaryMessage msg)
{
var response = new SocketIOResponse(msg.JsonElements, this)
{
PacketId = msg.Id,
};
response.InComingBytes.AddRange(msg.IncomingBytes);
foreach (var item in _onAnyHandlers)
{
item.TryInvoke(msg.Event, response);
}
if (_eventHandlers.ContainsKey(msg.Event))
{
_eventHandlers[msg.Event].TryInvoke(response);
}
}
private void BinaryAckMessageHandler(ClientBinaryAckMessage msg)
{
if (_ackHandlers.ContainsKey(msg.Id))
{
var response = new SocketIOResponse(msg.JsonElements, this)
{
PacketId = msg.Id,
};
response.InComingBytes.AddRange(msg.IncomingBytes);
_ackHandlers[msg.Id].TryInvoke(response);
}
}
private void OnErrorReceived(Exception ex)
{
Logger.LogError(ex, ex.Message);
_ = InvokeDisconnect(DisconnectReason.TransportClose);
}
private void OnMessageReceived(IMessage msg)
{
try
{
switch (msg.Type)
{
case MessageType.Ping:
PingHandler();
break;
case MessageType.Pong:
PongHandler(msg as PongMessage);
break;
case MessageType.Connected:
ConnectedHandler(msg as ConnectedMessage);
break;
case MessageType.Disconnected:
DisconnectedHandler();
break;
case MessageType.EventMessage:
EventMessageHandler(msg as EventMessage);
break;
case MessageType.AckMessage:
AckMessageHandler(msg as ClientAckMessage);
break;
case MessageType.ErrorMessage:
ErrorMessageHandler(msg as ErrorMessage);
break;
case MessageType.BinaryMessage:
BinaryMessageHandler(msg as BinaryMessage);
break;
case MessageType.BinaryAckMessage:
BinaryAckMessageHandler(msg as ClientBinaryAckMessage);
break;
}
}
catch (Exception e)
{
Logger.LogError(e, e.Message);
}
}
public async Task DisconnectAsync()
{
if (Connected)
{
var msg = new DisconnectedMessage
{
Namespace = _namespace
};
try
{
await _transport.SendAsync(msg, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception e)
{
Logger.LogError(e, e.Message);
}
await InvokeDisconnect(DisconnectReason.IOClientDisconnect);
}
}
/// <summary>
/// Register a new handler for the given event.
/// </summary>
/// <param name="eventName"></param>
/// <param name="callback"></param>
public void On(string eventName, Action<SocketIOResponse> callback)
{
if (_eventHandlers.ContainsKey(eventName))
{
_eventHandlers.Remove(eventName);
}
_eventHandlers.Add(eventName, callback);
}
/// <summary>
/// Unregister a new handler for the given event.
/// </summary>
/// <param name="eventName"></param>
public void Off(string eventName)
{
if (_eventHandlers.ContainsKey(eventName))
{
_eventHandlers.Remove(eventName);
}
}
public void OnAny(OnAnyHandler handler)
{
if (handler != null)
{
_onAnyHandlers.Add(handler);
}
}
public void PrependAny(OnAnyHandler handler)
{
if (handler != null)
{
_onAnyHandlers.Insert(0, handler);
}
}
public void OffAny(OnAnyHandler handler)
{
if (handler != null)
{
_onAnyHandlers.Remove(handler);
}
}
public OnAnyHandler[] ListenersAny() => _onAnyHandlers.ToArray();
internal async Task ClientAckAsync(int packetId, CancellationToken cancellationToken, params object[] data)
{
IMessage msg;
if (data != null && data.Length > 0)
{
var result = JsonSerializer.Serialize(data);
if (result.Bytes.Count > 0)
{
msg = new ServerBinaryAckMessage
{
Id = packetId,
Namespace = _namespace,
Json = result.Json
};
msg.OutgoingBytes = new List<byte[]>(result.Bytes);
}
else
{
msg = new ServerAckMessage
{
Namespace = _namespace,
Id = packetId,
Json = result.Json
};
}
}
else
{
msg = new ServerAckMessage
{
Namespace = _namespace,
Id = packetId
};
}
await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Emits an event to the socket
/// </summary>
/// <param name="eventName"></param>
/// <param name="data">Any other parameters can be included. All serializable datastructures are supported, including byte[]</param>
/// <returns></returns>
public async Task EmitAsync(string eventName, params object[] data)
{
await EmitAsync(eventName, CancellationToken.None, data).ConfigureAwait(false);
}
public async Task EmitAsync(string eventName, CancellationToken cancellationToken, params object[] data)
{
if (data != null && data.Length > 0)
{
var result = JsonSerializer.Serialize(data);
if (result.Bytes.Count > 0)
{
var msg = new BinaryMessage
{
Namespace = _namespace,
OutgoingBytes = new List<byte[]>(result.Bytes),
Event = eventName,
Json = result.Json
};
await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
}
else
{
var msg = new EventMessage
{
Namespace = _namespace,
Event = eventName,
Json = result.Json
};
await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
}
}
else
{
var msg = new EventMessage
{
Namespace = _namespace,
Event = eventName
};
await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
}
}
/// <summary>
/// Emits an event to the socket
/// </summary>
/// <param name="eventName"></param>
/// <param name="ack">will be called with the server answer.</param>
/// <param name="data">Any other parameters can be included. All serializable datastructures are supported, including byte[]</param>
/// <returns></returns>
public async Task EmitAsync(string eventName, Action<SocketIOResponse> ack, params object[] data)
{
await EmitAsync(eventName, CancellationToken.None, ack, data).ConfigureAwait(false);
}
public async Task EmitAsync(string eventName, CancellationToken cancellationToken, Action<SocketIOResponse> ack, params object[] data)
{
_ackHandlers.Add(++_packetId, ack);
if (data != null && data.Length > 0)
{
var result = JsonSerializer.Serialize(data);
if (result.Bytes.Count > 0)
{
var msg = new ClientBinaryAckMessage
{
Event = eventName,
Namespace = _namespace,
Json = result.Json,
Id = _packetId,
OutgoingBytes = new List<byte[]>(result.Bytes)
};
await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
}
else
{
var msg = new ClientAckMessage
{
Event = eventName,
Namespace = _namespace,
Id = _packetId,
Json = result.Json
};
await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
}
}
else
{
var msg = new ClientAckMessage
{
Event = eventName,
Namespace = _namespace,
Id = _packetId
};
await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
}
}
private async Task InvokeDisconnect(string reason)
{
if (Connected)
{
Connected = false;
Id = null;
OnDisconnected.TryInvoke(this, reason);
try
{
await _transport.DisconnectAsync(CancellationToken.None).ConfigureAwait(false);
}
catch (Exception e)
{
Logger.LogError(e, e.Message);
}
if (reason != DisconnectReason.IOServerDisconnect && reason != DisconnectReason.IOClientDisconnect)
{
//In the this cases (explicit disconnection), the client will not try to reconnect and you need to manually call socket.connect().
if (Options.Reconnection)
{
ConnectCore();
}
}
}
}
public void AddExpectedException(Type type)
{
if (!_expectedExceptions.Contains(type))
{
_expectedExceptions.Add(type);
}
}
private void DisposeForReconnect()
{
_hasError = false;
_isFaild = false;
_packetId = -1;
_ackHandlers.Clear();
_connectCoreException = null;
_hasError = false;
_connectionTokenSource.TryCancel();
_connectionTokenSource.TryDispose();
}
public void Dispose()
{
HttpClient.Dispose();
_transport.TryDispose();
_ackHandlers.Clear();
_onAnyHandlers.Clear();
_eventHandlers.Clear();
_connectionTokenSource.TryCancel();
_connectionTokenSource.TryDispose();
}
}
}

View File

@@ -1,65 +0,0 @@
using SocketIOClient.Transport;
using System;
using System.Collections.Generic;
namespace SocketIOClient
{
public sealed class SocketIOOptions
{
public SocketIOOptions()
{
RandomizationFactor = 0.5;
ReconnectionDelay = 1000;
ReconnectionDelayMax = 5000;
ReconnectionAttempts = int.MaxValue;
Path = "/socket.io";
ConnectionTimeout = TimeSpan.FromSeconds(20);
Reconnection = true;
Transport = TransportProtocol.Polling;
EIO = 4;
AutoUpgrade = true;
}
public string Path { get; set; }
public TimeSpan ConnectionTimeout { get; set; }
public IEnumerable<KeyValuePair<string, string>> Query { get; set; }
/// <summary>
/// Whether to allow reconnection if accidentally disconnected
/// </summary>
public bool Reconnection { get; set; }
public double ReconnectionDelay { get; set; }
public int ReconnectionDelayMax { get; set; }
public int ReconnectionAttempts { get; set; }
double _randomizationFactor;
public double RandomizationFactor
{
get => _randomizationFactor;
set
{
if (value >= 0 && value <= 1)
{
_randomizationFactor = value;
}
else
{
throw new ArgumentException($"{nameof(RandomizationFactor)} should be greater than or equal to 0.0, and less than 1.0.");
}
}
}
public Dictionary<string, string> ExtraHeaders { get; set; }
public TransportProtocol Transport { get; set; }
public int EIO { get; set; }
public bool AutoUpgrade { get; set; }
public object Auth { get; set; }
}
}

View File

@@ -1,62 +0,0 @@
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace SocketIOClient
{
public class SocketIOResponse
{
public SocketIOResponse(IList<JsonElement> array, SocketIO socket)
{
_array = array;
InComingBytes = new List<byte[]>();
SocketIO = socket;
PacketId = -1;
}
readonly IList<JsonElement> _array;
public List<byte[]> InComingBytes { get; }
public SocketIO SocketIO { get; }
public int PacketId { get; set; }
public T GetValue<T>(int index = 0)
{
var element = GetValue(index);
string json = element.GetRawText();
return SocketIO.JsonSerializer.Deserialize<T>(json, InComingBytes);
}
public JsonElement GetValue(int index = 0) => _array[index];
public int Count => _array.Count;
public override string ToString()
{
var builder = new StringBuilder();
builder.Append('[');
foreach (var item in _array)
{
builder.Append(item.GetRawText());
if (_array.IndexOf(item) < _array.Count - 1)
{
builder.Append(',');
}
}
builder.Append(']');
return builder.ToString();
}
public async Task CallbackAsync(params object[] data)
{
await SocketIO.ClientAckAsync(PacketId, CancellationToken.None, data).ConfigureAwait(false);
}
public async Task CallbackAsync(CancellationToken cancellationToken, params object[] data)
{
await SocketIO.ClientAckAsync(PacketId, cancellationToken, data).ConfigureAwait(false);
}
}
}

View File

@@ -1,245 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reactive.Subjects;
using Microsoft.Extensions.Logging;
using SocketIOClient.JsonSerializer;
using SocketIOClient.Messages;
using SocketIOClient.UriConverters;
namespace SocketIOClient.Transport
{
public abstract class BaseTransport : IObserver<string>, IObserver<byte[]>, IObservable<IMessage>, IDisposable
{
public BaseTransport(SocketIOOptions options, IJsonSerializer jsonSerializer, ILogger logger)
{
Options = options;
MessageSubject = new Subject<IMessage>();
JsonSerializer = jsonSerializer;
UriConverter = new UriConverter();
_messageQueue = new Queue<IMessage>();
_logger = logger;
}
DateTime _pingTime;
readonly Queue<IMessage> _messageQueue;
readonly ILogger _logger;
protected SocketIOOptions Options { get; }
protected Subject<IMessage> MessageSubject { get; }
protected IJsonSerializer JsonSerializer { get; }
protected CancellationTokenSource PingTokenSource { get; private set; }
protected OpenedMessage OpenedMessage { get; private set; }
public string Namespace { get; set; }
public IUriConverter UriConverter { get; set; }
public async Task SendAsync(IMessage msg, CancellationToken cancellationToken)
{
msg.Eio = Options.EIO;
msg.Protocol = Options.Transport;
var payload = new Payload
{
Text = msg.Write()
};
if (msg.OutgoingBytes != null)
{
payload.Bytes = msg.OutgoingBytes;
}
await SendAsync(payload, cancellationToken).ConfigureAwait(false);
}
protected virtual async Task OpenAsync(OpenedMessage msg)
{
OpenedMessage = msg;
if (Options.EIO == 3 && string.IsNullOrEmpty(Namespace))
{
return;
}
var connectMsg = new ConnectedMessage
{
Namespace = Namespace,
Eio = Options.EIO,
Query = Options.Query,
};
if (Options.EIO == 4)
{
if (Options.Auth != null)
{
connectMsg.AuthJsonStr = JsonSerializer.Serialize(new[] { Options.Auth }).Json.TrimStart('[').TrimEnd(']');
}
}
for (int i = 1; i <= 3; i++)
{
try
{
await SendAsync(connectMsg, CancellationToken.None).ConfigureAwait(false);
break;
}
catch (Exception e)
{
if (i == 3)
OnError(e);
else
await Task.Delay(TimeSpan.FromMilliseconds(Math.Pow(2, i) * 100));
}
}
}
/// <summary>
/// <para>Eio3 ping is sent by the client</para>
/// <para>Eio4 ping is sent by the server</para>
/// </summary>
/// <param name="cancellationToken"></param>
private void StartPing(CancellationToken cancellationToken)
{
_logger.LogDebug($"[Ping] Interval: {OpenedMessage.PingInterval}");
Task.Factory.StartNew(async () =>
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(OpenedMessage.PingInterval);
if (cancellationToken.IsCancellationRequested)
{
break;
}
try
{
var ping = new PingMessage();
_logger.LogDebug($"[Ping] Sending");
await SendAsync(ping, CancellationToken.None).ConfigureAwait(false);
_logger.LogDebug($"[Ping] Has been sent");
_pingTime = DateTime.Now;
MessageSubject.OnNext(ping);
}
catch (Exception e)
{
_logger.LogDebug($"[Ping] Failed to send, {e.Message}");
MessageSubject.OnError(e);
break;
}
}
}, TaskCreationOptions.LongRunning);
}
public abstract Task ConnectAsync(Uri uri, CancellationToken cancellationToken);
public abstract Task DisconnectAsync(CancellationToken cancellationToken);
public abstract void AddHeader(string key, string val);
public virtual void Dispose()
{
MessageSubject.Dispose();
_messageQueue.Clear();
if (PingTokenSource != null)
{
PingTokenSource.Cancel();
PingTokenSource.Dispose();
}
}
public abstract Task SendAsync(Payload payload, CancellationToken cancellationToken);
public void OnCompleted()
{
throw new NotImplementedException();
}
public void OnError(Exception error)
{
MessageSubject.OnError(error);
}
public void OnNext(string text)
{
_logger.LogDebug($"[Receive] {text}");
var msg = MessageFactory.CreateMessage(Options.EIO, text);
if (msg == null)
{
return;
}
if (msg.BinaryCount > 0)
{
msg.IncomingBytes = new List<byte[]>(msg.BinaryCount);
_messageQueue.Enqueue(msg);
return;
}
if (msg.Type == MessageType.Opened)
{
OpenAsync(msg as OpenedMessage).ConfigureAwait(false);
}
if (Options.EIO == 3)
{
if (msg.Type == MessageType.Connected)
{
var connectMsg = msg as ConnectedMessage;
connectMsg.Sid = OpenedMessage.Sid;
if ((string.IsNullOrEmpty(Namespace) && string.IsNullOrEmpty(connectMsg.Namespace)) || connectMsg.Namespace == Namespace)
{
if (PingTokenSource != null)
{
PingTokenSource.Cancel();
}
PingTokenSource = new CancellationTokenSource();
StartPing(PingTokenSource.Token);
}
else
{
return;
}
}
else if (msg.Type == MessageType.Pong)
{
var pong = msg as PongMessage;
pong.Duration = DateTime.Now - _pingTime;
}
}
MessageSubject.OnNext(msg);
if (msg.Type == MessageType.Ping)
{
_pingTime = DateTime.Now;
try
{
SendAsync(new PongMessage(), CancellationToken.None).ConfigureAwait(false);
MessageSubject.OnNext(new PongMessage
{
Eio = Options.EIO,
Protocol = Options.Transport,
Duration = DateTime.Now - _pingTime
});
}
catch (Exception e)
{
OnError(e);
}
}
}
public void OnNext(byte[] bytes)
{
_logger.LogDebug($"[Receive] binary message");
if (_messageQueue.Count > 0)
{
var msg = _messageQueue.Peek();
msg.IncomingBytes.Add(bytes);
if (msg.IncomingBytes.Count == msg.BinaryCount)
{
MessageSubject.OnNext(msg);
_messageQueue.Dequeue();
}
}
}
public IDisposable Subscribe(IObserver<IMessage> observer)
{
return MessageSubject.Subscribe(observer);
}
}
}

View File

@@ -1,76 +0,0 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Net.Http.Headers;
namespace SocketIOClient.Transport
{
public class Eio3HttpPollingHandler : HttpPollingHandler
{
public Eio3HttpPollingHandler(HttpClient httpClient) : base(httpClient) { }
public override async Task PostAsync(string uri, IEnumerable<byte[]> bytes, CancellationToken cancellationToken)
{
var list = new List<byte>();
foreach (var item in bytes)
{
list.Add(1);
var length = SplitInt(item.Length + 1).Select(x => (byte)x);
list.AddRange(length);
list.Add(byte.MaxValue);
list.Add(4);
list.AddRange(item);
}
var content = new ByteArrayContent(list.ToArray());
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
await HttpClient.PostAsync(AppendRandom(uri), content, cancellationToken).ConfigureAwait(false);
}
private List<int> SplitInt(int number)
{
List<int> list = new List<int>();
while (number > 0)
{
list.Add(number % 10);
number /= 10;
}
list.Reverse();
return list;
}
protected override void ProduceText(string text)
{
int p = 0;
while (true)
{
int index = text.IndexOf(':', p);
if (index == -1)
{
break;
}
if (int.TryParse(text.Substring(p, index - p), out int length))
{
string msg = text.Substring(index + 1, length);
TextSubject.OnNext(msg);
}
else
{
break;
}
p = index + length + 1;
if (p >= text.Length)
{
break;
}
}
}
public override Task PostAsync(string uri, string content, CancellationToken cancellationToken)
{
content = content.Length + ":" + content;
return base.PostAsync(uri, content, cancellationToken);
}
}
}

View File

@@ -1,48 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SocketIOClient.Transport
{
public class Eio4HttpPollingHandler : HttpPollingHandler
{
public Eio4HttpPollingHandler(HttpClient httpClient) : base(httpClient) { }
const char Separator = '\u001E'; //1E 
public override async Task PostAsync(string uri, IEnumerable<byte[]> bytes, CancellationToken cancellationToken)
{
var builder = new StringBuilder();
foreach (var item in bytes)
{
builder.Append('b').Append(Convert.ToBase64String(item)).Append(Separator);
}
if (builder.Length == 0)
{
return;
}
string text = builder.ToString().TrimEnd(Separator);
await PostAsync(uri, text, cancellationToken);
}
protected override void ProduceText(string text)
{
string[] items = text.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
foreach (var item in items)
{
if (item[0] == 'b')
{
byte[] bytes = Convert.FromBase64String(item.Substring(1));
BytesSubject.OnNext(bytes);
}
else
{
TextSubject.OnNext(item);
}
}
}
}
}

View File

@@ -1,118 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SocketIOClient.Transport
{
public abstract class HttpPollingHandler : IHttpPollingHandler
{
public HttpPollingHandler(HttpClient httpClient)
{
HttpClient = httpClient;
TextSubject = new Subject<string>();
BytesSubject = new Subject<byte[]>();
TextObservable = TextSubject.AsObservable();
BytesObservable = BytesSubject.AsObservable();
}
protected HttpClient HttpClient { get; }
protected Subject<string> TextSubject{get;}
protected Subject<byte[]> BytesSubject{get;}
public IObservable<string> TextObservable { get; }
public IObservable<byte[]> BytesObservable { get; }
protected string AppendRandom(string uri)
{
return uri + "&t=" + DateTimeOffset.Now.ToUnixTimeSeconds();
}
public async Task GetAsync(string uri, CancellationToken cancellationToken)
{
var req = new HttpRequestMessage(HttpMethod.Get, AppendRandom(uri));
var resMsg = await HttpClient.SendAsync(req, cancellationToken).ConfigureAwait(false);
if (!resMsg.IsSuccessStatusCode)
{
throw new HttpRequestException($"Response status code does not indicate success: {resMsg.StatusCode}");
}
await ProduceMessageAsync(resMsg).ConfigureAwait(false);
}
public async Task SendAsync(HttpRequestMessage req, CancellationToken cancellationToken)
{
var resMsg = await HttpClient.SendAsync(req, cancellationToken).ConfigureAwait(false);
if (!resMsg.IsSuccessStatusCode)
{
throw new HttpRequestException($"Response status code does not indicate success: {resMsg.StatusCode}");
}
await ProduceMessageAsync(resMsg).ConfigureAwait(false);
}
public async virtual Task PostAsync(string uri, string content, CancellationToken cancellationToken)
{
var httpContent = new StringContent(content);
var resMsg = await HttpClient.PostAsync(AppendRandom(uri), httpContent, cancellationToken).ConfigureAwait(false);
await ProduceMessageAsync(resMsg).ConfigureAwait(false);
}
public abstract Task PostAsync(string uri, IEnumerable<byte[]> bytes, CancellationToken cancellationToken);
private async Task ProduceMessageAsync(HttpResponseMessage resMsg)
{
if (resMsg.Content.Headers.ContentType.MediaType == "application/octet-stream")
{
byte[] bytes = await resMsg.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
ProduceBytes(bytes);
}
else
{
string text = await resMsg.Content.ReadAsStringAsync().ConfigureAwait(false);
ProduceText(text);
}
}
protected abstract void ProduceText(string text);
private void ProduceBytes(byte[] bytes)
{
int i = 0;
while (bytes.Length > i + 4)
{
byte type = bytes[i];
var builder = new StringBuilder();
i++;
while (bytes[i] != byte.MaxValue)
{
builder.Append(bytes[i]);
i++;
}
i++;
int length = int.Parse(builder.ToString());
if (type == 0)
{
var buffer = new byte[length];
Buffer.BlockCopy(bytes, i, buffer, 0, buffer.Length);
TextSubject.OnNext(Encoding.UTF8.GetString(buffer));
}
else if (type == 1)
{
var buffer = new byte[length - 1];
Buffer.BlockCopy(bytes, i + 1, buffer, 0, buffer.Length);
BytesSubject.OnNext(buffer);
}
i += length;
}
}
public void Dispose()
{
TextSubject.Dispose();
BytesSubject.Dispose();
}
}
}

View File

@@ -1,121 +0,0 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SocketIOClient.JsonSerializer;
using SocketIOClient.Messages;
namespace SocketIOClient.Transport
{
public class HttpTransport : BaseTransport
{
public HttpTransport(HttpClient http,
IHttpPollingHandler pollingHandler,
SocketIOOptions options,
IJsonSerializer jsonSerializer,
ILogger logger) : base(options, jsonSerializer, logger)
{
_http = http;
_httpPollingHandler = pollingHandler;
_httpPollingHandler.TextObservable.Subscribe(this);
_httpPollingHandler.BytesObservable.Subscribe(this);
}
string _httpUri;
CancellationTokenSource _pollingTokenSource;
readonly HttpClient _http;
readonly IHttpPollingHandler _httpPollingHandler;
private void StartPolling(CancellationToken cancellationToken)
{
Task.Factory.StartNew(async () =>
{
int retry = 0;
while (!cancellationToken.IsCancellationRequested)
{
if (!_httpUri.Contains("&sid="))
{
await Task.Delay(20);
continue;
}
try
{
await _httpPollingHandler.GetAsync(_httpUri, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception e)
{
retry++;
if (retry >= 3)
{
MessageSubject.OnError(e);
break;
}
await Task.Delay(100 * (int)Math.Pow(2, retry));
}
}
}, TaskCreationOptions.LongRunning);
}
public override async Task ConnectAsync(Uri uri, CancellationToken cancellationToken)
{
var req = new HttpRequestMessage(HttpMethod.Get, uri);
// if (_options.ExtraHeaders != null)
// {
// foreach (var item in _options.ExtraHeaders)
// {
// req.Headers.Add(item.Key, item.Value);
// }
// }
_httpUri = uri.ToString();
await _httpPollingHandler.SendAsync(req, new CancellationTokenSource(Options.ConnectionTimeout).Token).ConfigureAwait(false);
if (_pollingTokenSource != null)
{
_pollingTokenSource.Cancel();
}
_pollingTokenSource = new CancellationTokenSource();
StartPolling(_pollingTokenSource.Token);
}
public override Task DisconnectAsync(CancellationToken cancellationToken)
{
_pollingTokenSource.Cancel();
if (PingTokenSource != null)
{
PingTokenSource.Cancel();
}
return Task.CompletedTask;
}
public override void AddHeader(string key, string val)
{
_http.DefaultRequestHeaders.Add(key, val);
}
public override void Dispose()
{
base.Dispose();
_httpPollingHandler.Dispose();
}
public override async Task SendAsync(Payload payload, CancellationToken cancellationToken)
{
await _httpPollingHandler.PostAsync(_httpUri, payload.Text, cancellationToken);
if (payload.Bytes != null && payload.Bytes.Count > 0)
{
await _httpPollingHandler.PostAsync(_httpUri, payload.Bytes, cancellationToken);
}
}
protected override async Task OpenAsync(OpenedMessage msg)
{
//if (!_httpUri.Contains("&sid="))
//{
//}
_httpUri += "&sid=" + msg.Sid;
await base.OpenAsync(msg);
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace SocketIOClient.Transport
{
public interface IClientWebSocket : IDisposable
{
IObservable<string> TextObservable { get; }
IObservable<byte[]> BytesObservable { get; }
Task ConnectAsync(Uri uri, CancellationToken cancellationToken);
Task DisconnectAsync(CancellationToken cancellationToken);
Task SendAsync(byte[] bytes, TransportMessageType type, bool endOfMessage, CancellationToken cancellationToken);
void AddHeader(string key, string val);
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace SocketIOClient.Transport
{
public interface IHttpPollingHandler : IDisposable
{
IObservable<string> TextObservable { get; }
IObservable<byte[]> BytesObservable { get; }
Task GetAsync(string uri, CancellationToken cancellationToken);
Task SendAsync(HttpRequestMessage req, CancellationToken cancellationToken);
Task PostAsync(string uri, string content, CancellationToken cancellationToken);
Task PostAsync(string uri, IEnumerable<byte[]> bytes, CancellationToken cancellationToken);
}
}

View File

@@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace SocketIOClient.Transport
{
public class Payload
{
public string Text { get; set; }
public List<byte[]> Bytes { get; set; }
}
}

View File

@@ -1,143 +0,0 @@
using System;
using System.Net.WebSockets;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SocketIOClient.Transport
{
public class SystemNetWebSocketsClientWebSocket : IClientWebSocket
{
public SystemNetWebSocketsClientWebSocket(int eio)
{
_eio = eio;
_textSubject = new Subject<string>();
_bytesSubject = new Subject<byte[]>();
TextObservable = _textSubject.AsObservable();
BytesObservable = _bytesSubject.AsObservable();
_ws = new ClientWebSocket();
_listenCancellation = new CancellationTokenSource();
_sendLock = new SemaphoreSlim(1, 1);
}
const int ReceiveChunkSize = 1024 * 8;
readonly int _eio;
readonly ClientWebSocket _ws;
readonly Subject<string> _textSubject;
readonly Subject<byte[]> _bytesSubject;
readonly CancellationTokenSource _listenCancellation;
readonly SemaphoreSlim _sendLock;
public IObservable<string> TextObservable { get; }
public IObservable<byte[]> BytesObservable { get; }
private void Listen()
{
Task.Factory.StartNew(async() =>
{
while (true)
{
if (_listenCancellation.IsCancellationRequested)
{
break;
}
var buffer = new byte[ReceiveChunkSize];
int count = 0;
WebSocketReceiveResult result = null;
while (_ws.State == WebSocketState.Open)
{
var subBuffer = new byte[ReceiveChunkSize];
try
{
result = await _ws.ReceiveAsync(new ArraySegment<byte>(subBuffer), CancellationToken.None).ConfigureAwait(false);
// resize
if (buffer.Length - count < result.Count)
{
Array.Resize(ref buffer, buffer.Length + result.Count);
}
Buffer.BlockCopy(subBuffer, 0, buffer, count, result.Count);
count += result.Count;
if (result.EndOfMessage)
{
break;
}
}
catch (Exception e)
{
_textSubject.OnError(e);
break;
}
}
if (result == null)
{
break;
}
switch (result.MessageType)
{
case WebSocketMessageType.Text:
string text = Encoding.UTF8.GetString(buffer, 0, count);
_textSubject.OnNext(text);
break;
case WebSocketMessageType.Binary:
byte[] bytes;
if (_eio == 3)
{
bytes = new byte[count - 1];
Buffer.BlockCopy(buffer, 1, bytes, 0, bytes.Length);
}
else
{
bytes = new byte[count];
Buffer.BlockCopy(buffer, 0, bytes, 0, bytes.Length);
}
_bytesSubject.OnNext(bytes);
break;
case WebSocketMessageType.Close:
_textSubject.OnError(new WebSocketException("Received a Close message"));
break;
}
}
});
}
public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken)
{
await _ws.ConnectAsync(uri, cancellationToken);
Listen();
}
public async Task DisconnectAsync(CancellationToken cancellationToken)
{
await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken);
}
public async Task SendAsync(byte[] bytes, TransportMessageType type, bool endOfMessage, CancellationToken cancellationToken)
{
var msgType = WebSocketMessageType.Text;
if (type == TransportMessageType.Binary)
{
msgType = WebSocketMessageType.Binary;
}
await _ws.SendAsync(new ArraySegment<byte>(bytes), msgType, endOfMessage, cancellationToken).ConfigureAwait(false);
}
public void AddHeader(string key, string val)
{
_ws.Options.SetRequestHeader(key, val);
}
public void Dispose()
{
_textSubject.Dispose();
_bytesSubject.Dispose();
_ws.Dispose();
}
}
}

View File

@@ -1,8 +0,0 @@
namespace SocketIOClient.Transport
{
public enum TransportMessageType
{
Text = 0,
Binary = 1
}
}

View File

@@ -1,8 +0,0 @@
namespace SocketIOClient.Transport
{
public enum TransportProtocol
{
Polling,
WebSocket
}
}

View File

@@ -1,92 +0,0 @@
using System;
using System.Reactive.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SocketIOClient.JsonSerializer;
namespace SocketIOClient.Transport
{
public class WebSocketTransport : BaseTransport
{
public WebSocketTransport(IClientWebSocket ws, SocketIOOptions options, IJsonSerializer jsonSerializer, ILogger logger)
: base(options, jsonSerializer, logger)
{
_ws = ws;
_sendLock = new SemaphoreSlim(1, 1);
_ws.TextObservable.Subscribe(this);
_ws.BytesObservable.Subscribe(this);
}
const int ReceiveChunkSize = 1024 * 8;
const int SendChunkSize = 1024 * 8;
readonly IClientWebSocket _ws;
readonly SemaphoreSlim _sendLock;
private async Task SendAsync(TransportMessageType type, byte[] bytes, CancellationToken cancellationToken)
{
try
{
await _sendLock.WaitAsync().ConfigureAwait(false);
if (type == TransportMessageType.Binary && Options.EIO == 3)
{
byte[] buffer = new byte[bytes.Length + 1];
buffer[0] = 4;
Buffer.BlockCopy(bytes, 0, buffer, 1, bytes.Length);
bytes = buffer;
}
int pages = (int)Math.Ceiling(bytes.Length * 1.0 / SendChunkSize);
for (int i = 0; i < pages; i++)
{
int offset = i * SendChunkSize;
int length = SendChunkSize;
if (offset + length > bytes.Length)
{
length = bytes.Length - offset;
}
byte[] subBuffer = new byte[length];
Buffer.BlockCopy(bytes, offset, subBuffer, 0, subBuffer.Length);
bool endOfMessage = pages - 1 == i;
await _ws.SendAsync(subBuffer, type, endOfMessage, cancellationToken).ConfigureAwait(false);
}
}
finally
{
_sendLock.Release();
}
}
public override async Task ConnectAsync(Uri uri, CancellationToken cancellationToken)
{
await _ws.ConnectAsync(uri, cancellationToken);
}
public override async Task DisconnectAsync(CancellationToken cancellationToken)
{
await _ws.DisconnectAsync(cancellationToken);
}
public override async Task SendAsync(Payload payload, CancellationToken cancellationToken)
{
byte[] bytes = Encoding.UTF8.GetBytes(payload.Text);
await SendAsync(TransportMessageType.Text, bytes, cancellationToken);
if (payload.Bytes != null)
{
foreach (var item in payload.Bytes)
{
await SendAsync(TransportMessageType.Binary, item, cancellationToken);
}
}
}
public override void AddHeader(string key, string val) => _ws.AddHeader(key, val);
public override void Dispose()
{
base.Dispose();
_sendLock.Dispose();
}
}
}

View File

@@ -1,10 +0,0 @@
using System;
using System.Collections.Generic;
namespace SocketIOClient.UriConverters
{
public interface IUriConverter
{
Uri GetServerUri(bool ws, Uri serverUri, int eio, string path, IEnumerable<KeyValuePair<string, string>> queryParams);
}
}

View File

@@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SocketIOClient.UriConverters
{
public class UriConverter : IUriConverter
{
public Uri GetServerUri(bool ws, Uri serverUri, int eio, string path, IEnumerable<KeyValuePair<string, string>> queryParams)
{
var builder = new StringBuilder();
if (serverUri.Scheme == "https" || serverUri.Scheme == "wss")
{
builder.Append(ws ? "wss://" : "https://");
}
else if (serverUri.Scheme == "http" || serverUri.Scheme == "ws")
{
builder.Append(ws ? "ws://" : "http://");
}
else
{
throw new ArgumentException("Only supports 'http, https, ws, wss' protocol");
}
builder.Append(serverUri.Host);
if (!serverUri.IsDefaultPort)
{
builder.Append(":").Append(serverUri.Port);
}
if (string.IsNullOrEmpty(path))
{
builder.Append("/socket.io");
}
else
{
builder.Append(path);
}
builder
.Append("/?EIO=")
.Append(eio)
.Append("&transport=")
.Append(ws ? "websocket" : "polling");
if (queryParams != null)
{
foreach (var item in queryParams)
{
builder.Append('&').Append(item.Key).Append('=').Append(item.Value);
}
}
return new Uri(builder.ToString());
}
}
}

View File

@@ -1,214 +0,0 @@
using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Threading.Tasks;
namespace ElectronNET.API
{
/// <summary>
/// Render and control web pages.
/// </summary>
public class WebContents
{
/// <summary>
/// Gets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public int Id { get; private set; }
/// <summary>
/// Manage browser sessions, cookies, cache, proxy settings, etc.
/// </summary>
public Session Session { get; internal set; }
/// <summary>
/// Emitted when the renderer process crashes or is killed.
/// </summary>
public event Action<bool> OnCrashed
{
add
{
if (_crashed == null)
{
BridgeConnector.On<bool>("webContents-crashed" + Id, (killed) =>
{
_crashed(killed);
});
BridgeConnector.Emit("register-webContents-crashed", Id);
}
_crashed += value;
}
remove
{
_crashed -= value;
if (_crashed == null)
BridgeConnector.Off("webContents-crashed" + Id);
}
}
private event Action<bool> _crashed;
/// <summary>
/// Emitted when the navigation is done, i.e. the spinner of the tab has
/// stopped spinning, and the onload event was dispatched.
/// </summary>
public event Action OnDidFinishLoad
{
add
{
if (_didFinishLoad == null)
{
BridgeConnector.On("webContents-didFinishLoad" + Id, () =>
{
_didFinishLoad();
});
BridgeConnector.Emit("register-webContents-didFinishLoad", Id);
}
_didFinishLoad += value;
}
remove
{
_didFinishLoad -= value;
if (_didFinishLoad == null)
BridgeConnector.Off("webContents-didFinishLoad" + Id);
}
}
private event Action _didFinishLoad;
internal WebContents(int id)
{
Id = id;
Session = new Session(id);
}
/// <summary>
/// Opens the devtools.
/// </summary>
public void OpenDevTools()
{
BridgeConnector.Emit("webContentsOpenDevTools", Id);
}
/// <summary>
/// Opens the devtools.
/// </summary>
/// <param name="openDevToolsOptions"></param>
public void OpenDevTools(OpenDevToolsOptions openDevToolsOptions)
{
BridgeConnector.Emit("webContentsOpenDevTools", Id, openDevToolsOptions);
}
/// <summary>
/// Get system printers.
/// </summary>
/// <returns>printers</returns>
public Task<PrinterInfo[]> GetPrintersAsync()
{
return BridgeConnector.OnResult<PrinterInfo[]>("webContents-getPrinters", "webContents-getPrinters-completed" + Id, Id);
}
/// <summary>
/// Prints window's web page.
/// </summary>
/// <param name="options"></param>
/// <returns>success</returns>
public Task<bool> PrintAsync(PrintOptions options = null) => options is null ? BridgeConnector.OnResult<bool>("webContents-print", "webContents-print-completed" + Id, Id, "")
: BridgeConnector.OnResult<bool>("webContents-print", "webContents-print-completed" + Id, Id, options);
/// <summary>
/// Prints window's web page as PDF with Chromium's preview printing custom
/// settings.The landscape will be ignored if @page CSS at-rule is used in the web page.
/// By default, an empty options will be regarded as: Use page-break-before: always;
/// CSS style to force to print to a new page.
/// </summary>
/// <param name="path"></param>
/// <param name="options"></param>
/// <returns>success</returns>
public Task<bool> PrintToPDFAsync(string path, PrintToPDFOptions options = null) => options is null ? BridgeConnector.OnResult<bool>("webContents-printToPDF", "webContents-printToPDF-completed" + Id, Id, "", path)
: BridgeConnector.OnResult<bool>("webContents-printToPDF", "webContents-printToPDF-completed" + Id, Id, options, path);
/// <summary>
/// Is used to get the Url of the loaded page.
/// It's usefull if a web-server redirects you and you need to know where it redirects. For instance, It's useful in case of Implicit Authorization.
/// </summary>
/// <returns>URL of the loaded page</returns>
public Task<string> GetUrl()
{
return BridgeConnector.OnResult<string>("webContents-getUrl", "webContents-getUrl" + Id, Id);
}
/// <summary>
/// The async method will resolve when the page has finished loading,
/// and rejects if the page fails to load.
///
/// A noop rejection handler is already attached, which avoids unhandled rejection
/// errors.
///
/// Loads the `url` in the window. The `url` must contain the protocol prefix, e.g.
/// the `http://` or `file://`. If the load should bypass http cache then use the
/// `pragma` header to achieve it.
/// </summary>
/// <param name="url"></param>
public Task LoadURLAsync(string url)
{
return LoadURLAsync(url, new LoadURLOptions());
}
/// <summary>
/// The async method will resolve when the page has finished loading,
/// and rejects if the page fails to load.
///
/// A noop rejection handler is already attached, which avoids unhandled rejection
/// errors.
///
/// Loads the `url` in the window. The `url` must contain the protocol prefix, e.g.
/// the `http://` or `file://`. If the load should bypass http cache then use the
/// `pragma` header to achieve it.
/// </summary>
/// <param name="url"></param>
/// <param name="options"></param>
public Task LoadURLAsync(string url, LoadURLOptions options)
{
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.On("webContents-loadURL-complete" + Id, () =>
{
BridgeConnector.Off("webContents-loadURL-complete" + Id);
BridgeConnector.Off("webContents-loadURL-error" + Id);
taskCompletionSource.SetResult();
});
BridgeConnector.On<string>("webContents-loadURL-error" + Id, (error) =>
{
BridgeConnector.Off("webContents-loadURL-error" + Id);
BridgeConnector.Off("webContents-loadURL-complete" + Id);
taskCompletionSource.SetException(new InvalidOperationException(error.ToString()));
});
BridgeConnector.Emit("webContents-loadURL", Id, url, options);
return taskCompletionSource.Task;
}
/// <summary>
/// Inserts CSS into the web page.
/// See: https://www.electronjs.org/docs/api/web-contents#contentsinsertcsscss-options
/// Works for both BrowserWindows and BrowserViews.
/// </summary>
/// <param name="isBrowserWindow">Whether the webContents belong to a BrowserWindow or not (the other option is a BrowserView)</param>
/// <param name="path">Absolute path to the CSS file location</param>
public void InsertCSS(bool isBrowserWindow, string path)
{
BridgeConnector.Emit("webContents-insertCSS", Id, isBrowserWindow, path);
}
}
}

View File

@@ -1,7 +0,0 @@
{
"runtimeOptions": {
"configProperties": {
"System.Drawing.EnableUnixSupport": true
}
}
}

View File

@@ -1,134 +0,0 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ElectronNET.CLI.Commands.Actions
{
public static class GetTargetPlatformInformation
{
public struct GetTargetPlatformInformationResult
{
public string NetCorePublishRid { get; set; }
public string ElectronPackerPlatform { get; set; }
}
public static GetTargetPlatformInformationResult Do(string desiredPlatform, string specifiedPlatfromFromCustom)
{
string netCorePublishRid = string.Empty;
string electronPackerPlatform = string.Empty;
switch (desiredPlatform)
{
case "win":
netCorePublishRid = "win-x64";
electronPackerPlatform = "win";
break;
case "osx":
netCorePublishRid = "osx-x64";
electronPackerPlatform = "mac";
break;
case "osx-arm64":
netCorePublishRid = "osx-arm64";
electronPackerPlatform = "mac";
//Check to see if .net 6 is installed:
if (!Dotnet6Installed())
{
throw new ArgumentException("You are using a dotnet version older than dotnet 6. Compiling for osx-arm64 requires that dotnet 6 or greater is installed and targeted by your project.", "osx-arm64");
}
//Warn for .net 6 targeting:
Console.WriteLine("Please ensure that your project targets .net 6 or greater. Otherwise you may experience an error compiling for osx-arm64.");
break;
case "linux":
netCorePublishRid = "linux-x64";
electronPackerPlatform = "linux";
break;
case "linux-arm":
netCorePublishRid = "linux-arm";
electronPackerPlatform = "linux";
break;
case "custom":
var splittedSpecified = specifiedPlatfromFromCustom.Split(';');
netCorePublishRid = splittedSpecified[0];
electronPackerPlatform = splittedSpecified[1];
break;
default:
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
netCorePublishRid = $"win-x{(Environment.Is64BitOperatingSystem ? "64" : "86")}";
electronPackerPlatform = "win";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
if (RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64) && Dotnet6Installed())
{
//Warn for .net 6 targeting:
Console.WriteLine("Please ensure that your project targets .net 6. Otherwise you may experience an error.");
//Apple Silicon Mac:
netCorePublishRid = "osx-arm64";
electronPackerPlatform = "mac";
}
else
{
//Intel Mac:
netCorePublishRid = "osx-x64";
electronPackerPlatform = "mac";
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
netCorePublishRid = "linux-x64";
electronPackerPlatform = "linux";
}
break;
}
return new GetTargetPlatformInformationResult()
{
ElectronPackerPlatform = electronPackerPlatform,
NetCorePublishRid = netCorePublishRid
};
}
/// <summary>
/// Checks to see if dotnet 6 or greater is installed.
/// Required for MacOS arm targeting.
/// Note that an error may still occur if the project being compiled does not target dotnet 6 or greater.
/// </summary>
/// <returns>
/// Returns true if dotnet 6 or greater is installed.
/// </returns>
private static bool Dotnet6Installed()
{
//check for .net 6:
//execute dotnet --list-sdks to get versions
Process process = new Process();
process.StartInfo.FileName = "dotnet";
process.StartInfo.Arguments = "--list-sdks";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.Start();
string standard_output;
bool dotnet6Exists = false;
//get command output:
while ((standard_output = process.StandardOutput.ReadLine()) != null)
{
//get the major version and see if its greater than or equal to 6
int majorVer = int.Parse(standard_output.Split(".")[0]);
if (majorVer >= 6)
{
dotnet6Exists = true;
break;
}
}
process.WaitForExit();
return dotnet6Exists;
}
}
}

View File

@@ -1,13 +0,0 @@
{
"profiles": {
"Build Test App": {
"commandName": "Project",
"commandLineArgs": "build /target win /PublishSingleFile false /PublishReadyToRun false",
"workingDirectory": "$(SolutionDir)ElectronNET.WebApp"
},
"Start Test App": {
"commandName": "Project",
"commandLineArgs": "start /project-path \"$(SolutionDir)ElectronNET.WebApp\" /watch"
}
}
}

View File

@@ -1 +0,0 @@
node_modules

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