# 게이트웨이

> WebSocket / JSON 게이트웨이: Python SDK로 호출할 수 있는 공개 API를 JS, C#, Go, Rust, 브라우저 페이지, 다른 컴퓨터의 프로그램에서도 호출할 수 있습니다. 세 가지 역할, JS 클라이언트와 브라우저 데모 페이지 포함. 지연은 고속 레인에 약 1 ms가 더해지는 정도입니다.

출처: https://war3ai.com/ko/docs/gateway/

게이트웨이는 고속 레인과 푸시 상태를 **WebSocket / JSON**으로 감쌉니다. [API 카탈로그](https://war3ai.com/ko/api/)에서 Python SDK로 호출할 수 있는 공개 API를 JS, C#, Go, Rust, 브라우저 페이지, 다른 컴퓨터의 프로그램, LLM에서도 호출할 수 있으며, 메서드 이름과 인자도 같습니다. 지연은 고속 레인에 약 1 ms가 더해지는 정도입니다.

**가장 간단한 방법: Farsight 첫 화면 "컨트롤 센터" → 게이트웨이 → 시작**(중지, 재시작, 로그 보기, 데모 페이지 열기도 그 카드에서 합니다). 명령줄:

```bash
python gateway/server.py                 # ws://127.0.0.1:8870/ws(포트는 openwar3.json의 ports.gateway)
python gateway/server.py --open          # 위와 같고, 포트가 열리면 데모 페이지 http://127.0.0.1:8870/demo를 엶
python gateway/server.py --host 0.0.0.0  # LAN용: 토큰을 자동으로 요구(bin/gateway/token.txt)
python gateway/server.py --allow-origin http://localhost:5173   # 직접 만든 웹 페이지에서도 연결 가능
```

## 연결과 역할

연결 주소: `ws://127.0.0.1:8870/ws?inst=9&role=dev`(`inst=` 대신 `pid=`를 써도 됩니다. 토큰이 필요하면 `&token=`을 붙입니다).

| 역할 | 호출할 수 있는 것 | 적합한 용도 |
|---|---|---|
| `dev` | 전부: 관찰, 명령, 게임 제어, 샌드박스(JASS로 월드 변경), UI 그리기 | 로컬 도구, [게임플레이 모드](https://war3ai.com/ko/docs/mods/), 동료 |
| `player`(`&player=N` 추가) | 관찰, N번 플레이어의 유닛 지휘, UI 그리기. **기본값은 공정 모드**로, N번 플레이어의 시야 안에 있는 것만 보임(`&fair=0`으로 끔) | 특정 플레이어 대신 출전하는 Bot이나 LLM |
| `observer` | 읽기 전용(내린 명령은 런타임이 바로 거부) | 관전, 해설, 데이터 수집 |

`player`가 쓸 수 없는 것: 게임 종료, 속도 변경, 일시 정지 같은 게임 제어, 다른 플레이어의 패를 볼 수 있는 `players`와 `enemy_ai_plan`, 게임 프로세스가 로컬 파일을 열게 하는 `canvas.image`, 그리고 JASS. `resources`, `tech`, `stats`처럼 플레이어 번호를 받는 조회는 자기 자신만 조회할 수 있습니다.

연결 하나가 세션 하나이며, 고속 레인 하나를 차지합니다(런타임에는 모두 16개). 게이트웨이는 동시에 최대 12개 세션까지 받아 Bot, 모드, Farsight 몫으로 몇 개를 남겨 둡니다. 연결이 끊기면 그 세션이 직접 그린 것과 단축키만 정리하고, 다른 프로그램이 그린 것은 건드리지 않습니다.

## 메시지

연결되면 먼저 `hello`를 받습니다: 프로토콜 버전, 역할, 게임 프로세스 ID, 이 역할이 호출할 수 있는 메서드 목록. 이후 요청마다 `id`를 붙이며, 응답에는 같은 `id`가 붙습니다.

```json
→ {"id": 1, "op": "call", "method": "units", "args": ["me"]}
← {"id": 1, "ok": true, "result": [{"addr": 123456, "type": "hpea", "owner": 0, "x": -4480, "y": 2120, "hp": 220, "hp_max": 220}]}

→ {"id": 2, "op": "call", "method": "move", "args": [[{"unit": 123456}], 100, 200]}
→ {"id": 3, "op": "call", "method": "ui.button", "args": ["shop", "물약 사기"], "kwargs": {"screen": [40, 300]}}
→ {"id": 4, "op": "subscribe", "state": {"hz": 4, "units": "mine"}, "events": true}
← {"type": "state", ...}    {"type": "events", ...}        이후 계속 푸시
→ {"id": 5, "op": "overview"}                             게임 상황 한 페이지: 자원, 유닛 종류별 수, 영웅, 보이는 적, 생산
→ {"id": 6, "op": "jass", "code": "call PingMinimapEx(0, 0, 3, 255, 0, 0, false)"}    dev 전용
→ {"id": 7, "op": "api"}                                  메서드 카탈로그(그 밖에 ping / unsubscribe)
```

- **유닛 인자**는 `{"unit": 주소}`로 씁니다. 주소는 유닛 JSON의 `addr`입니다. `"handle": [lo, hi]`를 함께 넘기면 이 주소가 다른 유닛에게 재사용되지 않았는지 확인합니다.
- **메서드 이름**은 Game의 공개 메서드이며, 여기에 `ui.*`(button / choice / toast / hotkey / mouse / cursor…), `canvas.*`(text / panel / bar / image / circle / path / remove…), `jass.<함수 이름>`(dev 전용)이 더해집니다.
- 원격에서는 콜백 함수를 넘길 수 없습니다. 클릭과 단축키는 이벤트 푸시로 받으며, `ui.click` 이벤트에 `key`가 들어 있습니다. [UI와 입력](https://war3ai.com/ko/docs/ui-input/)을 참고하세요.
- 호출 하나에서 오류가 나면 그 호출에만 오류를 돌려주며(`ok: false`와 `error`), 연결은 끊기지 않습니다. 보낸 내용이 JSON이 아닐 때도 마찬가지입니다.
- 이벤트 JSON의 필드는 [W3P 프로토콜](https://war3ai.com/ko/docs/protocol/)과 같으며, 편의 필드(`spell`, `key`, `text`, `chat`, `button`, `player`, `mods`)가 추가됩니다.

HTTP도 쓸 수 있으며, 일회성 호출이나 curl에 적합합니다. `GET /api?role=player`는 메서드 카탈로그를 나열하고, `POST /call`에 `inst`, `role`, `method`, `args`, `kwargs`를 담아 한 번 호출합니다. `/call`은 세션을 재사용합니다. 게임을 다시 시작해 프로세스가 바뀌면 자동으로 새 세션으로 바꾸고, 10분 동안 쓰지 않은 세션은 정리합니다.

## 클라이언트

**JS**(브라우저 또는 Node 22+, 의존성 없음): `gateway/clients/js/openwar3.mjs`

```js
import { OpenWar3, unit } from "./openwar3.mjs";

const ow = new OpenWar3("ws://127.0.0.1:8870/ws", { inst: 9, role: "dev" });
await ow.connect();
const mine = await ow.api.units("me");
await ow.api.move(mine.slice(0, 3).map(unit), 100, 200);
await ow.api.ui.button("hi", "눌러 보세요", { screen: [40, 300] });     // 마지막 일반 객체 = 키워드 인자
ow.on("event:ui.click", (e) => console.log("클릭됨", e.key));
await ow.subscribe({ state: { hz: 2, units: "mine" }, events: true });
```

Node 20 / 21에서는 `--experimental-websocket`을 붙여야 합니다. 전체 예제는 `gateway/clients/js/example.mjs`에 있습니다.

**브라우저 데모 페이지** `http://127.0.0.1:8870/demo`: 게임 상황, 아군 유닛 표, 게임에 버튼 하나 놓기, 이벤트 스트림을 한 페이지에서 모두 볼 수 있습니다.

**다른 언어**: WebSocket 라이브러리 하나 + 위의 JSON이면 충분합니다. 공유 메모리를 건드릴 필요가 없습니다.

**LLM**: [MCP 서버](https://war3ai.com/ko/docs/mcp/)를 바로 쓰세요. 자주 하는 일을 미리 도구로 만들어 두었습니다.

## 실측

2026-09-25, 실제 게임에 연결해 항목별로 확인 16/16(게이트웨이 9개 + MCP 7개): 핸드셰이크(dev 역할 메서드 121개), `units('me')`, 게임 상황 한 페이지, 화면 알림, 버튼 놓기. 구독한 뒤 게임에서 그 버튼을 클릭 → `ui.click`이 클라이언트로 푸시됨. JASS. 잘못된 유닛을 넘기면 그 호출에만 오류를 보고. HTTP `/call`(observer 역할).

JS 클라이언트(Node)와 브라우저 데모 페이지도 실행해 봤습니다. 웹 페이지에서 놓은 버튼을 게임에서 클릭하자, 웹 페이지의 이벤트 로그가 `ui.click`을 받았습니다.

## 보안

- 기본적으로 로컬 `127.0.0.1`에서만 수신 대기하며, 토큰이 필요 없습니다(Farsight와 같음). `--host`가 로컬 주소가 아니면 자동으로 토큰을 요구하고, `--auth`를 쓰면 로컬에서도 요구합니다.
- **브라우저 안의 다른 웹사이트는 연결할 수 없습니다**: 브라우저가 여는 연결에는 모두 출처(`Origin`)가 붙으며, 게이트웨이는 자체 데모 페이지와 `--allow-origin`으로 지정한 주소만 받아들입니다. Python, Node, curl 같은 프로그램은 출처를 붙이지 않으므로 평소처럼 연결됩니다. 로컬에서만 수신 대기할 때는 `Host`도 확인해, 외부 도메인을 로컬로 해석시키는 공격을 막습니다.
- 역할은 연결할 때 클라이언트가 스스로 선언합니다. 로컬 모드에서는 약속일 뿐 보안 경계가 아닙니다. 아레나에서는 누가 어떤 역할을 받을지 심판 프로세스가 정합니다. [아레나](https://war3ai.com/ko/arena/)를 참고하세요.
