diff --git a/samy.ps1 b/samy.ps1 index b1068a5..60de2b3 100644 --- a/samy.ps1 +++ b/samy.ps1 @@ -1006,12 +1006,26 @@ function Get-UIHtml { [Parameter(Mandatory = $true)][object] $Context, [Parameter(Mandatory = $true)][string] $Html ) + if (-not $Context -or -not $Context.Response) { return } + + # --- Prevent caching (Edge app-mode loves to be "helpful") --- + try { + $Context.Response.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" + $Context.Response.Headers["Pragma"] = "no-cache" + $Context.Response.Headers["Expires"] = "0" + } catch { + # HttpListenerResponse headers can throw in rare cases; ignore + } + $bytes = [Text.Encoding]::UTF8.GetBytes($Html) - $Context.Response.ContentType = 'text/html' + + # Include charset + $Context.Response.ContentType = "text/html; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length + $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) $Context.Response.OutputStream.Close() } @@ -1027,16 +1041,17 @@ function Send-JSON { return } + $json = $null + try { - # 🔹 Normalize $Object so we never feed $null to GetBytes + # Normalize output so GetBytes never sees $null if ($null -eq $Object) { Write-LogHybrid "Send-JSON called with `$null object; returning empty JSON array." Warning Printers -LogToEvent $json = '[]' } else { - # If ConvertTo-Json fails, force an empty array string instead of bubbling $null try { - $json = $Object | ConvertTo-Json -Depth 5 -ErrorAction Stop + $json = $Object | ConvertTo-Json -Depth 8 -ErrorAction Stop } catch { Write-LogHybrid "Send-JSON serialization failed: $($_.Exception.Message); returning empty JSON array." Error Printers -LogToEvent @@ -1044,32 +1059,49 @@ function Send-JSON { } } - # 🔹 Final safety: ensure we always pass a *string* to GetBytes $json = [string]$json + # ---- No-cache headers (prevents stale UI data) ---- + try { + $Context.Response.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" + $Context.Response.Headers["Pragma"] = "no-cache" + $Context.Response.Headers["Expires"] = "0" + } catch { } + $bytes = [Text.Encoding]::UTF8.GetBytes($json) - $Context.Response.ContentType = 'application/json' + + $Context.Response.ContentType = "application/json; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) - $Context.Response.OutputStream.Close() } catch { - # Last-resort error handling - don't let the whole request crash Write-LogHybrid "Send-JSON fatal error: $($_.Exception.Message)" Error Printers -LogToEvent + + # Best-effort fallback response try { $fallback = '[]' $bytes = [Text.Encoding]::UTF8.GetBytes($fallback) - $Context.Response.ContentType = 'application/json' + + try { + $Context.Response.Headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" + $Context.Response.Headers["Pragma"] = "no-cache" + $Context.Response.Headers["Expires"] = "0" + } catch { } + + $Context.Response.ContentType = "application/json; charset=utf-8" $Context.Response.ContentLength64 = $bytes.Length $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length) - $Context.Response.OutputStream.Close() } catch { - # If even this fails, just give up silently - we've already logged it. + # swallow: nothing else we can do safely here } } + finally { + try { $Context.Response.OutputStream.Close() } catch { } + } } + #endregion HTTP responder helpers function Invoke-TasksCompleted {