Private/Runtime/Invoke-OpenApiHttpExchange.ps1

function Invoke-OpenApiHttpExchange {
    <#
    .SYNOPSIS
        Sends a request with authentication and retries (tcs.core Invoke-WithRetry), refreshing an OAuth2 token once on 401; returns the final response object.
    .DESCRIPTION
        The returned response may be any status code; callers decide what is an error. Transport failures that are not
        retried (or keep failing) are thrown.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [pscustomobject]$Context,

        [Parameter(Mandatory)]
        [object]$Operation,

        [Parameter(Mandatory)]
        [System.Net.Http.HttpClient]$Client,

        [Parameter(Mandatory)]
        [string]$Method,

        [Parameter(Mandatory)]
        [string]$Uri,

        [Parameter()]
        [AllowNull()]
        [System.Collections.IDictionary]$Header,

        [Parameter()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]$Cookie,

        [Parameter()]
        [AllowNull()]
        [scriptblock]$ContentFactory,

        [Parameter()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]$SensitiveName
    )

    $policy = Get-OpenApiRetryPolicy -Method $Method -MaxRetries $Context.MaxRetries
    $refreshed = $false
    $forceRefresh = $false
    while ($true) {
        $auth = Get-OpenApiAuthorization -Operation $Operation -Context $Context -Client $Client -ForceRefresh:$forceRefresh

        # Query credentials are added unless the URL (for example a next-page link) already has them
        $queryPairs = New-Object System.Collections.Generic.List[string]
        foreach ($pair in $auth.Query) {
            $name = $pair.Substring(0, $pair.IndexOf('='))
            if ($Uri -notmatch ('[?&]' + [regex]::Escape($name) + '=')) {
                $queryPairs.Add($pair)
            }
        }
        $requestUri = Add-OpenApiQueryString -Uri $Uri -Pair $queryPairs.ToArray()

        $requestHeaders = [ordered]@{}
        if ($null -ne $Header) {
            foreach ($key in $Header.Keys) {
                $requestHeaders[[string]$key] = $Header[$key]
            }
        }
        foreach ($key in $auth.Header.Keys) {
            $requestHeaders[$key] = $auth.Header[$key]
        }
        $allCookies = @(@($Cookie) + @($auth.Cookie) | Where-Object -FilterScript { -not [string]::IsNullOrEmpty($_) })
        if ($allCookies.Count -gt 0) {
            $requestHeaders['Cookie'] = $allCookies -join '; '
        }
        $names = @(@($SensitiveName) + @($auth.SensitiveName) | Where-Object -FilterScript { -not [string]::IsNullOrEmpty($_) })

        $attempt = {
            $response = Send-OpenApiHttpRequest -Client $Client -Method $Method -Uri $requestUri -Header $requestHeaders -ContentFactory $ContentFactory -SensitiveName $names
            if ($policy.StatusCodes -contains $response.StatusCode) {
                throw (New-OpenApiRetryException -Response $response)
            }
            $response
        }
        $shouldRetry = {
            param($errorRecord)
            Test-OpenApiRetryableFailure -ErrorRecord $errorRecord -Policy $policy
        }
        $retryParameters = @{
            ScriptBlock       = $attempt
            MaxRetries        = $policy.MaxRetries
            DelaySeconds      = $policy.DelaySeconds
            BackoffMultiplier = $policy.BackoffMultiplier
            MaxDelaySeconds   = $policy.MaxDelaySeconds
            JitterPercent     = $policy.JitterPercent
            RetryOnStatusCode = $policy.StatusCodes
            ShouldRetry       = $shouldRetry
            ErrorAction       = 'Stop'
            Verbose           = ($VerbosePreference -ne 'SilentlyContinue')
        }
        try {
            $response = Invoke-WithRetry @retryParameters
        }
        catch {
            $failed = $_.Exception
            if ($null -ne $failed -and $null -ne $failed.PSObject.Properties['TcsOpenApiResponse']) {
                # Retries are used up: the last response is returned for the caller to report
                $response = $failed.TcsOpenApiResponse
            }
            else {
                throw
            }
        }

        if ($response.StatusCode -eq 401 -and $auth.UsesOAuth -and -not $refreshed) {
            Write-Verbose -Message 'The server answered 401 Unauthorized; requesting a new OAuth2 access token and trying once more.'
            $null = Read-OpenApiResponseBody -Response $response
            $refreshed = $true
            $forceRefresh = $true
            continue
        }
        return $response
    }
}