# task-3000 — 프론트엔드 영향 분석 (브리짓/개발3팀)

- 대상: `chat_register_push_subscription` / `chat_unsubscribe_push` RPC 호출부 전수, 서비스워커 endpoint 형태, 기존 vitest 봉인 테스트, 회귀 기준선 실측
- worktree: `/home/jay/projects/InsuRo/.worktrees/task-3000-dev3` (base `96ad2b2`)
- **코드 수정 없음 — 읽기 전용 분석만 수행**

## 1. RPC 호출부 전수 조사

### `chat_register_push_subscription`
- 정의/호출 지점: `/home/jay/projects/InsuRo/.worktrees/task-3000-dev3/src/lib/push-utils.ts:49` (`registerCustomerSubscription` 내부)
- `p_endpoint` 값: `subscription.toJSON().endpoint` (원본 그대로, 가공/래핑 없음). `JSON.stringify` 로 감싸지 않는다 — `push-utils.ts:229-236` 에서 `subJson.endpoint!` 를 그대로 `endpoint` 인자로 전달.
- `.error` 검사: 되어 있다 (`push-utils.ts:56-63`). error 있으면 `console.error` 로 마스킹된 사유(`error.message` 만)를 남기고 `false` 반환.
- `data !== true` 검사도 별도로 되어 있다 (`push-utils.ts:65-72`) — RPC 가 `false`/`null`/비정상 값을 돌려줘도 `false` 로 취급.
- **`false` 반환 시 프론트 처리**: 이 함수를 직접 호출하는 곳은 두 곳.
  - `/home/jay/projects/InsuRo/.worktrees/task-3000-dev3/src/components/PushNotificationToggle.tsx:84-96` — `customer` 경로. `ok` boolean 을 명시 검사하고, `false` 면 `toast.error("알림 활성화에 실패했습니다. 브라우저 설정을 확인해 주세요.")` 로 **사용자에게 알린다**. 조용히 삼키지 않음. 다만 토스트 문구가 "브라우저 설정을 확인해 주세요" 로 고정돼 있어, 화이트리스트 거부(구조적 서버측 사유)를 브라우저 문제로 오인시킬 소지는 있음(사유 미분기 — RPC 도 사유를 구분해 주지 않으므로 프론트가 구분할 수 없는 구조).
  - `/home/jay/projects/InsuRo/.worktrees/task-3000-dev3/src/pages/CrmMessenger.tsx:122-125` — `agent` 경로. `registerPushSubscription(selectedConv.id, 'agent').catch(() => {})` — 반환된 boolean 을 전혀 검사하지 않는 fire-and-forget. 단, 이 경로는 `subscriberType='agent'` 이므로 **RPC 를 타지 않는다**(테이블 직접 접근, `push-utils.ts:166-184`). 화이트리스트가 RPC 내부(customer 전용 함수)에만 적용된다면 이 호출부는 영향권 밖.
- `PushNotificationToggle` 을 `subscriberType="customer"` 로 쓰는 곳은 `/home/jay/projects/InsuRo/.worktrees/task-3000-dev3/src/pages/CustomerChat.tsx:299` 1곳뿐(전수 확인, grep 기준).

### `chat_unsubscribe_push`
- 정의: `push-utils.ts:257` (`unsubscribeCustomer` 내부), `.error`/`data!==true` 검사 동일하게 되어 있음 (`push-utils.ts:262-277`).
- **프로덕션 호출부 전수 결과: 0건.** `unsubscribePush` (export 함수, `chat_unsubscribe_push` 를 호출하는 유일한 경로)를 import 하는 곳은 `src/lib/__tests__/push-utils.test.ts` 뿐이며, 컴포넌트 어디서도 `unsubscribePush` 를 호출하지 않는다.
- 실제로 `PushNotificationToggle.tsx` 의 해제(unsubscribe) 분기(`handleToggle`, 65-76행)는 `subscription.unsubscribe()` (브라우저 레벨)만 호출하고 `toast.success("알림이 해제되었습니다")` 를 띄운 뒤 끝난다 — **백엔드 RPC 를 아예 호출하지 않는다.** 이는 이번 task-3000 범위(RPC 검증)와 별개의 기존 결함으로 보이며(서버 DB 에 구독이 `is_active=true` 로 남아 push 발송 대상에서 안 빠짐), 참고로만 기록. 화이트리스트 도입이 이 경로에 미치는 영향은 없음(애초에 안 불림).

## 2. 서비스워커 실측

- `public/sw-push.js` (`/home/jay/projects/InsuRo/.worktrees/task-3000-dev3/public/sw-push.js`): `push` / `notificationclick` 이벤트 핸들러만 존재. **`pushManager.subscribe` 호출은 여기 없다.**
- `pushManager.subscribe` 실호출부는 저장소 전체에서 **1곳뿐**: `push-utils.ts:224-227` (메인 스레드 코드, `registerPushSubscription` 내부).
  - `userVisibleOnly: true`
  - `applicationServerKey: urlBase64ToUint8Array(vapidKey)` — VAPID 공개키 사용 확인. `vapidKey` 는 `VITE_VAPID_PUBLIC_KEY` env 우선, 없으면 `get-vapid-key` 엣지함수 호출로 조회 (`push-utils.ts:213-221`).
