Private/WebSockets.ps1

using namespace Pode

function Test-PodeWebSocketsExist {
    return (($null -ne $PodeContext.Server.WebSockets) -and (($PodeContext.Server.WebSockets.Enabled) -or ($PodeContext.Server.WebSockets.Connections.Count -gt 0)))
}

function Find-PodeWebSocket {
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.WebSockets.Connections[$Name]
}

function New-PodeWebSocketReceiver {
    if ($null -ne $PodeContext.Server.WebSockets.Receiver) {
        return
    }

    try {
        $receiver = [PodeReceiver]::new($PodeContext.Tokens.Cancellation.Token)
        $receiver.ErrorLoggingEnabled = (Test-PodeErrorLoggingEnabled)
        $receiver.ErrorLoggingLevels = @(Get-PodeErrorLoggingLevel)
        $PodeContext.Server.WebSockets.Receiver = $receiver
        $PodeContext.Receivers += $receiver
    }
    catch {
        $_ | Write-PodeErrorLog
        $_.Exception | Write-PodeErrorLog -CheckInnerException
        Close-PodeDisposable -Disposable $receiver
        throw $_.Exception
    }
}

function Start-PodeWebSocketRunspace {
    if (!(Test-PodeWebSocketsExist)) {
        return
    }

    # script for listening out of for incoming requests (Receiver)
    $receiveScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Receiver,

            [Parameter(Mandatory = $true)]
            [int]
            $ThreadId
        )

        try {
            while ($Receiver.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                # get request
                $request = (Wait-PodeTask -Task $Receiver.GetWebSocketRequestAsync($PodeContext.Tokens.Cancellation.Token))

                try {
                    try {
                        $WsEvent = @{
                            Request   = $request
                            Data      = $null
                            Files     = $null
                            Lockable  = $PodeContext.Threading.Lockables.Global
                            Timestamp = [datetime]::UtcNow
                            Metadata  = @{}
                        }

                        # find the websocket definition
                        $websocket = Find-PodeWebSocket -Name $request.WebSocket.Name
                        if ($null -eq $websocket.Logic) {
                            continue
                        }

                        # parse data
                        $result = ConvertFrom-PodeRequestContent -Request $request -ContentType $request.WebSocket.ContentType
                        $WsEvent.Data = $result.Data
                        $WsEvent.Files = $result.Files

                        # invoke websocket script
                        $null = Invoke-PodeScriptBlock -ScriptBlock $websocket.Logic -Arguments $websocket.Arguments -UsingVariables $websocket.UsingVariables -Scoped -Splat
                    }
                    catch [System.OperationCanceledException] {
                        $_ | Write-PodeErrorLog -Level Debug
                    }
                    catch {
                        $_ | Write-PodeErrorLog
                        $_.Exception | Write-PodeErrorLog -CheckInnerException
                    }
                }
                finally {
                    $WsEvent = $null
                    Close-PodeDisposable -Disposable $request
                }
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
    }

    # start the runspace for listening on x-number of threads
    1..$PodeContext.Threads.WebSockets | ForEach-Object {
        Add-PodeRunspace -Type WebSockets -Name 'Receiver' -Id $_ -ScriptBlock $receiveScript -Parameters @{ 'Receiver' = $PodeContext.Server.WebSockets.Receiver; 'ThreadId' = $_ }
    }

    # script to keep websocket server receiving until cancelled
    $waitScript = {
        param(
            [Parameter(Mandatory = $true)]
            [ValidateNotNull()]
            $Receiver
        )

        try {
            while ($Receiver.IsConnected -and !$PodeContext.Tokens.Cancellation.IsCancellationRequested) {
                Start-Sleep -Seconds 1
            }
        }
        catch [System.OperationCanceledException] {
            $_ | Write-PodeErrorLog -Level Debug
        }
        catch {
            $_ | Write-PodeErrorLog
            $_.Exception | Write-PodeErrorLog -CheckInnerException
            throw $_.Exception
        }
        finally {
            Close-PodeDisposable -Disposable $Receiver
        }
    }

    Add-PodeRunspace -Type WebSockets -Name 'KeepAlive' -ScriptBlock $waitScript -Parameters @{ 'Receiver' = $PodeContext.Server.WebSockets.Receiver } -NoProfile
}