banner_enabled(쿠키 배너 노출) 토글 안내가 "마이페이지 동의 관리 카드"까지 함께 켜지고 꺼지는 것처럼 서술되어 있었으나, 실제로는 카드가 이 토글과 무관하게 동의/철회 이력이 있는 회원에게 항상 노출된다(GDPR Art.7(3) 철회 대칭성 보장, mypage_privacy_tab.json 회귀 가드로 이미 고정된 설계). 문구만 실제 동작과 어긋나 있어 ko/en 양쪽 hint 및 관련 코드 주석을 바로잡았다. 문구 확인 과정에서 field_legal_entity_name / field_data_storage_location / field_banner_enabled / field_banner_position 4개 필드가 텍스트 스타일 전용 클래스(section-heading-md, grid 미정의)를 컨테이너로 잘못 사용해 안내 문구가 길어지면 입력/토글 컨트롤이 아래 줄로 밀려나던 기존 UI 결함도 함께 발견해 수정했다. 같은 파일의 field_privacy_policy_slug가 이미 쓰던 올바른 grid-3col-responsive 패턴으로 통일.
154 lines
5.4 KiB
TypeScript
154 lines
5.4 KiB
TypeScript
/**
|
|
* GDPR 레이아웃 확장 부트스트랩
|
|
*
|
|
* G7 코어가 `resources/extensions/*.json` 파일들을 자동 발견·등록하므로
|
|
* 별도의 명시적 등록 코드는 필요 없다. 본 모듈은:
|
|
*
|
|
* 1. 페이지 로드 직후 `/plugins/sirsoft-gdpr/settings` 호출하여 banner_enabled 토글 확인
|
|
* 2. `/plugins/sirsoft-gdpr/consent/cookie/status` 호출하여 카테고리별 동의 상태 페치
|
|
* (회원/게스트 통합 — 인증 불필요. 회원은 sanctum 토큰이 있으면 자동 적용)
|
|
*
|
|
* banner_enabled 가 단일 토글 (쿠키 배너 노출) — ON 시 배너 노출 + 동의 전 외부 추적
|
|
* 자동 차단이 함께 시작됨. 차단 별도 토글 없음 (위반 조합 구조적 차단).
|
|
*
|
|
* 마이페이지 동의 카드(F-04)는 banner_enabled 와 무관 — GDPR Art.7(3) 대칭성 의무(철회는
|
|
* 동의만큼 쉬워야 함)에 따라 회원에게 동의 데이터가 있으면 배너 비활성 중에도 항상 노출
|
|
* (빈 카드 노출만 방지). 가드 조건은 `mypage_privacy_tab.json` 의 `if` 속성에 정의.
|
|
*
|
|
* G7Core.api 가 로드된 환경에서는 G7Core.api.get() 사용 (auth/devtools/locale 헤더 자동),
|
|
* 미로드 환경(부트 초기 등)에서는 fetch 로 fallback.
|
|
*
|
|
* @module sirsoft-gdpr/layouts
|
|
*/
|
|
|
|
export interface GdprPublicSettings {
|
|
cookie_policy_version: string;
|
|
/**
|
|
* 쿠키 배너 노출 단일 토글. ON 시 배너 + 자동 차단이 함께 활성.
|
|
* 마이페이지 동의 관리 카드는 이 값과 무관 (동의/철회 이력 존재 여부로만 판정).
|
|
*/
|
|
banner_enabled: boolean;
|
|
/**
|
|
* 카테고리 → 차단 도메인 패턴 배열 (정확 매칭 또는 `*.example.com` 와일드카드).
|
|
* 게스트도 차단 동작해야 하므로 공개 응답에 포함.
|
|
*/
|
|
blocked_domains?: Record<string, string[]>;
|
|
/**
|
|
* 기본 카탈로그(GA/GTM/Meta Pixel 등) 자동 적용 토글. default OFF.
|
|
*/
|
|
blocked_domains_default_catalog?: boolean;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface G7ApiClient {
|
|
get: <T = unknown>(url: string) => Promise<T>;
|
|
}
|
|
|
|
let cachedSettings: GdprPublicSettings | null = null;
|
|
|
|
/**
|
|
* G7Core.api 클라이언트 반환 (없으면 null).
|
|
*
|
|
* @return G7ApiClient | null
|
|
*/
|
|
function resolveG7Api(): G7ApiClient | null {
|
|
if (typeof window === 'undefined') return null;
|
|
const g7Core = (window as unknown as { G7Core?: { api?: G7ApiClient } }).G7Core;
|
|
const api = g7Core?.api;
|
|
if (api && typeof api.get === 'function') return api;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* G7Core.api 또는 fetch 로 GET 호출.
|
|
*
|
|
* @param path API 경로 (G7Core.api 는 `/api` 자동 prefix, fetch 는 `/api/...` 명시)
|
|
* @return JSON 데이터 또는 null
|
|
*/
|
|
async function getJson<T>(path: string): Promise<T | null> {
|
|
const api = resolveG7Api();
|
|
if (api) {
|
|
try {
|
|
return await api.get<T>(path);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Fallback: G7Core 미로드 (부트 초기) — fetch 직접 사용
|
|
try {
|
|
const response = await fetch('/api' + path, {
|
|
method: 'GET',
|
|
headers: { Accept: 'application/json' },
|
|
credentials: 'same-origin',
|
|
});
|
|
if (!response.ok) return null;
|
|
return (await response.json()) as T;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공개 설정 페치 + 캐시.
|
|
*
|
|
* @return GdprPublicSettings
|
|
*/
|
|
export async function fetchPublicSettings(): Promise<GdprPublicSettings | null> {
|
|
if (cachedSettings) return cachedSettings;
|
|
|
|
const json = await getJson<{ data?: GdprPublicSettings }>('/plugins/sirsoft-gdpr/settings');
|
|
const data = json?.data;
|
|
if (!data) return null;
|
|
|
|
cachedSettings = data;
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* 현재 방문자(회원/게스트)의 카테고리별 쿠키 동의 상태를 페치합니다.
|
|
*
|
|
* 자동 차단 엔진(blocker) 의 초기 캐시 주입용. 응답 키는 cookie_ 접두사가 제거된
|
|
* 카테고리 키 (예: 'analytics', 'marketing'). 정책 버전이 일치하지 않거나 동의가
|
|
* 없으면 빈 객체 반환.
|
|
*
|
|
* needs_renewal=true 면 옛 정책 동의가 신정책에 그대로 쓰이는 상태 — 차단 엔진이
|
|
* 필수 외 모든 카테고리 강제 false 처리 (보수적 차단, GDPR Art.6 강화).
|
|
* current_policy_version 은 배너 사유 안내문 ("버전 X 로 변경") 용.
|
|
*
|
|
* 인증 불필요 — 회원이 sanctum 토큰을 가지고 있으면 자동으로 user_id 기반 응답.
|
|
*
|
|
* @return 동의 스냅샷 (categories + needs_renewal + current_policy_version), 또는 페치 실패 시 null
|
|
*/
|
|
export interface FetchedConsentSnapshot {
|
|
categories: Record<string, boolean>;
|
|
needs_renewal: boolean;
|
|
current_policy_version: string;
|
|
}
|
|
|
|
export async function fetchConsentSnapshot(): Promise<FetchedConsentSnapshot | null> {
|
|
const json = await getJson<{
|
|
data?: {
|
|
consents?: Record<string, boolean>;
|
|
needs_renewal?: boolean;
|
|
current_policy_version?: string;
|
|
};
|
|
}>('/plugins/sirsoft-gdpr/consent/cookie/status');
|
|
|
|
const consents = json?.data?.consents;
|
|
if (consents === undefined || consents === null) return null;
|
|
|
|
return {
|
|
categories: consents,
|
|
needs_renewal: json?.data?.needs_renewal === true,
|
|
current_policy_version: String(json?.data?.current_policy_version ?? ''),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 캐시 클리어 — 테스트용.
|
|
*/
|
|
export function clearSettingsCache(): void {
|
|
cachedSettings = null;
|
|
}
|