mirror of
https://github.com/ElectronNET/Electron.NET.git
synced 2026-02-07 21:25:18 +00:00
Compare commits
52 Commits
legacy/mai
...
23.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d22532488e | ||
|
|
30558dea5f | ||
|
|
464eaca593 | ||
|
|
df3bd122df | ||
|
|
eabcc3a6b6 | ||
|
|
2b5435381f | ||
|
|
0d63383899 | ||
|
|
f31fe19652 | ||
|
|
0a80367e3e | ||
|
|
34761456ad | ||
|
|
e4e96bbcc4 | ||
|
|
1253df3d9a | ||
|
|
b8b634beb3 | ||
|
|
94dc82ec62 | ||
|
|
53ccf4d302 | ||
|
|
bf0bdc8386 | ||
|
|
73a3e331dc | ||
|
|
a15db713ad | ||
|
|
e2d03d6818 | ||
|
|
0dc8369fb3 | ||
|
|
77b7141513 | ||
|
|
dbf85c6f14 | ||
|
|
c67f117bc7 | ||
|
|
9746edb936 | ||
|
|
23f4d39a30 | ||
|
|
05ac4a1886 | ||
|
|
33ac4edbe3 | ||
|
|
e5f9bae64f | ||
|
|
437404d6cc | ||
|
|
1ae2f1de93 | ||
|
|
6bfd0c33af | ||
|
|
6311d55a75 | ||
|
|
7b522c1779 | ||
|
|
b1c08f5865 | ||
|
|
a3f19055b9 | ||
|
|
ef9a95d9e9 | ||
|
|
2367035acd | ||
|
|
3470a70572 | ||
|
|
1365918efd | ||
|
|
e909de54af | ||
|
|
a2514ed5bc | ||
|
|
b453278803 | ||
|
|
a244382383 | ||
|
|
a82e714ef8 | ||
|
|
b339485fdc | ||
|
|
551635867d | ||
|
|
941b8cf5c2 | ||
|
|
06b01f75da | ||
|
|
e4b1f6586e | ||
|
|
0657a274d4 | ||
|
|
e3acc79c4f | ||
|
|
73c1d1cd46 |
@@ -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
2
.github/FUNDING.yml
vendored
@@ -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
47
.github/workflows/ci.yml
vendored
Normal 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
5
.gitignore
vendored
@@ -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
144
.nuke/build.schema.json
Normal 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
4
.nuke/parameters.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "./build.schema.json",
|
||||
"Solution": "src/ElectronNET.sln"
|
||||
}
|
||||
@@ -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
37
.vscode/tasks.json
vendored
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
16
.vscode/tasks.json.old
vendored
16
.vscode/tasks.json.old
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
53
Changelog.md
53
Changelog.md
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
/// Doesn’t 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
public class SecondInstanceResponse
|
||||
{
|
||||
public string[] args { get; set; }
|
||||
|
||||
public string workingDirectory { get;set;}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace ElectronNET.API.Entities
|
||||
{
|
||||
public class TrayClickEventResponse
|
||||
{
|
||||
public TrayClickEventArgs eventArgs { get; set; }
|
||||
public Rectangle bounds { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 Chrome’s 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 application’s 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 instance’s 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 Chrome’s 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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
/// Doesn’t 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();
|
||||
}
|
||||
}
|
||||
@@ -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 don’t 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 = "");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 dock’s badging area.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
void SetBadge(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string to be displayed in the dock’s 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);
|
||||
}
|
||||
}
|
||||
@@ -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 don’t
|
||||
/// 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 don’t 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 desktop’s default manner.
|
||||
/// (For example, mailto: URLs in the user’s 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 desktop’s default manner.
|
||||
/// (For example, mailto: URLs in the user’s 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace ElectronNET.API
|
||||
{
|
||||
internal class MenuResponse
|
||||
{
|
||||
public string id { get; set; }
|
||||
public int windowId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SocketIOClient.Extensions
|
||||
{
|
||||
internal static class DisposableExtensions
|
||||
{
|
||||
public static void TryDispose(this IDisposable disposable)
|
||||
{
|
||||
disposable?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + ",";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace SocketIOClient.Messages
|
||||
{
|
||||
public enum MessageType
|
||||
{
|
||||
Opened,
|
||||
Ping = 2,
|
||||
Pong,
|
||||
Connected = 40,
|
||||
Disconnected,
|
||||
EventMessage,
|
||||
AckMessage,
|
||||
ErrorMessage,
|
||||
BinaryMessage,
|
||||
BinaryAckMessage
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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 couldn’t 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace SocketIOClient.Transport
|
||||
{
|
||||
public enum TransportMessageType
|
||||
{
|
||||
Text = 0,
|
||||
Binary = 1
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace SocketIOClient.Transport
|
||||
{
|
||||
public enum TransportProtocol
|
||||
{
|
||||
Polling,
|
||||
WebSocket
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"configProperties": {
|
||||
"System.Drawing.EnableUnixSupport": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
node_modules
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user