Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/cgv-network-analysis-result.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# CGV 네트워크 분석 결과 (실측 업데이트)

## 직접 호출 복구 (2026-09-21, KST)

현재 비용 정책에서는 Zyte 유료 호출을 사용하지 않습니다. 아래 과거 기록의 Zyte fallback 전략은 현재 구현 지침이 아닙니다.

### 원인과 최소 수정

기존 서명 헤더로 극장 목록을 호출하면 Cloudflare HTML 403이 발생했습니다. 같은 서명과 URL에서 헤더만 바꾼 통제 실험 결과는 다음과 같습니다.

| 추가 헤더 | 실측 결과 |
| --- | --- |
| 없음 | HTTP 403 HTML |
| Chrome 140의 전체 User-Agent, Origin, Referer | HTTP 200 JSON |
| Chrome 140의 전체 User-Agent만 | HTTP 200, statusCode 0, 9개 지역 / 176개 극장 |
| Origin, Referer만 | HTTP 403 HTML |
| 일반 `Mozilla/5.0` User-Agent만 | HTTP 403 HTML |

기존 HMAC 키와 서명 규칙은 그대로 승인되었습니다. 확인된 원인은 기본 fetch User-Agent에 민감한 upstream 필터이며, `transport.ts`에 실측한 전체 브라우저 User-Agent만 추가했습니다. 서명·엔드포인트·차단 오류 처리 및 유료 호출 비활성화 정책은 유지합니다. 헤더의 특정 토큰까지 분해해 필터 규칙을 추측하지는 않았습니다.

### 검증

- 신규 회귀 테스트가 수정 전 HTTP 403으로 실패하는 것을 확인한 뒤 구현했습니다.
- `npx vitest run tests/services/cgv`: 8개 파일, 87개 테스트 통과.
- 수정된 실제 클라이언트 함수의 로컬 직접 호출: 극장 176개, 강남(`0056`) 영화 9개, 2026-09-21 시간표 29개.
- `wrangler dev --remote` Cloudflare 미리보기에서 수정된 앱 경로 호출: `/api/cgv/theaters?limit=3`, `/api/cgv/movies?theaterCode=0056&playDate=20260921`, `/api/cgv/timetable?theaterCode=0056&playDate=20260921&limit=3` 모두 HTTP 200, 비어 있지 않은 결과 확인.
- Cloudflare 미리보기에서도 User-Agent를 잠시 제거한 대조군은 원본 HTTP 403 / 앱 HTTP 503, 복원한 실험군은 HTTP 200 / 극장 3개로 확인했습니다. 경로의 24시간 edge cache 영향을 배제하기 위해 매 요청에 서로 다른 `probe` 쿼리를 사용했습니다. 처음 동일 URL로 비교한 캐시 응답은 원인 판단에서 제외했습니다.
- 미리보기 성공은 해당 Cloudflare 실행 환경의 근거입니다. 실제 운영 배포나 GitHub Actions 실행은 이 조사에서 수행하지 않았으므로 모든 IP의 접근을 보장하지 않습니다.

---

작성일: 2026-03-04 (KST)
대상:
- `https://www.cgv.co.kr`
Expand Down
29 changes: 29 additions & 0 deletions docs/gs25-seveneleven-transport-recovery-20260921.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# GS25·세븐일레븐 직접 호출 추가 조사

조사일: 2026-09-21 (KST). 유료 프록시 호출이나 운영 배포 없이 제한된 요청으로 확인했습니다.

## 세븐일레븐

공식 웹 `https://new.7-elevenapp.co.kr/`은 로컬에서 기존 Android User-Agent로 HTTP 200이며, Incapsula 스크립트를 포함합니다. 여기서 참조하는 최신 공식 번들 `https://static.7-elevenapp.co.kr/k7app/_nuxt/app.js`를 읽었습니다.

`useCustomFetch`는 GET 요청에 params, 그 외 요청에 body를 사용하며 `credentials: "include"`를 지정합니다. 확인한 공통 래퍼에 새로운 필수 고정 헤더를 추가하는 코드는 없었습니다. 이는 별도 쿠키 초기화 필요성을 확인할 근거이지만, 인증이 불필요한 모든 엔드포인트를 보장하지는 않습니다.

