@xevy/heny-connect 0.1.0 → 0.3.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.
@@ -0,0 +1,264 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)][string] $NodePath,
3
+ [Parameter(Mandatory = $true)][string] $CliPath,
4
+ [Parameter(Mandatory = $true)][string] $HomePath,
5
+ [string] $IconPath,
6
+ [switch] $Install,
7
+ [switch] $Uninstall
8
+ )
9
+
10
+ $ErrorActionPreference = "Stop"
11
+ $taskName = "HenyConnect"
12
+ $powershellPath = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
13
+ $statusPath = Join-Path $HomePath "worker-status.json"
14
+ $trayStatusPath = Join-Path $HomePath "tray-status.json"
15
+ $pausePath = Join-Path $HomePath "paused"
16
+ $stopPath = Join-Path $HomePath "stopping"
17
+ if (-not $IconPath) { $IconPath = Join-Path $PSScriptRoot "heny.ico" }
18
+
19
+ function Get-TaskArguments {
20
+ return "-NoLogo -NoProfile -STA -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$PSCommandPath`" -NodePath `"$NodePath`" -CliPath `"$CliPath`" -HomePath `"$HomePath`" -IconPath `"$IconPath`""
21
+ }
22
+
23
+ function Install-HenyTask {
24
+ param([bool] $StartNow)
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
+ $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent().Name
31
+ $action = New-ScheduledTaskAction -Execute $powershellPath -Argument (Get-TaskArguments)
32
+ $trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser
33
+ $principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited
34
+ $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew -StartWhenAvailable
35
+ Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null
36
+ if ($StartNow) { Start-ScheduledTask -TaskName $taskName }
37
+ }
38
+
39
+ if ($Uninstall) {
40
+ $existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
41
+ if ($existing) {
42
+ Stop-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
43
+ Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
44
+ }
45
+ Write-Output "Heny Connect start-at-sign-in removed. Quit the tray to end the current session."
46
+ exit 0
47
+ }
48
+
49
+ if ($Install) {
50
+ Install-HenyTask -StartNow $true
51
+ Write-Output "Heny Connect installed. It is running in the system tray and will start at sign-in."
52
+ exit 0
53
+ }
54
+
55
+ $createdNew = $false
56
+ $mutex = [Threading.Mutex]::new($true, "Local\HenyConnectTray", [ref] $createdNew)
57
+ if (-not $createdNew) { $mutex.Dispose(); exit 0 }
58
+
59
+ Add-Type -AssemblyName System.Windows.Forms
60
+ Add-Type -AssemblyName System.Drawing
61
+ [Windows.Forms.Application]::EnableVisualStyles()
62
+
63
+ $iconAvailable = [Drawing.Icon]::new($IconPath)
64
+ $iconPaused = [Drawing.Icon]::new((Join-Path $PSScriptRoot "heny-paused.ico"))
65
+ $iconError = [Drawing.Icon]::new((Join-Path $PSScriptRoot "heny-error.ico"))
66
+ $notifyIcon = [Windows.Forms.NotifyIcon]::new()
67
+ $notifyIcon.Icon = $iconPaused
68
+ $notifyIcon.Text = "Heny Connect - Browser starting"
69
+ $notifyIcon.Visible = $true
70
+
71
+ $menu = [Windows.Forms.ContextMenuStrip]::new()
72
+ $titleItem = [Windows.Forms.ToolStripMenuItem]::new("Heny Connect")
73
+ $titleItem.Enabled = $false
74
+ $titleItem.Font = [Drawing.Font]::new($titleItem.Font, [Drawing.FontStyle]::Bold)
75
+ $statusItem = [Windows.Forms.ToolStripMenuItem]::new("Status: Browser starting")
76
+ $statusItem.Enabled = $false
77
+ $actionItem = [Windows.Forms.ToolStripMenuItem]::new("Current action: None")
78
+ $actionItem.Enabled = $false
79
+ $openItem = [Windows.Forms.ToolStripMenuItem]::new("Open Heny")
80
+ $pauseItem = [Windows.Forms.ToolStripMenuItem]::new("Pause")
81
+ $reconnectItem = [Windows.Forms.ToolStripMenuItem]::new("Reconnect now")
82
+ $startItem = [Windows.Forms.ToolStripMenuItem]::new("Start at sign-in")
83
+ $startItem.Checked = [bool] (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue)
84
+ $quitItem = [Windows.Forms.ToolStripMenuItem]::new("Quit")
85
+ foreach ($item in @($titleItem, $statusItem, $actionItem, [Windows.Forms.ToolStripSeparator]::new(), $openItem, $pauseItem, $reconnectItem, $startItem, [Windows.Forms.ToolStripSeparator]::new(), $quitItem)) { $null = $menu.Items.Add($item) }
86
+ $notifyIcon.ContextMenuStrip = $menu
87
+
88
+ $script:workerProcess = $null
89
+ $script:nextStart = Get-Date
90
+ $script:paused = Test-Path -LiteralPath $pausePath
91
+ $script:exiting = $false
92
+
93
+ function Set-HenyStatus {
94
+ param([string] $State, [string] $Action)
95
+ $label = switch ($State) {
96
+ "available" { "Available" }
97
+ "working" { "Working" }
98
+ "paused" { "Paused" }
99
+ "pausing" { "Pausing" }
100
+ "browser_starting" { "Browser starting" }
101
+ "error" { "Error" }
102
+ "offline" { "Offline" }
103
+ default { "Connecting" }
104
+ }
105
+ $statusItem.Text = "Status: $label"
106
+ $actionItem.Text = if ($Action) { "Current action: $Action" } else { "Current action: None" }
107
+ $notifyIcon.Text = "Heny Connect - $label"
108
+ $notifyIcon.Icon = if ($State -in @("available", "working")) { $iconAvailable } elseif ($State -eq "error") { $iconError } else { $iconPaused }
109
+ }
110
+
111
+ function Write-TrayStatus {
112
+ param([string] $State, [int] $ExitCode = 0, [string] $Detail = "")
113
+ @{ state = $State; exitCode = $ExitCode; detail = $Detail.Substring(0, [Math]::Min(200, $Detail.Length)); updatedAt = [DateTime]::UtcNow.ToString("o") } | ConvertTo-Json -Compress | Set-Content -LiteralPath $trayStatusPath -Encoding UTF8
114
+ }
115
+
116
+ function Get-HenyUrl {
117
+ try {
118
+ $state = Get-Content -LiteralPath (Join-Path $HomePath ".heny-connect.json") -Raw | ConvertFrom-Json
119
+ return ([string] $state.server).TrimEnd("/") + "/desktop"
120
+ } catch { return "https://heny.vyte.dev/desktop" }
121
+ }
122
+
123
+ function Stop-Worker {
124
+ if (-not $script:workerProcess) { return }
125
+ try {
126
+ if (-not $script:workerProcess.HasExited) {
127
+ New-Item -ItemType Directory -Path $HomePath -Force | Out-Null
128
+ Set-Content -LiteralPath $pausePath -Value "paused" -NoNewline
129
+ Set-Content -LiteralPath $stopPath -Value "stop" -NoNewline
130
+ $limit = (Get-Date).AddSeconds(5)
131
+ while (-not $script:workerProcess.HasExited -and (Get-Date) -lt $limit) {
132
+ try {
133
+ $state = Get-Content -LiteralPath $statusPath -Raw | ConvertFrom-Json
134
+ if ($state.state -eq "paused") { break }
135
+ } catch {}
136
+ Start-Sleep -Milliseconds 200
137
+ }
138
+ if (-not $script:workerProcess.HasExited) {
139
+ & taskkill.exe /PID ([string] $script:workerProcess.Id) /T /F 2>$null | Out-Null
140
+ $script:workerProcess.WaitForExit(2000) | Out-Null
141
+ }
142
+ }
143
+ $script:workerProcess.Dispose()
144
+ } catch {}
145
+ $script:workerProcess = $null
146
+ }
147
+
148
+ function Start-Worker {
149
+ if ($script:workerProcess -or $script:paused -or $script:exiting) { return }
150
+ Remove-Item -LiteralPath $stopPath -Force -ErrorAction SilentlyContinue
151
+ $startInfo = [Diagnostics.ProcessStartInfo]::new()
152
+ $startInfo.FileName = $NodePath
153
+ $startInfo.Arguments = "`"$CliPath`" run"
154
+ $startInfo.UseShellExecute = $false
155
+ $startInfo.CreateNoWindow = $true
156
+ $startInfo.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
157
+ $startInfo.EnvironmentVariables["HENY_CONNECT_HOME"] = $HomePath
158
+ $process = [Diagnostics.Process]::new()
159
+ $process.StartInfo = $startInfo
160
+ try {
161
+ if (-not $process.Start()) { throw "The worker did not start." }
162
+ $script:workerProcess = $process
163
+ Write-TrayStatus -State "worker_started"
164
+ Set-HenyStatus -State "browser_starting"
165
+ } catch {
166
+ $process.Dispose()
167
+ Write-TrayStatus -State "worker_start_failed" -ExitCode -1 -Detail $_.Exception.Message
168
+ Set-HenyStatus -State "error"
169
+ $script:nextStart = (Get-Date).AddSeconds(15)
170
+ }
171
+ }
172
+
173
+ function Request-Pause {
174
+ New-Item -ItemType Directory -Path $HomePath -Force | Out-Null
175
+ Set-Content -LiteralPath $pausePath -Value "paused" -NoNewline
176
+ $script:paused = $true
177
+ $pauseItem.Text = "Resume"
178
+ Set-HenyStatus -State "pausing"
179
+ }
180
+
181
+ function Request-Resume {
182
+ Remove-Item -LiteralPath $pausePath -Force -ErrorAction SilentlyContinue
183
+ Remove-Item -LiteralPath $stopPath -Force -ErrorAction SilentlyContinue
184
+ $script:paused = $false
185
+ $pauseItem.Text = "Pause"
186
+ $script:nextStart = Get-Date
187
+ Set-HenyStatus -State "browser_starting"
188
+ }
189
+
190
+ $openHeny = { Start-Process (Get-HenyUrl) }
191
+ $openItem.Add_Click($openHeny)
192
+ $notifyIcon.Add_DoubleClick($openHeny)
193
+ $pauseItem.Add_Click({ if ($script:paused) { Request-Resume } else { Request-Pause } })
194
+ $reconnectItem.Add_Click({ Request-Pause; Stop-Worker; Request-Resume })
195
+
196
+ $startItem.Add_Click({
197
+ try {
198
+ if ($startItem.Checked) {
199
+ Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
200
+ $startItem.Checked = $false
201
+ } else {
202
+ Install-HenyTask -StartNow $false
203
+ $startItem.Checked = $true
204
+ }
205
+ } catch { Set-HenyStatus -State "error" }
206
+ })
207
+
208
+ $quitItem.Add_Click({
209
+ $script:exiting = $true
210
+ $timer.Stop()
211
+ Request-Pause
212
+ $limit = (Get-Date).AddSeconds(5)
213
+ while ((Get-Date) -lt $limit) {
214
+ try {
215
+ $state = Get-Content -LiteralPath $statusPath -Raw | ConvertFrom-Json
216
+ if ($state.state -eq "paused") { break }
217
+ } catch {}
218
+ Start-Sleep -Milliseconds 200
219
+ }
220
+ Stop-Worker
221
+ Remove-Item -LiteralPath $pausePath -Force -ErrorAction SilentlyContinue
222
+ Remove-Item -LiteralPath $stopPath -Force -ErrorAction SilentlyContinue
223
+ $notifyIcon.Visible = $false
224
+ [Windows.Forms.Application]::ExitThread()
225
+ })
226
+
227
+ $timer = [Windows.Forms.Timer]::new()
228
+ $timer.Interval = 1000
229
+ $timer.Add_Tick({
230
+ if ($script:workerProcess -and $script:workerProcess.HasExited) {
231
+ $code = $script:workerProcess.ExitCode
232
+ Write-TrayStatus -State "worker_exited" -ExitCode $code
233
+ $script:workerProcess.Dispose()
234
+ $script:workerProcess = $null
235
+ if (-not $script:paused) {
236
+ Set-HenyStatus -State "error"
237
+ $script:nextStart = if ($code -in @(2, 3)) { [DateTime]::MaxValue } else { (Get-Date).AddSeconds(15) }
238
+ }
239
+ }
240
+ if ($script:workerProcess) {
241
+ try {
242
+ $state = Get-Content -LiteralPath $statusPath -Raw | ConvertFrom-Json
243
+ if ($script:paused -and $state.state -ne "paused") { Set-HenyStatus -State "pausing" } else { Set-HenyStatus -State ([string] $state.state) -Action ([string] $state.currentAction) }
244
+ } catch {}
245
+ } elseif ($script:paused) { Set-HenyStatus -State "paused" }
246
+ if (-not $script:paused -and -not $script:workerProcess -and (Get-Date) -ge $script:nextStart) { Start-Worker }
247
+ })
248
+ $timer.Start()
249
+ if ($script:paused) { Set-HenyStatus -State "paused"; $pauseItem.Text = "Resume" } else { Start-Worker }
250
+
251
+ try {
252
+ [Windows.Forms.Application]::Run()
253
+ } finally {
254
+ $timer.Stop()
255
+ Stop-Worker
256
+ $notifyIcon.Visible = $false
257
+ $notifyIcon.Dispose()
258
+ $menu.Dispose()
259
+ $iconAvailable.Dispose()
260
+ $iconPaused.Dispose()
261
+ $iconError.Dispose()
262
+ if ($createdNew) { $mutex.ReleaseMutex() }
263
+ $mutex.Dispose()
264
+ }
Binary file
Binary file
Binary file