> ## Documentation Index
> Fetch the complete documentation index at: https://globaliptv.misidev.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connectivity

> How GlobalTV Roku monitors network and server reachability in the background, reflects online/offline state globally, and responds to connectivity loss.

GlobalTV runs a background connectivity check loop to detect network loss or server unreachability while the app is in use. The result is written to global state so any screen can react without performing its own HTTP probes.

## ConnectivityTask

`components/tasks/ConnectivityTask.brs` is a background `Task` node that runs an infinite polling loop. It wakes up on a 10-second timer tick and calls `DoCheck()`, which performs an HTTP probe against the active server's health endpoint.

```brightscript theme={null}
sub RunConnectivityTask()
    GTV_Log("ConnectivityTask", "Started - checking internet every 10s")

    port     = CreateObject("roMessagePort")
    interval = 10000

    m.top.observeFieldScoped("checkNow", port)
    m.top.observeFieldScoped("control",  port)

    DoCheck()

    while true
        msg = wait(interval, port)

        if msg <> invalid
            msgType = type(msg)
            if msgType = "roSGNodeEvent"
                field = msg.GetField()
                if field = "checkNow"
                    GTV_Log("ConnectivityTask", "Immediate check triggered")
                    m.top.checkNow = false
                    DoCheck()
                else if field = "control"
                    if m.top.control = "stop" then return
                end if
            end if
        else
            DoCheck()
        end if
    end while
end sub
```

<Note>
  The `checkNow` field allows other parts of the app to trigger an immediate check outside the normal 10-second cadence — for example, when the player encounters a stream error.
</Note>

### Retry interval

The constant `NET_RETRY_MS = 30000` in `AppConstants.brs` defines the interval used by higher-level retry logic (e.g., the player's reconnection policy). The task's own internal polling cycle runs at 10 seconds, independent of `NET_RETRY_MS`.

## Online/offline state in `DoCheck()`

`DoCheck()` writes the result of each probe to both the task's own output field and to `m.global`:

```brightscript theme={null}
sub DoCheck()
    server = GTV_ServerNormalizeBaseUrl(m.global.activeServer)
    if server = "" or server = invalid
        server = GTV_ServerPrimaryConnectivityUrl()
    end if

    if server = "" or server = invalid
        m.top.hasInternet = false
        m.global.hasInternet = false
        m.global.isOnline = GTV_IsOnline()
        if m.lastConnectivityState <> "missing_server"
            GTV_Warn("ConnectivityTask", "Connectivity check unavailable: no control server")
            m.lastConnectivityState = "missing_server"
        end if
        return
    end if

    result = GTV_PingServer(server)

    m.top.hasInternet      = result
    m.global.hasInternet   = result
    m.global.isOnline      = GTV_IsOnline()

    if result
        if m.lastConnectivityState <> "online"
            GTV_Log("ConnectivityTask", "Internet OK")
            m.lastConnectivityState = "online"
        end if
    else
        if m.lastConnectivityState <> "offline"
            GTV_Warn("ConnectivityTask", "No internet reachability")
            m.lastConnectivityState = "offline"
        end if
    end if
end sub
```

### Log spam suppression

The task tracks the previous state in `m.lastConnectivityState`. A log or warning is only emitted when the state **changes** — not on every poll tick. A device that stays offline for minutes will produce a single `"No internet reachability"` warning rather than one every 10 seconds.

### Global state fields written

| Field                  | Type    | Description                                                            |
| ---------------------- | ------- | ---------------------------------------------------------------------- |
| `m.global.hasInternet` | Boolean | `true` if the health probe succeeded                                   |
| `m.global.isOnline`    | Boolean | `true` if the device has any network interface (from `GTV_IsOnline()`) |
| `m.top.hasInternet`    | Boolean | Task output field; mirrors `m.global.hasInternet`                      |

## NetworkDetector utility

`source/utils/NetworkDetector.brs` provides two low-level functions that `DoCheck()` and other utilities use:

```brightscript theme={null}
function GTV_IsOnline() as Boolean
    di = CreateObject("roDeviceInfo")
    connType = di.GetConnectionType()
    return (connType <> "")
end function

function GTV_HasInternet(activeServer as String) as Boolean
    if not GTV_IsOnline()
        return false
    end if

    serverToCheck = activeServer
    if serverToCheck = ""
        serverToCheck = GTV_ServerPrimaryConnectivityUrl()
    end if

    if serverToCheck = "" then return false
    return GTV_PingServer(serverToCheck)
end function
```

`GTV_IsOnline()` uses the Roku `roDeviceInfo` API — it only confirms that a network interface exists (Wi-Fi or Ethernet is connected), not that the internet is reachable. `GTV_HasInternet()` adds a server probe on top of that, making it suitable for callers that need confirmed reachability rather than just interface presence.

## HealthCheck utility

`source/utils/HealthCheck.brs` contains the `GTV_PingServer()` function used by all connectivity probes:

```brightscript theme={null}
function GTV_PingServer(baseUrl as String) as Boolean
    c   = AppConstants()
    url = baseUrl + c.PATH_HEALTH

    http = CreateObject("roUrlTransfer")
    http.SetUrl(url)

    if Left(url, 5) = "https"
        http.SetCertificatesFile(c.CERTS)
    end if
    http.RetainBodyOnError(true)

    port = CreateObject("roMessagePort")
    http.SetMessagePort(port)

    if not http.AsyncGetToString()
        return false
    end if

    msg = wait(c.TIMEOUT_HEALTH, port)
    if msg = invalid
        http.AsyncCancel()
        return false
    end if

    if type(msg) = "roUrlEvent"
        code = msg.GetResponseCode()
        return (code >= 200 and code < 500)
    end if

    return false
end function
```

Key details:

* The endpoint probed is `baseUrl + "/health"` (`PATH_HEALTH = "/health"` in `AppConstants.brs`).
* The timeout is `TIMEOUT_HEALTH = 2500` ms — fast enough to not block the UI.
* Any HTTP response code in the range 200–499 is treated as success. This means a `404` or `401` from the server still counts as "reachable"; only network-level failures or a `5xx` cause the probe to return `false`.
* HTTPS connections use the Roku CA bundle at `common:/certs/ca-bundle.crt`.

## How the app responds to connectivity loss

<AccordionGroup>
  <Accordion title="Player screen">
    The `PlayerScreen` monitors `m.global.hasInternet`. When it goes `false`, the player shows an offline error modal and pauses stream attempts. It does not immediately stop the video — if the stream buffer holds, playback may continue briefly. Once the modal is visible, the player retries reconnection on a `NET_RETRY_MS = 30000` ms interval.
  </Accordion>

  <Accordion title="Auth and playlist tasks">
    If `AUTH_REASON_NETWORK_DOWN` is returned from an auth or playlist HTTP call (HTTP code `-1`), `MainScene` shows an `OfflineDialog` with a retry button rather than logging the user out. The retry re-runs the handshake flow from the beginning.
  </Accordion>

  <Accordion title="Session auth checks">
    The periodic session re-auth timer (`SESSION_AUTH_CHECK_MS = 420000` ms) skips its check when `m.global.isOnline` or `m.global.hasInternet` is `false`, avoiding spurious session-expiry dialogs during a network outage.
  </Accordion>

  <Accordion title="Connectivity task itself">
    The task continues polling regardless of state. As soon as the probe succeeds again, `m.global.hasInternet` returns to `true`, and any subscriber that observes that field can resume normal operation.
  </Accordion>
</AccordionGroup>