동일한 Cloudflare 원격 미리보기 Worker(HKG)에서 다음 세션 초기화 대조를 수행했습니다.

1. 기존 API 헤더로 동일 호스트 첫 페이지 GET: HTTP 403, Set-Cookie 2개.
2. 쿠키를 같은 Worker 요청 내에서만 보관해 공개 상품 검색 POST에 전달: HTTP 403, `Request unsuccessful` HTML.

쿠키 값은 출력하거나 저장하지 않았습니다. 첫 페이지 초기화 자체가 차단되므로 이 절차는 데이터 경로를 복구하지 못했습니다. 부모 조사에서 기존 User-Agent, 전체 데스크톱 User-Agent, Origin·Referer 조합 역시 원격 403이었습니다. 새 헤더나 API 경로 변경을 뒷받침하는 증거가 없어 서비스 코드는 변경하지 않았습니다.

## GS25

기존 상품 검색 요청 계약은 `POST https://b2c-apigw.woodongs.com/search/v3/totalSearch`, JSON `{query}`, 전체 Android User-Agent 및 기존 Origin·Referer입니다.

동일한 공개 앱 경로 `/api/gs25/products?keyword=%EC%BD%9C%EB%9D%BC`에 서로 다른 `probe` 값을 붙여 캐시를 우회했습니다.

| 실행 환경 | 결과 |
| --- | --- |
| 현재 코드 Cloudflare 원격 미리보기 | HTTP 200, 상품 20개 |
| 운영 `https://mcp.aka.page` | HTTP 500, `GS25_PRODUCT_SEARCH_FAILED`, 원본 HTTP 403 CloudFront HTML |

환경 차이를 재현했지만 운영과 미리보기의 정확한 차단 기준은 확인되지 않았습니다. 현재 요청 계약이 미리보기에서 실데이터를 반환하므로 서명·헤더 변경으로 운영 차단을 해결할 수 있다고 주장하지 않습니다. 운영 복구를 검증한 코드 변경은 없습니다.
18 changes: 17 additions & 1 deletion docs/oliveyoung-free-relay.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
# 올리브영 무료 브라우저 릴레이

2026-09-15 검증: 이 Mac의 일반 HTTP 요청은 올리브영 검색 API에서 HTTP 403 보안 검증 HTML을 받았습니다. 개인 프로필과 분리한 **화면 있는 Chromium**에서는 네 API 모두 HTTP 200, `status: SUCCESS`였습니다. 새 headless 브라우저는 보안 검증 화면에 머물렀습니다. 따라서 GUI 로그인 세션, 브라우저, 전원과 네트워크를 유지할 수 있는 운영 호스트가 필요합니다. 설정만 추가했다고 복구된 것으로 판단하면 안 됩니다.
## 2026-09-21 Windows 로컬 검증

Windows 11의 설치된 Chrome을 별도 임시 컨텍스트로 실행하여 기존 `createBrowserRunner`와 `createOliveyoungRelay`를 검증했습니다. 개인 프로필·로그인 정보는 사용하지 않았습니다. Node 클라이언트가 `127.0.0.1`의 임시 포트에 생성한 인증 릴레이를 거쳐 브라우저에서 조회하는 전체 경로를 확인했습니다.

- 상품 검색: 선크림 399개, 상품 번호 `A000000263510`.
- 매장 검색: 강남 기준 50개.
- 상품 상세에서 실제 `masterGoodsNumber`를 구한 뒤 매장별 재고 조회: `checkedCount: 1`, `stockSource: nearby_store`.
- 매장별 재고: 50개 중 재고 있음 47개. 수량은 조회 시점 값입니다.
- 실행 종료 시 임시 HTTP 서버와 소유한 브라우저를 닫았습니다. 인증 토큰은 메모리에서 생성했으며 출력하거나 저장하지 않았습니다.

이 검증은 Windows에서도 브라우저 조회와 로컬 HTTP 릴레이를 시험할 수 있음을 보여 줍니다. 기존 `start.ts`의 상시 프로세스 감시와 설치 도구는 POSIX/macOS용이므로 Windows 상시 운영 지원을 의미하지 않습니다. 공개 Worker에서 이 PC로 연결되는 터널이나 자동 실행은 구성하지 않았습니다.

