### lyng.io.ws — WebSocket client for Lyng scripts

This module provides a compact WebSocket client API for Lyng scripts. It is implemented in `lyngio` and currently backed by Ktor WebSockets on the JVM.

> **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes.

---

#### Install the module into a Lyng session

Kotlin (host) bootstrap example:

```kotlin
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Scope
import net.sergeych.lyng.io.ws.createWsModule
import net.sergeych.lyngio.ws.security.PermitAllWsAccessPolicy

suspend fun bootstrapWs() {
    val session = EvalSession()
    val scope: Scope = session.getScope()
    createWsModule(PermitAllWsAccessPolicy, scope)
    session.eval("import lyng.io.ws")
}
```

---

#### Using from Lyng scripts

Simple text message exchange:

    import lyng.io.ws

    val ws = Ws.connect(WS_TEST_URL)
    ws.sendText("ping")
    val m: WsMessage = ws.receive()
    ws.close()
    [ws.url() == WS_TEST_URL, m.isText, m.text]
    >>> [true,true,echo:ping]

Binary message exchange:

    import lyng.buffer
    import lyng.io.ws

    val ws = Ws.connect(WS_TEST_BINARY_URL)
    ws.sendBytes(Buffer(9, 8, 7))
    val m: WsMessage = ws.receive()
    ws.close()
    [m.isText, (m.data as Buffer).hex]
    >>> [false,010203090807]

Secure websocket (`wss`) exchange:

    import lyng.io.ws

    val ws = Ws.connect(WSS_TEST_URL)
    ws.sendText("ping")
    val m: WsMessage = ws.receive()
    ws.close()
    [ws.url() == WSS_TEST_URL, m.text]
    >>> [true,secure:ping]

---

#### API reference

##### `Ws` (static methods)

- `isSupported(): Bool` — Whether WebSocket client support is available on the current runtime.
- `connect(url: String, headers...): WsSession` — Open a client websocket session.

`headers...` accepts:

- `MapEntry`, e.g. `"Authorization" => "Bearer x"`
- 2-item lists, e.g. `["Authorization", "Bearer x"]`

##### `WsSession`

- `isOpen(): Bool`
- `url(): String`
- `sendText(text: String): void`
- `sendBytes(data: Buffer): void`
- `receive(): WsMessage?`
- `close(code: Int = 1000, reason: String = ""): void`

`receive()` returns `null` after a clean close.

##### `WsMessage`

- `isText: Bool`
- `text: String?`
- `data: Buffer?`

Text messages populate `text`; binary messages populate `data`.

---

#### Security policy

The module uses `WsAccessPolicy` to authorize websocket operations.

- `WsAccessPolicy` — interface for custom policies
- `PermitAllWsAccessPolicy` — allows all websocket operations
- `WsAccessOp.Connect(url)`
- `WsAccessOp.Send(url, bytes, isText)`
- `WsAccessOp.Receive(url)`

Example restricted policy in Kotlin:

```kotlin
import net.sergeych.lyngio.fs.security.AccessContext
import net.sergeych.lyngio.fs.security.AccessDecision
import net.sergeych.lyngio.fs.security.Decision
import net.sergeych.lyngio.ws.security.WsAccessOp
import net.sergeych.lyngio.ws.security.WsAccessPolicy

val allowLocalOnly = object : WsAccessPolicy {
    override suspend fun check(op: WsAccessOp, ctx: AccessContext): AccessDecision =
        when (op) {
            is WsAccessOp.Connect ->
                if (
                    op.url.startsWith("ws://127.0.0.1:") ||
                    op.url.startsWith("wss://127.0.0.1:") ||
                    op.url.startsWith("ws://localhost:") ||
                    op.url.startsWith("wss://localhost:")
                )
                    AccessDecision(Decision.Allow)
                else
                    AccessDecision(Decision.Deny, "only local ws/wss connections are allowed")

            else -> AccessDecision(Decision.Allow)
        }
}
```

---

#### Platform support

- **JVM:** supported
- **Android:** supported via the Ktor CIO websocket client backend
- **JS:** supported via the Ktor JS websocket client backend
- **Linux native:** supported via the Ktor Curl websocket client backend
- **Windows native:** supported via the Ktor WinHttp websocket client backend
- **Apple native:** supported via the Ktor Darwin websocket client backend
- **Other targets:** may report unsupported; use `Ws.isSupported()` before relying on websocket client access
