@xevy/heny-connect 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/bin/heny-connect.mjs +65 -5
- package/package.json +2 -1
- package/windows/heny-connect-tray.ps1 +306 -0
- package/windows/heny-error.ico +0 -0
- package/windows/heny-paused.ico +0 -0
- package/windows/heny.ico +0 -0
package/README.md
CHANGED
|
@@ -23,6 +23,34 @@ npx @xevy/heny-connect run
|
|
|
23
23
|
|
|
24
24
|
The process sends a heartbeat every 30 seconds. Keep it running for the device to remain available in Heny.
|
|
25
25
|
|
|
26
|
+
## Windows system tray
|
|
27
|
+
|
|
28
|
+
Install the tray companion after pairing:
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
heny-connect install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Heny Connect starts at Windows sign-in and runs without an open terminal. The tray menu shows the connection state and provides these controls:
|
|
35
|
+
|
|
36
|
+
- Open Heny
|
|
37
|
+
- Pause or resume the connection
|
|
38
|
+
- Reconnect now
|
|
39
|
+
- Turn start-at-sign-in on or off
|
|
40
|
+
- Quit until the next sign-in
|
|
41
|
+
|
|
42
|
+
To start the tray for the current session without installing start-at-sign-in:
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
heny-connect tray
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
To remove start-at-sign-in:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
heny-connect uninstall
|
|
52
|
+
```
|
|
53
|
+
|
|
26
54
|
## Check status
|
|
27
55
|
|
|
28
56
|
```bash
|
package/bin/heny-connect.mjs
CHANGED
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Pairing state is stored in ~/.heny-connect.json (mode 600).
|
|
11
11
|
*/
|
|
12
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
12
13
|
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
13
14
|
import { homedir, hostname, platform, release } from "node:os";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
15
17
|
|
|
16
18
|
const STATE_FILE = join(process.env.HENY_CONNECT_HOME || homedir(), ".heny-connect.json");
|
|
19
|
+
const CLI_FILE = fileURLToPath(import.meta.url);
|
|
20
|
+
const TRAY_SCRIPT = join(dirname(CLI_FILE), "..", "windows", "heny-connect-tray.ps1");
|
|
17
21
|
const args = process.argv.slice(2);
|
|
18
22
|
const command = args[0] || "help";
|
|
19
23
|
const opt = (name, fallback) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] : fallback; };
|
|
@@ -46,7 +50,15 @@ async function pair() {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
async function heartbeat(state, detail) {
|
|
49
|
-
return call(state.server, "/api/devices/heartbeat", { state:
|
|
53
|
+
return call(state.server, "/api/devices/heartbeat", { state: detail.state, detail: detail.message }, state.token);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function sendHeartbeat(stateName = "available", message = `${hostname()}: browser ready`) {
|
|
57
|
+
const state = await loadState();
|
|
58
|
+
if (!state) { console.error("Not paired. Run: heny-connect pair --code … --server …"); process.exitCode = 2; return false; }
|
|
59
|
+
if (!["online", "available", "offline"].includes(stateName)) { console.error("State must be online, available, or offline."); process.exitCode = 2; return false; }
|
|
60
|
+
await heartbeat(state, { state: stateName, message });
|
|
61
|
+
return true;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
async function run() {
|
|
@@ -55,7 +67,7 @@ async function run() {
|
|
|
55
67
|
const every = Number(opt("every", 30)) * 1000;
|
|
56
68
|
const beats = Number(opt("beats", 0)); let count = 0;
|
|
57
69
|
const tick = async () => {
|
|
58
|
-
try { await heartbeat(state, `${hostname()}: browser ready`); count += 1; console.log(`${new Date().toISOString()} heartbeat ok (${count})`); }
|
|
70
|
+
try { await heartbeat(state, { state: "available", message: `${hostname()}: browser ready` }); count += 1; console.log(`${new Date().toISOString()} heartbeat ok (${count})`); }
|
|
59
71
|
catch (err) { console.error(`${new Date().toISOString()} heartbeat failed: ${err.message}`); if (/Unknown device token/.test(err.message)) process.exit(3); }
|
|
60
72
|
if (beats && count >= beats) process.exit(0);
|
|
61
73
|
};
|
|
@@ -63,6 +75,12 @@ async function run() {
|
|
|
63
75
|
if (!beats || count < beats) setInterval(tick, every);
|
|
64
76
|
}
|
|
65
77
|
|
|
78
|
+
async function heartbeatOnce() {
|
|
79
|
+
const stateName = opt("state", "available");
|
|
80
|
+
const message = opt("detail", stateName === "offline" ? `${hostname()}: paused locally` : `${hostname()}: browser ready`);
|
|
81
|
+
if (await sendHeartbeat(stateName, message)) console.log(`Heartbeat reported ${stateName}.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
async function status() {
|
|
67
85
|
const state = await loadState();
|
|
68
86
|
if (!state) { console.log("Not paired."); return; }
|
|
@@ -70,5 +88,47 @@ async function status() {
|
|
|
70
88
|
console.log(res.ok ? JSON.stringify(await res.json(), null, 2) : `Server answered HTTP ${res.status}`);
|
|
71
89
|
}
|
|
72
90
|
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
function requireWindows() {
|
|
92
|
+
if (platform() === "win32") return true;
|
|
93
|
+
console.error("The system tray is available on Windows.");
|
|
94
|
+
process.exitCode = 2;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function trayArgs(mode) {
|
|
99
|
+
const values = ["-NoLogo", "-NoProfile", "-STA", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-File", TRAY_SCRIPT, "-NodePath", process.execPath, "-CliPath", CLI_FILE];
|
|
100
|
+
if (mode === "install") values.push("-Install");
|
|
101
|
+
if (mode === "uninstall") values.push("-Uninstall");
|
|
102
|
+
return values;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function tray() {
|
|
106
|
+
if (!requireWindows()) return;
|
|
107
|
+
if (!await loadState()) { console.error("Not paired. Pair this computer before starting the tray."); process.exitCode = 2; return; }
|
|
108
|
+
const child = spawn("powershell.exe", trayArgs("tray"), { detached: true, stdio: "ignore", windowsHide: true });
|
|
109
|
+
child.unref();
|
|
110
|
+
console.log("Heny Connect is running in the system tray.");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function install() {
|
|
114
|
+
if (!requireWindows()) return;
|
|
115
|
+
if (!await loadState()) { console.error("Not paired. Pair this computer before installing the tray."); process.exitCode = 2; return; }
|
|
116
|
+
const result = spawnSync("powershell.exe", trayArgs("install"), { encoding: "utf8", windowsHide: true });
|
|
117
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
118
|
+
if (result.status !== 0) { if (result.stderr) process.stderr.write(result.stderr); process.exitCode = result.status || 1; }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function uninstall() {
|
|
122
|
+
if (!requireWindows()) return;
|
|
123
|
+
const result = spawnSync("powershell.exe", trayArgs("uninstall"), { encoding: "utf8", windowsHide: true });
|
|
124
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
125
|
+
if (result.status !== 0) { if (result.stderr) process.stderr.write(result.stderr); process.exitCode = result.status || 1; }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const commands = { pair, run, status, heartbeat: heartbeatOnce, tray, install, uninstall, help: async () => console.log("Commands: pair --code <6 digits> --server <url> [--run] | run [--every 30] [--beats N] | status | tray | install | uninstall") };
|
|
129
|
+
try {
|
|
130
|
+
await (commands[command] || commands.help)();
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error(err.message);
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xevy/heny-connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Pair a computer with Heny and keep its device presence online.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
+
"windows",
|
|
11
12
|
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory = $true)]
|
|
3
|
+
[string] $NodePath,
|
|
4
|
+
|
|
5
|
+
[Parameter(Mandatory = $true)]
|
|
6
|
+
[string] $CliPath,
|
|
7
|
+
|
|
8
|
+
[string] $IconPath,
|
|
9
|
+
[switch] $Install,
|
|
10
|
+
[switch] $Uninstall
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
$ErrorActionPreference = "Stop"
|
|
14
|
+
$taskName = "HenyConnect"
|
|
15
|
+
$powershellPath = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
|
|
16
|
+
if (-not $IconPath) { $IconPath = Join-Path $PSScriptRoot "heny.ico" }
|
|
17
|
+
|
|
18
|
+
function Get-TaskArguments {
|
|
19
|
+
return "-NoLogo -NoProfile -STA -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$PSCommandPath`" -NodePath `"$NodePath`" -CliPath `"$CliPath`" -IconPath `"$IconPath`""
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function Install-HenyTask {
|
|
23
|
+
param([bool] $StartNow)
|
|
24
|
+
|
|
25
|
+
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
26
|
+
if ($existing) {
|
|
27
|
+
Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
28
|
+
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
32
|
+
$action = New-ScheduledTaskAction -Execute $powershellPath -Argument (Get-TaskArguments)
|
|
33
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser
|
|
34
|
+
$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited
|
|
35
|
+
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -StartWhenAvailable
|
|
36
|
+
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null
|
|
37
|
+
if ($StartNow) { Start-ScheduledTask -TaskName $taskName }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if ($Uninstall) {
|
|
41
|
+
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
42
|
+
if ($existing) {
|
|
43
|
+
Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
44
|
+
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
|
|
45
|
+
}
|
|
46
|
+
Write-Output "Heny Connect start-at-sign-in removed. Quit the tray to end the current session."
|
|
47
|
+
exit 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if ($Install) {
|
|
51
|
+
Install-HenyTask -StartNow $true
|
|
52
|
+
Write-Output "Heny Connect installed. It is running in the system tray and will start at sign-in."
|
|
53
|
+
exit 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
$createdNew = $false
|
|
57
|
+
$mutex = [Threading.Mutex]::new($true, "Local\HenyConnectTray", [ref] $createdNew)
|
|
58
|
+
if (-not $createdNew) {
|
|
59
|
+
$mutex.Dispose()
|
|
60
|
+
exit 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
64
|
+
Add-Type -AssemblyName System.Drawing
|
|
65
|
+
[Windows.Forms.Application]::EnableVisualStyles()
|
|
66
|
+
|
|
67
|
+
$iconAvailable = [Drawing.Icon]::new($IconPath)
|
|
68
|
+
$iconPaused = [Drawing.Icon]::new((Join-Path $PSScriptRoot "heny-paused.ico"))
|
|
69
|
+
$iconError = [Drawing.Icon]::new((Join-Path $PSScriptRoot "heny-error.ico"))
|
|
70
|
+
$notifyIcon = [Windows.Forms.NotifyIcon]::new()
|
|
71
|
+
$notifyIcon.Icon = $iconPaused
|
|
72
|
+
$notifyIcon.Text = "Heny Connect - Connecting"
|
|
73
|
+
$notifyIcon.Visible = $true
|
|
74
|
+
|
|
75
|
+
$menu = [Windows.Forms.ContextMenuStrip]::new()
|
|
76
|
+
$titleItem = [Windows.Forms.ToolStripMenuItem]::new("Heny Connect")
|
|
77
|
+
$titleItem.Enabled = $false
|
|
78
|
+
$titleItem.Font = [Drawing.Font]::new($titleItem.Font, [Drawing.FontStyle]::Bold)
|
|
79
|
+
$statusItem = [Windows.Forms.ToolStripMenuItem]::new("Status: Connecting...")
|
|
80
|
+
$statusItem.Enabled = $false
|
|
81
|
+
$openItem = [Windows.Forms.ToolStripMenuItem]::new("Open Heny")
|
|
82
|
+
$pauseItem = [Windows.Forms.ToolStripMenuItem]::new("Pause")
|
|
83
|
+
$reconnectItem = [Windows.Forms.ToolStripMenuItem]::new("Reconnect now")
|
|
84
|
+
$startItem = [Windows.Forms.ToolStripMenuItem]::new("Start at sign-in")
|
|
85
|
+
$startItem.CheckOnClick = $false
|
|
86
|
+
$startItem.Checked = [bool] (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue)
|
|
87
|
+
$quitItem = [Windows.Forms.ToolStripMenuItem]::new("Quit")
|
|
88
|
+
|
|
89
|
+
$null = $menu.Items.Add($titleItem)
|
|
90
|
+
$null = $menu.Items.Add($statusItem)
|
|
91
|
+
$null = $menu.Items.Add([Windows.Forms.ToolStripSeparator]::new())
|
|
92
|
+
$null = $menu.Items.Add($openItem)
|
|
93
|
+
$null = $menu.Items.Add($pauseItem)
|
|
94
|
+
$null = $menu.Items.Add($reconnectItem)
|
|
95
|
+
$null = $menu.Items.Add($startItem)
|
|
96
|
+
$null = $menu.Items.Add([Windows.Forms.ToolStripSeparator]::new())
|
|
97
|
+
$null = $menu.Items.Add($quitItem)
|
|
98
|
+
$notifyIcon.ContextMenuStrip = $menu
|
|
99
|
+
|
|
100
|
+
$script:paused = $false
|
|
101
|
+
$script:heartbeatProcess = $null
|
|
102
|
+
$script:heartbeatState = $null
|
|
103
|
+
$script:nextHeartbeat = Get-Date
|
|
104
|
+
$script:lastStatus = "connecting"
|
|
105
|
+
$script:exiting = $false
|
|
106
|
+
|
|
107
|
+
function Set-HenyStatus {
|
|
108
|
+
param(
|
|
109
|
+
[ValidateSet("available", "paused", "connecting", "error")]
|
|
110
|
+
[string] $State,
|
|
111
|
+
[string] $Detail
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
$label = switch ($State) {
|
|
115
|
+
"available" { "Available" }
|
|
116
|
+
"paused" { "Paused" }
|
|
117
|
+
"connecting" { "Connecting..." }
|
|
118
|
+
"error" { "Connection error" }
|
|
119
|
+
}
|
|
120
|
+
$statusItem.Text = "Status: $label"
|
|
121
|
+
if ($Detail) { $statusItem.ToolTipText = $Detail }
|
|
122
|
+
$notifyIcon.Text = "Heny Connect - $label"
|
|
123
|
+
$notifyIcon.Icon = switch ($State) {
|
|
124
|
+
"available" { $iconAvailable }
|
|
125
|
+
"error" { $iconError }
|
|
126
|
+
default { $iconPaused }
|
|
127
|
+
}
|
|
128
|
+
$script:lastStatus = $State
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function Get-HenyUrl {
|
|
132
|
+
try {
|
|
133
|
+
$homePath = if ($env:HENY_CONNECT_HOME) { $env:HENY_CONNECT_HOME } else { [Environment]::GetFolderPath("UserProfile") }
|
|
134
|
+
$statePath = Join-Path $homePath ".heny-connect.json"
|
|
135
|
+
$state = Get-Content -LiteralPath $statePath -Raw | ConvertFrom-Json
|
|
136
|
+
return ([string] $state.server).TrimEnd("/") + "/desktop"
|
|
137
|
+
} catch {
|
|
138
|
+
return "https://heny.vyte.dev/desktop"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function Stop-HeartbeatProcess {
|
|
143
|
+
if (-not $script:heartbeatProcess) { return }
|
|
144
|
+
try {
|
|
145
|
+
if (-not $script:heartbeatProcess.HasExited) {
|
|
146
|
+
$script:heartbeatProcess.Kill()
|
|
147
|
+
$script:heartbeatProcess.WaitForExit(2000) | Out-Null
|
|
148
|
+
}
|
|
149
|
+
$script:heartbeatProcess.Dispose()
|
|
150
|
+
} catch {}
|
|
151
|
+
$script:heartbeatProcess = $null
|
|
152
|
+
$script:heartbeatState = $null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function Start-Heartbeat {
|
|
156
|
+
param(
|
|
157
|
+
[ValidateSet("available", "offline")]
|
|
158
|
+
[string] $State = "available"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if ($script:heartbeatProcess) { return }
|
|
162
|
+
$detail = if ($State -eq "offline") { "$env:COMPUTERNAME`: paused in tray" } else { "$env:COMPUTERNAME`: browser ready" }
|
|
163
|
+
$startInfo = [Diagnostics.ProcessStartInfo]::new()
|
|
164
|
+
$startInfo.FileName = $NodePath
|
|
165
|
+
$startInfo.Arguments = "`"$CliPath`" heartbeat --state $State --detail `"$detail`""
|
|
166
|
+
$startInfo.UseShellExecute = $false
|
|
167
|
+
$startInfo.CreateNoWindow = $true
|
|
168
|
+
$startInfo.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
|
|
169
|
+
$startInfo.RedirectStandardOutput = $true
|
|
170
|
+
$startInfo.RedirectStandardError = $true
|
|
171
|
+
$process = [Diagnostics.Process]::new()
|
|
172
|
+
$process.StartInfo = $startInfo
|
|
173
|
+
try {
|
|
174
|
+
if (-not $process.Start()) { throw "The heartbeat process did not start." }
|
|
175
|
+
$script:heartbeatProcess = $process
|
|
176
|
+
$script:heartbeatState = $State
|
|
177
|
+
if ($State -eq "available") { Set-HenyStatus -State "connecting" -Detail "Contacting Heny" }
|
|
178
|
+
} catch {
|
|
179
|
+
$process.Dispose()
|
|
180
|
+
Set-HenyStatus -State "error" -Detail $_.Exception.Message
|
|
181
|
+
$script:nextHeartbeat = (Get-Date).AddSeconds(30)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function Complete-Heartbeat {
|
|
186
|
+
if (-not $script:heartbeatProcess -or -not $script:heartbeatProcess.HasExited) { return }
|
|
187
|
+
$process = $script:heartbeatProcess
|
|
188
|
+
$state = $script:heartbeatState
|
|
189
|
+
$stdout = $process.StandardOutput.ReadToEnd().Trim()
|
|
190
|
+
$stderr = $process.StandardError.ReadToEnd().Trim()
|
|
191
|
+
$exitCode = $process.ExitCode
|
|
192
|
+
$process.Dispose()
|
|
193
|
+
$script:heartbeatProcess = $null
|
|
194
|
+
$script:heartbeatState = $null
|
|
195
|
+
$script:nextHeartbeat = (Get-Date).AddSeconds(30)
|
|
196
|
+
|
|
197
|
+
if ($exitCode -eq 0) {
|
|
198
|
+
if ($state -eq "offline" -or $script:paused) {
|
|
199
|
+
Set-HenyStatus -State "paused" -Detail "Paused on this computer"
|
|
200
|
+
} else {
|
|
201
|
+
Set-HenyStatus -State "available" -Detail "Connected to Heny"
|
|
202
|
+
}
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
$detail = if ($stderr) { $stderr } elseif ($stdout) { $stdout } else { "Heartbeat exited with code $exitCode" }
|
|
207
|
+
$previousStatus = $script:lastStatus
|
|
208
|
+
Set-HenyStatus -State "error" -Detail $detail
|
|
209
|
+
if ($previousStatus -ne "error") {
|
|
210
|
+
$notifyIcon.ShowBalloonTip(4000, "Heny Connect", $detail, [Windows.Forms.ToolTipIcon]::Warning)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function Send-OfflineAndWait {
|
|
215
|
+
Stop-HeartbeatProcess
|
|
216
|
+
$startInfo = [Diagnostics.ProcessStartInfo]::new()
|
|
217
|
+
$startInfo.FileName = $NodePath
|
|
218
|
+
$startInfo.Arguments = "`"$CliPath`" heartbeat --state offline --detail `"$env:COMPUTERNAME`: tray closed`""
|
|
219
|
+
$startInfo.UseShellExecute = $false
|
|
220
|
+
$startInfo.CreateNoWindow = $true
|
|
221
|
+
$startInfo.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
|
|
222
|
+
$process = [Diagnostics.Process]::Start($startInfo)
|
|
223
|
+
if ($process) {
|
|
224
|
+
$process.WaitForExit(10000) | Out-Null
|
|
225
|
+
if (-not $process.HasExited) { $process.Kill() }
|
|
226
|
+
$process.Dispose()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
$openHeny = {
|
|
231
|
+
Start-Process (Get-HenyUrl)
|
|
232
|
+
}
|
|
233
|
+
$openItem.Add_Click($openHeny)
|
|
234
|
+
$notifyIcon.Add_DoubleClick($openHeny)
|
|
235
|
+
|
|
236
|
+
$pauseItem.Add_Click({
|
|
237
|
+
if ($script:paused) {
|
|
238
|
+
$script:paused = $false
|
|
239
|
+
$pauseItem.Text = "Pause"
|
|
240
|
+
$script:nextHeartbeat = Get-Date
|
|
241
|
+
Set-HenyStatus -State "connecting" -Detail "Resuming"
|
|
242
|
+
} else {
|
|
243
|
+
$script:paused = $true
|
|
244
|
+
$pauseItem.Text = "Resume"
|
|
245
|
+
Stop-HeartbeatProcess
|
|
246
|
+
Start-Heartbeat -State "offline"
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
$reconnectItem.Add_Click({
|
|
251
|
+
$script:paused = $false
|
|
252
|
+
$pauseItem.Text = "Pause"
|
|
253
|
+
Stop-HeartbeatProcess
|
|
254
|
+
$script:nextHeartbeat = Get-Date
|
|
255
|
+
Set-HenyStatus -State "connecting" -Detail "Reconnecting"
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
$startItem.Add_Click({
|
|
259
|
+
try {
|
|
260
|
+
if ($startItem.Checked) {
|
|
261
|
+
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
|
262
|
+
$startItem.Checked = $false
|
|
263
|
+
$notifyIcon.ShowBalloonTip(2500, "Heny Connect", "Start at sign-in is off.", [Windows.Forms.ToolTipIcon]::Info)
|
|
264
|
+
} else {
|
|
265
|
+
Install-HenyTask -StartNow $false
|
|
266
|
+
$startItem.Checked = $true
|
|
267
|
+
$notifyIcon.ShowBalloonTip(2500, "Heny Connect", "Start at sign-in is on.", [Windows.Forms.ToolTipIcon]::Info)
|
|
268
|
+
}
|
|
269
|
+
} catch {
|
|
270
|
+
Set-HenyStatus -State "error" -Detail $_.Exception.Message
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
$quitItem.Add_Click({
|
|
275
|
+
$script:exiting = $true
|
|
276
|
+
$timer.Stop()
|
|
277
|
+
Send-OfflineAndWait
|
|
278
|
+
$notifyIcon.Visible = $false
|
|
279
|
+
[Windows.Forms.Application]::ExitThread()
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
$timer = [Windows.Forms.Timer]::new()
|
|
283
|
+
$timer.Interval = 1000
|
|
284
|
+
$timer.Add_Tick({
|
|
285
|
+
Complete-Heartbeat
|
|
286
|
+
if (-not $script:paused -and -not $script:heartbeatProcess -and (Get-Date) -ge $script:nextHeartbeat) {
|
|
287
|
+
Start-Heartbeat -State "available"
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
$timer.Start()
|
|
291
|
+
Start-Heartbeat -State "available"
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
[Windows.Forms.Application]::Run()
|
|
295
|
+
} finally {
|
|
296
|
+
$timer.Stop()
|
|
297
|
+
Stop-HeartbeatProcess
|
|
298
|
+
$notifyIcon.Visible = $false
|
|
299
|
+
$notifyIcon.Dispose()
|
|
300
|
+
$menu.Dispose()
|
|
301
|
+
$iconAvailable.Dispose()
|
|
302
|
+
$iconPaused.Dispose()
|
|
303
|
+
$iconError.Dispose()
|
|
304
|
+
if ($createdNew) { $mutex.ReleaseMutex() }
|
|
305
|
+
$mutex.Dispose()
|
|
306
|
+
}
|
|
Binary file
|
|
Binary file
|
package/windows/heny.ico
ADDED
|
Binary file
|