2026-09-15 검증: 이 Mac의 일반 HTTP 요청은 올리브영 검색 API에서 HTTP 403 보안 검증 HTML을 받았습니다. 개인 프로필과 분리한 **화면 있는 Chromium**에서는 네 API 모두 HTTP 200, `status: SUCCESS`였습니다. 새 headless 브라우저는 보안 검증 화면에 머물렀습니다. 이 브라우저 릴레이를 사용하려면 GUI 로그인 세션, 브라우저, 전원과 네트워크를 유지할 수 있는 운영 호스트가 필요합니다. 설정만 추가했다고 복구된 것으로 판단하면 안 됩니다.

2026-09-21 Cloudflare 원격 프리뷰에서는 직접 요청의 런타임 오류를 수정하고 고정 브라우저 헤더를 추가해 상품·매장 조회의 실제 성공을 확인했습니다. 다만 같은 요청에서도 HTTP 403이 간헐적으로 발생하고 상품 상세 재고 API는 계속 실패했습니다. 상세 매장 재고까지 복구되었거나 릴레이 호스트가 더 이상 필요하지 않다고 판단하지 않습니다. 설정된 릴레이는 계속 우선 사용합니다.

Workers가 지원하지 않는 `redirect: 'error'` 대신 `manual`을 사용하며, HTTP 200만 허용하는 검사로 리다이렉트를 거절합니다. 토큰을 다른 주소로 따라 보내지 않습니다. User-Agent·Origin·Referer·Accept-Language는 공식 API 직접 요청에만 고정값으로 추가하며 릴레이에는 전달하지 않습니다. [조사 및 실제 검증 기록](superpowers/plans/2026-09-21-oliveyoung-recovery.md)을 참고하세요.

## 실행

Expand Down
37 changes: 37 additions & 0 deletions docs/superpowers/plans/2026-09-21-cgv-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# CGV direct transport recovery implementation plan

**Goal:** Restore free direct CGV requests where the upstream accepts browser request headers.

**Architecture:** Keep the existing endpoint, HMAC signing, normalization and explicit upstream errors. Add only the browser User-Agent proven necessary by controlled live requests. Do not add paid proxy calls or return empty success for failures.

**Tech stack:** TypeScript, fetch, Web Crypto, Vitest.

## Evidence

2026-09-21 local Windows network, `GET https://api.cgv.co.kr/cnm/atkt/searchRegnList?coCd=A420`:

| Request variation | Result |
| --- | --- |
| Existing signed headers | Cloudflare HTTP 403 HTML |
| Same signature + full Chrome User-Agent + Origin + Referer | HTTP 200 JSON |
| Same signature + full Chrome User-Agent only | HTTP 200, statusCode 0, 9 regions, 176 theaters |
| Same signature + Origin + Referer only | HTTP 403 HTML |
| Same signature + generic Mozilla/5.0 only | HTTP 403 HTML |

The existing signing key and message format are accepted. This isolates the local failure to User-Agent-sensitive upstream filtering. It does not establish that Cloudflare Workers or GitHub Actions IPs are accepted with the same header.

## Tasks

- [x] Reproduce the failure and isolate the request-header variable with bounded direct requests.
- [x] Add a regression test to `tests/services/cgv/transport.test.ts` asserting the observed required header and successful JSON handling. Run it and verify it fails before implementation.
- [x] Add the measured browser User-Agent to `src/services/cgv/transport.ts` without changing signing or error handling.
- [x] Run `npx vitest run tests/services/cgv` and repeat direct theater/movie/timetable calls using production functions.
- [x] Record results and remaining environment limitations in `docs/cgv-network-analysis-result.md`.

No commits, pushes or deployments are part of this delegated change.

## Verification outcome

Regression test failed with HTTP 403 before the change. All 87 CGV tests then passed. Local production functions returned 176 theaters, 9 movies and 29 timetable entries. Cloudflare remote preview returned HTTP 200 and nonempty data for all three CGV app routes. No production deployment was performed.

Controlled Cloudflare remote preview test with unique `probe` query parameters (to avoid edge cache): temporarily removing User-Agent produced upstream HTTP 403 / app HTTP 503; restoring User-Agent produced HTTP 200 with 3 theaters. The initial same-URL cached response was excluded from this comparison. The source was restored and the 14 transport tests passed again.
43 changes: 43 additions & 0 deletions docs/superpowers/plans/2026-09-21-cu-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# CU 매장 조회 복구 조사 및 구현 계획

