# Web (TeaVM/WebGL) and its real limitations

> The app compiled to JS via TeaVM running on a WebGL canvas — and what does NOT work.

Source: https://crossfly.dev/docs/platforms/web-teavm — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Web (TeaVM/WebGL) 🌐

**The most honest page in the section.** The web target (module `web`, `WebLauncher`) compiles the app to
**JavaScript via TeaVM** and runs it on a WebGL2 `<canvas>` (backend `gdx-teavm`). **It is not GWT** and
it is **not** an embedded webview: it is the **app itself** running in the browser.

## Build and serve
```bash
gradlew :web:webBundle       # (alias: dist)
```
Generates `web/build/webapp/` with `index.html`, `web.js`, `scripts/howler.js`, `gdx.wasm.js`, and
`assets/` + `assets.txt`. Serve it locally:
```bash
cd web/build/webapp && python -m http.server 8080
# open http://127.0.0.1:8080/index.html
```
The `WebLauncher` does `new TeaApplication(new Main(true, cmdline), config)`.

## ⚠️ What does NOT work (it is a *stub*)
On the web, `PlatformUtilsWeb` implements most features as a *stub* — the call exists but **does
nothing useful**:
| Feature | Behavior on the web |
|---|---|
| `WebContainer` | `createWebView()` returns **`null`** |
| GPS / location | *stub* (no fix) |
| Bluetooth / radios | *stub* (`WirelessState.UNSUPPORTED`) |
| Biometrics | `AuthResult.ERROR` "Not supported" |
| Voice (TTS/STT) | `voiceToText` → `onError` |
| Native maps | not supported |
| Vibration / battery | *stub* |
| **File persistence** | `Device.homeFolder = null` |

> Since `crossfly-lib` (JNI) is **not loaded on WebGL** (`Main.create()` skips the `SharedLibraryLoader`
> on the WebGL target), everything that depends on native code falls back to the *stubs* above.

## What DOES work
- **Rendering the** full UI (it is the real app on the WebGL2 canvas).
- **Opening an external URL / dialing**: `PlatformUtils.instance.view(url)` / `dial(numero)` (via
  `Gdx.net.openURI`).
- HTTP with **restrictions** (CORS; `Utils.getRemoteFile` returns `null` on failure).

## Mandatory pattern: detect and fall back
```java
if (PlatformUtils.instance.createWebView() != null) {
   // Android/iOS/Desktop: embedded webview
} else {
   PlatformUtils.instance.view(url);   // Web: opens in a new tab
}
```
And check `Device.homeFolder == null` before attempting to persist a file on the web.

## Best practices
- **Disable/adapt** device features on the web (don't leave buttons that do nothing).
- Persistence on the web: use whatever is available in your build (don't count on `homeFolder`).
- Test by serving the static bundle locally before hosting.

## Common mistakes
- Assuming `WebContainer`/GPS/biometrics on the web (they are *stubs*).
- Trying `Device.homeFolder + "arquivo"` on the web (`homeFolder` is `null` → NPE).

## See also
- [Native integration](/docs/platforms/native-integration) · [Platform matrix](/docs/platforms/overview)
