Public/Connect-MyWebApi.ps1

function Connect-MyWebApi {
    <#
    .SYNOPSIS
        Creates an isolated WebAPI v2 session and makes it the optional default connection.
    .DESCRIPTION
        Returns a connection object. Pass that object to generated cmdlets with -Connection
        when more than one target is used; omitting -Connection retains the single-session
        convenience API. OAuth discovery is same-origin by default and all endpoints require
        HTTPS, except an explicit loopback-only HTTP opt-in for tests.
    .PARAMETER RequestTimeout
        Session default for how long the server waits for the trading platform, in seconds
        (1-300), sent as the X-Request-Timeout header on every REST call of this connection.
        A cmdlet's own -RequestTimeout wins. Omit it to let the server apply each operation's
        default (trade 5 s, read 10 s, change 15 s, history 30 s, maintenance 60 s).
    .PARAMETER HttpTimeoutSeconds
        Deadline of one HTTP call, in seconds (default 30). When omitted, trading platform calls
        get the operation's server timeout plus 30 s if that is longer, so the server's own answer
        (Timeout, OutcomeUnknown) arrives before the client gives up. When set, it is a hard cap,
        except for calls with an explicit -RequestTimeout, which always get that value plus 30 s.
    #>

    [CmdletBinding()]
    param(
        [Parameter()][ValidateSet('Staging','Production')][string] $Environment,
        [Parameter()][string] $BaseUrl,
        [Parameter()][string] $Authority,
        [Parameter()][string] $ClientId,
        [Parameter()][SecureString] $ClientSecret,
        [Parameter()][string] $AccessToken,
        [Parameter()][string] $Scope = 'webapi',
        [Parameter()][string] $DefaultTradePlatform,
        [Parameter()][string] $TrustedTokenEndpoint,
        [Parameter()][switch] $AllowInsecureLoopback,
        [Parameter()][ValidateRange(1, 600)][int] $HttpTimeoutSeconds = 30,
        [Parameter()][ValidateRange(1, 3600)][int] $RealtimeTimeoutSeconds = 30,
        [Parameter()][ValidateRange(0, 3)][int] $MaxGetRetries = 2,
        [Parameter()][ValidateRange(1, 300)][double] $RequestTimeout
    )

    if ($Environment) {
        $environmentConfig = Resolve-MyWebApiEnvironment -Environment $Environment
        $resolvedBase = $environmentConfig.BaseUrl
        $resolvedAuthority = $environmentConfig.Authority
    } else {
        if (-not $BaseUrl) { throw 'Provide -Environment, or -BaseUrl (and -Authority for client-credentials).' }
        $resolvedBase = $BaseUrl
        $resolvedAuthority = $Authority
    }

    if (-not $AccessToken -and -not $ClientId) {
        throw 'Provide either -AccessToken, or -ClientId and -ClientSecret for client-credentials.'
    }
    if (-not $AccessToken -and -not $resolvedAuthority) {
        throw 'Authority is required for client-credentials: pass -Environment, or -Authority with -BaseUrl.'
    }
    if (-not $AccessToken -and -not $ClientSecret) {
        throw 'ClientSecret is required for client-credentials.'
    }

    # Validate before storing any mutable session state. Authority and API URLs are
    # deliberately separate so a token endpoint can never silently redirect credentials.
    $candidate = [pscustomobject]@{
        BaseUrl              = $resolvedBase.TrimEnd('/')
        Authority            = if ($resolvedAuthority) { $resolvedAuthority.TrimEnd('/') } else { $null }
        ClientId             = $ClientId
        ClientSecret         = $ClientSecret
        Scope                = $Scope
        DefaultTradePlatform = $DefaultTradePlatform
        AccessToken          = $AccessToken
        ExpiresAt            = if ($AccessToken) { [DateTimeOffset]::UtcNow.AddHours(1) } else { $null }
        TrustedTokenEndpoint = $TrustedTokenEndpoint
        AllowInsecureLoopback = [bool]$AllowInsecureLoopback
        HttpTimeoutSeconds   = $HttpTimeoutSeconds
        HttpTimeoutExplicit  = $PSBoundParameters.ContainsKey('HttpTimeoutSeconds')
        RealtimeTimeoutSeconds = $RealtimeTimeoutSeconds
        MaxGetRetries        = $MaxGetRetries
        RequestTimeout       = if ($PSBoundParameters.ContainsKey('RequestTimeout')) { $RequestTimeout } else { $null }
        CancellationSource    = [System.Threading.CancellationTokenSource]::new()
        RefreshGate          = [System.Threading.SemaphoreSlim]::new(1, 1)
        Disposed             = $false
    }
    $null = Assert-MyWebApiEndpoint -Value $candidate.BaseUrl -Name 'BaseUrl' -Context $candidate
    if ($candidate.Authority) {
        $null = Assert-MyWebApiEndpoint -Value $candidate.Authority -Name 'Authority' -Context $candidate
    }
    if ($TrustedTokenEndpoint) {
        $null = Assert-MyWebApiEndpoint -Value $TrustedTokenEndpoint -Name 'TrustedTokenEndpoint' -Context $candidate
    }

    # Keep the old one-session convenience while returning an explicit immutable target
    # reference for callers that need independent sessions.
    $script:MyWebApiContext = $candidate
    if (-not $AccessToken) {
        [void](Get-MyWebApiToken -Connection $candidate)
    }
    return $candidate
}