## 범위

- `src/services/cu/**`, `tests/services/cu/**` 및 CU 조사 문서만 수정한다.
- 무료 공식 웹 조회를 조사한다. 유료 폴백이나 오류를 빈 성공으로 바꾸는 처리는 추가하지 않는다.

## 확인한 사실

2026-09-21 로컬 Node 환경에서 인증 없이 15초 제한으로 확인했다.

- 변경 전 `fetchCuStores({ searchWord: '강남' })`: 실제 매장 5개 반환.
- 공식 페이지 `https://cu.bgfretail.com/store/list.do?category=store`: HTTP 200.
- 공식 페이지의 `searchList(pageNo)`는 `type: "post"`, `/store/list_Ajax.do`, `$('#listForm').serialize()`를 사용한다.
- 공식 폼에는 현재 코드의 검색 필드와 빈 `user_id` 필드가 있다.
- 현재 클라이언트의 전체 헤더와 폼을 유지한 GET/POST 비교: 둘 다 HTTP 200, 동일 매장 코드 `39418`, `50212`, `48893`, `50615`, `51006` 반환.
- 따라서 GET이 공식 호출 방식이라는 기존 주석은 잘못되었지만, 메서드만으로 운영 HTTP 400의 원인이라고 결론 내릴 수 없다.
- 변경 전 CU 테스트: 4개 파일, 52개 테스트 통과.

## 다음 단계

1. Cloudflare 실행 환경에서 동일 GET/POST를 비교해 로컬 환경과의 차이를 확인한다.
2. 성공하는 요청 계약이 확인되면 해당 회귀 테스트를 먼저 작성하고 예상 실패를 확인한다.
3. 입증된 최소 수정만 적용하고 CU 테스트 및 실제 조회를 다시 실행한다.
4. 원격 환경에서도 모두 실패하면 메서드 변경을 복구로 주장하지 않고 제약과 증거를 기록한다.

## Cloudflare 원격 비교 결과

- 임시 `wrangler dev --remote` 프리뷰의 `/api/cu/stores?keyword=강남&limit=1`에서 변경 전 GET은 upstream HTTP 400을 반환했다.
- POST 계약 회귀 테스트를 먼저 수정하여 예상한 GET/POST 불일치 실패를 확인했다.
- 메서드와 폼 위치만 POST로 바꾼 임시 후보도 원격 요청 두 번 모두 같은 upstream HTTP 400을 반환했다.
- POST 복구 가설은 원격 비교에서 입증되지 않았다. 임시 소스와 테스트 변경은 모두 되돌렸다.
- 로컬에서 GET/POST가 모두 실제 매장을 반환하므로, 실행 환경에 따른 상위 서버 응답 차이가 남아 있다. 이것만으로 특정 차단 장비나 IP 정책을 확정할 수 없다.

## 원인 확인과 최종 수정

- 웹 전용 헤더에는 `User-Agent`가 명시되어 있지 않았다. Node와 Workers의 기본 헤더 차이를 가설로 세웠다.
- 헤더 계약 테스트에 `User-Agent: Mozilla/5.0` 기대값을 추가하고 기존 구현에서 실패함을 확인했다.
- 메서드는 기존 GET 그대로 두고 해당 헤더 하나만 추가했다. 원격 프리뷰가 HTTP 200과 실제 매장 5개로 바뀌었다.
- 헤더를 다시 제거하고 캐시를 피한 새 요청으로 upstream 400 재현을 확인했다.
- 헤더를 다시 추가한 새 요청은 다시 HTTP 200, 매장 코드 `39418`, 총 5개를 반환했다. 이 A/B/A 비교로 이번 원격 오류가 User-Agent 명시 여부에 달려 있음을 확인했다.
- 최종 수정은 웹 요청의 명시적 User-Agent와 회귀 테스트뿐이다. GET/POST 전환이나 재시도, 유료 폴백은 추가하지 않았다.
- 최종 CU 테스트: 4개 파일, 52개 통과. 프로덕션 배포는 수행하지 않았으며 운영 배포 후 동일 조회 확인이 필요하다.
Loading
Loading