
# Web API

ECMAScript 자체 외에도 Phasis는 여러 계층형 WHATWG / W3C API 팩을 제공합니다.

- **웹 플랫폼 팩** — I/O가 없는 순수 값 유형: `URL`, `URLSearchParams`, `URLPattern`, `TextEncoder`, `TextDecoder`, `atob`, `btoa`, `structuredClone`, `performance`, `DOMException`.
- **Fetch 팩** — JavaScript의 실제 HTTP: `fetch`, `Request`, `Response`, `Headers`, `Body`, `AbortController`, `AbortSignal`, `Blob`, `File`, `FormData`, `EventTarget`, `Event`, WHATWG Streams 전체(`ReadableStream`, `WritableStream`, `TransformStream`, 큐잉 전략, BYOB), `CompressionStream` / `DecompressionStream`(zlib를 통한 gzip / deflate / deflate-raw, `kjdev/php-ext-brotli` PECL 확장 로드 시 brotli), `navigator`, 그리고 HTML 사양의 `self` 전역 별칭 + `reportError`.
- **암호화** — `crypto.getRandomValues`, `crypto.randomUUID`, `SubtleCrypto` 전체: digest(SHA-1/256/384/512), HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA(P-256/384/521), ECDH, HKDF, PBKDF2 및 JWK 가져오기/내보내기. `ext-openssl`를 기반으로 합니다.
- **WebSocket** — 생성자, RFC 6455 프레임 코덱, 이벤트를 제공합니다. 기본 전송은 `stream_socket_client`를 사용하며 ReactPHP / Ratchet / 테스트 목의 경우 `Engine::setWebSocketTransport()`로 교체할 수 있습니다.
- **XMLHttpRequest** — fetch 전송 계층 위에 구축되어 같은 정책 훅과 교체 가능한 전송을 적용합니다.
- **이벤트 루프** — `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `queueMicrotask`를 제공합니다. 또한 마이크로태스크 / 타이머 경계를 넘어 값을 전파하는 `AsyncContext`(Stage 3)도 지원합니다.

모든 팩은 **기본적으로 활성화**되어 있습니다. 플래그나 설정 없이 새 `new Engine()`에서 `typeof fetch`가 `"function"`를 반환합니다.

## 적합성

가져온 [Web Platform Tests](https://github.com/web-platform-tests/wpt) 모음(`tests/Wpt/fixtures/`)은 `AbortController` / `AbortSignal`, `atob` / `btoa`, `Blob` / `File`, `TextEncoder` / `TextDecoder`, `EventTarget` / `Event`, `FormData`, `Headers`, `performance.now`, Streams Standard 전체(`Readable`/`Writable`/`Transform` + BYOB + 큐잉), `structuredClone`, `URL` / `URLSearchParams`, **`crypto` / `SubtleCrypto`** 전체 범위(digest, HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH, HKDF, PBKDF2 — `hkdf` 및 `pbkdf2`의 수천 가지 derive-bits 조합 포함), **`WebSocket`** 상태 머신(생성자 URL 및 하위 프로토콜 검증, 종료 코드 의미론, 문자열 / `ArrayBuffer` / 형식화된 배열 뷰 / `Blob`를 위한 `send()`, `binaryType`, `bufferedAmount`, 연결 중 비동기 종료, 이벤트 핸들러 null 허용 여부), 그리고 **`Fetch`** + **`XMLHttpRequest`** 의 다양한 부분(`responseType` 상태 머신, 중단/전송 전환, CORS 허용 헤더, 멀티파트 `FormData` 업로드, `XMLHttpRequestUpload` + `ProgressEvent`, `Response`/`Headers` 초기화 검증, 정적 `Response.json` / `Response.error` 생성자, 리디렉션 체인에서 Authorization 제거, `URL.createObjectURL` Blob URL, 본문 믹스인 소비(`text` / `blob` / `json` / `formData` / `arrayBuffer`), `structuredClone`-d 티 스트림을 사용하는 `Response.clone`, 동기 XHR의 네트워크 오류 발생)을 다룹니다. 가져온 모든 하위 테스트가 통과합니다.

범위를 더 넓히려면 새 `*.any.js`를 `tests/Wpt/fixtures/<category>/`에 추가하면 됩니다. 실행기가 다음 실행에서 자동으로 처리합니다. XHR/Fetch 테스트는 WPT `resources/*.py` 핸들러를 단일 파일로 에뮬레이션하는 `tests/Wpt/fetch-server.php`가 실행합니다. WebSocket 테스트는 실행기 내부의 비활성 전송으로 연결/에코/종료를 시뮬레이션하며, 가져온 픽스처를 실행하기에 충분합니다. 직접 테스트 모음을 실행하려면 다음을 사용하세요.

```bash
bin/wpt                # everything
bin/wpt --category headers --verbose   # one area, per-subtest output
bin/wpt --json         # machine-readable
```

## fetch() 예제

```js
const res = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "alice" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();
```

기본 설정만으로 작동합니다. 기본 전송 방식은 PHP `ext-curl`입니다. 본문은 WHATWG `ReadableStream` Phasis를 통해 스트리밍되므로 `for await (const chunk of res.body)`도 작동합니다.

## AbortController 예제

```js
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5000);
try {
  const res = await fetch(slowUrl, { signal: ctrl.signal });
} catch (e) {
  if (e.name === "AbortError") {
    // request was cancelled
  }
}
```

`AbortSignal.timeout(ms)`도 구현되어 있습니다(지연 마감 방식 — 이벤트 루프 도중 이벤트를 발생시키는 대신 `aborted` 읽기 시 확인합니다. 전송 중 신호를 확인하는 fetch 전송과 함께 작동합니다).

## 임베더 제어(PHP 측)

`Phasis\Engine` 클래스의 세 가지 훅으로 fetch 동작을 제어합니다.

```php
$engine = new Phasis\Engine();

// 1. Swap the HTTP transport. The callable receives a request descriptor
//    and an optional AbortSignal; returns a response descriptor.
$engine->setFetchTransport(function (array $request, $signal) {
    // ... use Guzzle, Symfony HttpClient, your stub, etc.
    return [
        'status' => 200,
        'statusText' => 'OK',
        'headers' => [['content-type', 'application/json']],
        'body' => '{"data":"value"}',
    ];
});

// 2. Pre-flight policy hook — rewrite, deny, or allow.
$engine->setFetchPolicy(function ($request) {
    $url = $request->get('url')->value;
    if (!str_starts_with($url, 'https://')) {
        throw new \RuntimeException('HTTPS only');
    }
    return null;  // allow as-is; return modified Request to rewrite
});

// 3. Cookie jar. Opt-in. Accepts any object with get(url):string and
//    set(url, header):void methods. JsObject or PHP object both work.
$engine->setCookieJar($myJar);
```

`setFetchTransport()`를 호출하지 않으면 기본 `CurlTransport`가 실행됩니다. 쿠키 저장소는 **선택 사항**입니다. 기본 저장소가 없으며 직접 제공하지 않는 한 쿠키가 저장되지 않습니다.

## Streams 예제

```js
// Producer: ReadableStream that yields numbers 1..5
const rs = new ReadableStream({
  start(controller) {
    for (let i = 1; i <= 5; i++) controller.enqueue(i);
    controller.close();
  },
});

// Consumer: async iteration
for await (const value of rs) {
  console.log(value);  // 1, 2, 3, 4, 5
}

// Pipe through a transform
const ts = new TransformStream({
  transform(chunk, controller) { controller.enqueue(chunk * 2); },
});
const doubled = new ReadableStream({
  start(c) { [1, 2, 3].forEach(n => c.enqueue(n)); c.close(); }
}).pipeThrough(ts);
for await (const v of doubled) console.log(v);  // 2, 4, 6
```

Streams 사양 전체: `ReadableStream`, `WritableStream`, `TransformStream`, `ReadableStreamDefaultController`, `ReadableByteStreamController`, `ReadableStreamDefaultReader`, `ReadableStreamBYOBReader`, `ReadableStreamBYOBRequest`, `WritableStreamDefaultController`, `WritableStreamDefaultWriter`, `TransformStreamDefaultController`, `ByteLengthQueuingStrategy`, `CountQueuingStrategy`, 그리고 `pipeTo` / `pipeThrough` / `tee`.

## 이벤트 루프

`setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `queueMicrotask`는 전역 객체에 있습니다. 마이크로태스크(Promise 핸들러, `queueMicrotask`)는 매크로태스크 사이에 모두 처리되며, 타이머는 영역별 큐에서 마감 시간 순으로 실행됩니다.

```js
console.log('sync');
queueMicrotask(() => console.log('microtask'));
setTimeout(() => console.log('timer'), 0);
// → sync, microtask, timer

const id = setInterval(() => console.log('tick'), 1000);
// ...later
clearInterval(id);

await new Promise(r => setTimeout(r, 50));   // common "delay" pattern works
```

Phasis의 `Engine::runEventLoop()`를 기반으로 합니다. 동기 코드가 끝나면 `eval()`와 `evalAsModule()`가 자동으로 호출됩니다. 루프는 기본적으로 **가상 시간**을 사용합니다. `setTimeout(cb, 5000)`는 PHP 동안 대기하지 않습니다. 대신 루프가 마감 시간까지 시계를 진행시켜 프로그램을 신속히 완료합니다. 실시간 임베더는 직접 `tickEventLoop()`를 구동합니다. 호스트 측 API는 [API: 이벤트 루프](https://phasis.inline0.dev/ko/docs/api#event-loop)를 참조하세요.

## 제공되지 않는 기능

브라우저 전용 API 또는 런타임 모델 API는 의도적으로 범위에서 제외했습니다. 자세한 내용은 [알려진 제한 사항](https://phasis.inline0.dev/ko/docs/compatibility/limitations)을 참조하세요.

- `window`, `document`, DOM 트리
- `localStorage`, `sessionStorage`, `IndexedDB`
- `Worker`, `SharedWorker`, `MessageChannel`
- Service Worker, Push API, 알림, BroadcastChannel

필요한 경우 대부분은 임베더가 `$engine->setGlobal()`를 통해 폴리필로 구현할 수 있습니다.

## WPT 테스트 모음 확장

`tests/Wpt/upstream/`는 [web-platform-tests/wpt](https://github.com/web-platform-tests/wpt)의 sparse-checkout으로, `fetch/`, `streams/`, `FileAPI/`, `xhr/formdata/`, `dom/abort/`, `dom/events/`를 포함합니다. 범위를 넓히려면 `upstream/<area>/`의 `*.any.js` 파일을 `tests/Wpt/fixtures/<category>/`에 복사하세요. 다음 실행부터 실행기가 자동으로 처리하며, 실패는 `bin/wpt --verbose`를 통해 기록됩니다.