- endpoint 는 `subscription.toJSON().endpoint` 로 얻어지며, 이는 브라우저/푸시서비스가 생성하는 실제 URL(Chrome=FCM `https://fcm.googleapis.com/...`, Firefox=Mozilla autopush, Safari=`web.push.apple.com` 등)이다. **이 worktree 에서 실제 브라우저로 구독을 발생시켜 endpoint 문자열을 실측하지는 않았다** — 정적 코드 분석 결과만이며, 실제 브라우저별 endpoint prefix 는 이 리포트 범위 밖(별도 E2E 필요).

## 3. 기존 테스트 파악 (`src/lib/__tests__/push-utils.test.ts`)

- 커버리지: customer 등록(★봉인1~5-b), agent 등록(★봉인6~), unsubscribe(customer/agent), 로그 마스킹(인증 재료 미노출), `.maybeSingle()` 회귀 금지(로키 H1) 등 폭넓게 커버.
- endpoint mock 값: `SECRET.endpoint = "https://fcm.googleapis.com/fcm/send/SECRET-ENDPOINT-9f3a7b"` (`push-utils.test.ts:178`).
- **RPC 호출 자체가 완전히 mock 처리**되어 있다 (`vi.mock("@/integrations/supabase/client", ...)` 로 `supabase.rpc` 를 `mockRpc` 로 대체, `push-utils.test.ts:141,163-169`, 기본값은 `mockRpc.mockResolvedValue({ data: true, error: null })`, `push-utils.test.ts:290`). 즉 이 테스트는 **실제 Postgres 함수를 전혀 실행하지 않고**, `push-utils.ts` 가 어떤 인자로 `supabase.rpc(...)` 를 호출했는지, 그리고 mock 이 돌려준 반환값을 `push-utils.ts` 가 어떻게 처리하는지만 검증한다.
- **판정: SQL 에 화이트리스트를 넣어도 이 vitest 파일은 깨지지 않는다.** 근거: (1) 테스트가 실제 Supabase DB/RPC 를 타지 않고 mock 만 사용, (2) mock 의 기본 endpoint 값(`fcm.googleapis.com`)은 통상적인 화이트리스트(FCM/Mozilla/Apple push 도메인)에 포함될 형태이기도 하고, (3) 설사 화이트리스트에 안 걸리는 형태였다 해도 mock 이 `data: true` 를 고정 반환하므로 SQL 로직 자체가 테스트 실행 경로에 개입할 여지가 없다. 단, 이는 **"프론트 vitest 는 안전하다"는 뜻이지 "실제 anon 사용자가 이 RPC 를 호출할 때 화이트리스트에 안 걸린다"는 뜻은 아니다** — 그 실측은 프로덕션/스테이징 DB 대상 SQL 레벨 검증이 별도로 필요하다(이 분석 범위 밖).

## 4. 회귀 기준선 실측

- `npm ci`: 실행함(`node_modules` 없었음) — 정상 완료, 1101 packages added. audit 경고(31건, 취약점)는 기존 lockfile 상태이며 이번 분석과 무관.
- `npx vitest run` 실측 결과:
  - **Test Files: 93 passed (93)**
  - **Tests: 1349 passed (1349)**
  - 실패 0건
  - Duration 17.66s
- `npm run build` 실측 결과:
  - **EXIT CODE: 0** (파이프 오염 없이 `npm run build > log 2>&1; echo $?` 로 재확인)
  - Vite 빌드 성공, PWA precache 180 entries 생성, `dist/sw.js` / `dist/workbox-*.js` 생성 확인
  - 500kB 초과 청크 경고 있음(`pdf-libs`, `charts`, `ort.*` 등) — 기존부터 있던 경고로 보이며 이번 변경과 무관, 빌드 실패 아님

이 수치(vitest 93/1349 all pass, build exit 0)가 **base 기준선**이다. 이후 SQL 화이트리스트 적용 후 대조 시 이 값과 비교할 것.

## 결론 요약

- customer 경로 등록(`registerPushSubscription`)은 `false` 를 조용히 삼키지 않는다 — `PushNotificationToggle.tsx` 가 `ok` 를 검사해 `toast.error` 로 사용자에게 알린다. 화이트리스트로 `false` 가 늘어나도 **사용자는 실패를 인지할 수 있다**(단, 사유 문구가 "브라우저 설정 확인"으로 고정돼 있어 원인 오인 가능성은 있음).
- `chat_unsubscribe_push` 는 현재 프로덕션 프론트 어디서도 호출되지 않는다(0곳) — 화이트리스트 영향 없음, 별도로 기존에 있던 "해제 버튼이 백엔드에 통보하지 않는다"는 결함을 발견(참고용, 이번 task 범위 아님).
- agent 경로(`CrmMessenger.tsx`)는 RPC 를 타지 않으므로 화이트리스트와 무관.
- `p_endpoint` 는 항상 `subscription.toJSON().endpoint` 원본 문자열이 그대로 전달된다(가공/JSON.stringify 없음).
- 서비스워커(`sw-push.js`)는 구독 생성에 관여하지 않는다 — `pushManager.subscribe` 호출은 `push-utils.ts` 1곳뿐, VAPID(`applicationServerKey`) 사용 확인.
- 기존 vitest 봉인 테스트(`push-utils.test.ts`)는 RPC 를 완전히 mock 하므로 **SQL 화이트리스트 도입으로 깨지지 않는다** — 프론트 vitest 는 SQL 레벨 회귀 검출 수단이 아님(별도 DB 레벨 검증 필요).
- 회귀 기준선 실측: vitest 93 files / 1349 tests **all pass**, `npm run build` **exit 0**.
