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
14 changes: 14 additions & 0 deletions apps/extension/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const isProd = import.meta.env.PROD;

export const API_BASE_URL =
import.meta.env.WXT_PUBLIC_API_URL ||
(isProd ? 'https://api.dookmark.site' : 'http://localhost:3001');

export const WEB_BASE_URL =
import.meta.env.WXT_PUBLIC_WEB_URL ||
(isProd ? 'https://dookmark.site' : 'http://localhost:3000');

export const SOCKET_URL = `${API_BASE_URL}/bookmarks`;

// 열린 웹 탭을 찾을 때 쓰는 match 패턴 (browser.tabs.query)
export const WEB_TAB_MATCHES = isProd ? ['https://dookmark.site/*'] : ['http://localhost/*'];
11 changes: 5 additions & 6 deletions apps/extension/domain/auth/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { createApiClient, type TokenManager, type UserSession } from '@dookmark/
export type { UserSession };

import { browser } from 'wxt/browser';

const API_BASE_URL = 'http://localhost:3001';
import { API_BASE_URL, WEB_BASE_URL, WEB_TAB_MATCHES } from '@/config';

export async function getTokens(): Promise<{
accessToken: string | null;
Expand Down Expand Up @@ -49,11 +48,11 @@ async function importCookiesToStorage(): Promise<boolean> {
if (typeof browser === 'undefined' || !browser.cookies) return false;

const accessTokenCookie = await browser.cookies.get({
url: 'http://localhost:3001',
url: API_BASE_URL,
name: 'access_token',
});
const refreshTokenCookie = await browser.cookies.get({
url: 'http://localhost:3001',
url: API_BASE_URL,
name: 'refresh_token',
});

Expand Down Expand Up @@ -134,9 +133,9 @@ export async function logout(): Promise<void> {

// 2. 현재 열린 웹 탭들에 로그아웃 메시지 브로드캐스트
try {
const tabs = await browser.tabs.query({ url: 'http://localhost/*' });
const tabs = await browser.tabs.query({ url: WEB_TAB_MATCHES });
for (const tab of tabs) {
if (tab.id && tab.url?.includes(':3000')) {
if (tab.id && tab.url?.startsWith(WEB_BASE_URL)) {
await browser.tabs.sendMessage(tab.id, { type: 'FORCE_LOGOUT' }).catch(() => {});
}
}
Expand Down
7 changes: 3 additions & 4 deletions apps/extension/domain/auth/use-session.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import type { UserSession } from '@dookmark/api-client';
import { useEffect, useState } from 'react';
import { browser } from 'wxt/browser';
import { WEB_BASE_URL, WEB_TAB_MATCHES } from '@/config';
import { checkSession, logout } from './api';

const WEB_BASE_URL = 'http://localhost:3000';

export function useSession() {
const [user, setUser] = useState<UserSession | null>(null);
const [checkingSession, setCheckingSession] = useState(true);

useEffect(() => {
const requestSyncFromWebTabs = async () => {
try {
const tabs = await browser.tabs.query({ url: 'http://localhost/*' });
const tabs = await browser.tabs.query({ url: WEB_TAB_MATCHES });
for (const tab of tabs) {
if (tab.id && tab.url?.includes(':3000')) {
if (tab.id && tab.url?.startsWith(WEB_BASE_URL)) {
await browser.tabs.sendMessage(tab.id, { type: 'REQUEST_SYNC' }).catch(() => {});
}
}
Expand Down
3 changes: 1 addition & 2 deletions apps/extension/domain/bookmarks/sync/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import type {
} from '@dookmark/extension-bridge';
import { BOOKMARK_SOCKET_EVENTS } from '@dookmark/extension-bridge';
import { io, type Socket } from 'socket.io-client';

const SOCKET_URL = 'http://localhost:3001/bookmarks';
import { SOCKET_URL } from '@/config';

// 소켓 페이로드 타입은 @dookmark/extension-bridge가 단일 출처 (서버 게이트웨이와 공유)
export type { BookmarkNativeActionPayload, CrossBrowserMovePayload };
Expand Down
4 changes: 3 additions & 1 deletion apps/extension/entrypoints/background/auth-messaging.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { ExtensionMessage } from '@dookmark/extension-bridge';
import { browser } from 'wxt/browser';
import { WEB_BASE_URL } from '@/config';
import { clearTokens, saveTokens } from '@/domain/auth/api';

type MessageListener = Parameters<typeof browser.runtime.onMessage.addListener>[0];
type MessageSender = Parameters<MessageListener>[1];

const TRUSTED_WEB_ORIGIN = 'http://localhost:3000';
// 프로덕션 빌드는 dookmark.site, 개발은 localhost:3000 만 신뢰
const TRUSTED_WEB_ORIGIN = WEB_BASE_URL;

// 토큰 동기화/로그아웃 메시지를 처리 (외부/내부 공통)
function handleAuthMessage(
Expand Down
8 changes: 6 additions & 2 deletions apps/extension/entrypoints/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ function sendMessageToBackground(message: { type: string; payload?: unknown }) {
}

export default defineContentScript({
matches: ['http://localhost/*'],
// 웹앱(개발=localhost:3000, 프로덕션=dookmark.site)에만 주입
matches: ['http://localhost/*', 'https://dookmark.site/*'],
main() {
if (window.location.port !== '3000') return;
const { hostname, port } = window.location;
const isDevWeb = hostname === 'localhost' && port === '3000';
const isProdWeb = hostname === 'dookmark.site';
if (!isDevWeb && !isProdWeb) return;

document.documentElement.dataset.dookmarkInstalled = 'true';

Expand Down
3 changes: 1 addition & 2 deletions apps/extension/entrypoints/sidepanel/pages/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { browser } from 'wxt/browser';
import { WEB_BASE_URL } from '@/config';
import { openQuickBookmarkPopup } from '@/domain/bookmarks/quick-bookmark';
import { useLocalDnd } from '@/domain/dnd/use-local-dnd';
import { useSidepanel } from '@/entrypoints/sidepanel/providers/sidepanel-provider';
Expand All @@ -10,8 +11,6 @@ import { SidepanelMain } from '@/entrypoints/sidepanel/ui/shared/sidepanel-main'
import { BookmarkItem } from '../ui/bookmarks/bookmark-item';
import { FavoritesSection } from '../ui/bookmarks/favorites-section';

const WEB_BASE_URL = 'http://localhost:3000';

export function MainPage() {
const nav = useNavigation();
const {
Expand Down
3 changes: 1 addition & 2 deletions apps/extension/entrypoints/welcome/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Bookmark, MousePointerClick, PanelRight, Search } from 'lucide-react';
import { useEffect } from 'react';
import { browser } from 'wxt/browser';

const WEB_BASE_URL = 'http://localhost:3000';
import { WEB_BASE_URL } from '@/config';

const STEPS = [
{
Expand Down
2 changes: 1 addition & 1 deletion apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "dookmark",
"description": "dookmark",
"private": true,
"version": "0.0.0",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "wxt --port 3007",
Expand Down
4 changes: 2 additions & 2 deletions apps/extension/wxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default defineConfig({
modules: ['@wxt-dev/module-react'],
manifest: ({ browser }) => {
return {
host_permissions: ['http://localhost/*'],
host_permissions: ['http://localhost/*', 'https://api.dookmark.site/*'],
permissions: [
'bookmarks',
'tabs',
Expand All @@ -18,7 +18,7 @@ export default defineConfig({
],
omnibox: { keyword: 'dk' },
externally_connectable: {
matches: ['http://localhost/*'],
matches: ['http://localhost/*', 'https://dookmark.site/*'],
},
...(browser === 'firefox' && {
browser_action: {
Expand Down
5 changes: 4 additions & 1 deletion apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ export default function LandingPage() {
</main>

{/* Landing Footer */}
<footer className='border-t border-outline/10 py-8 text-center text-xs text-on-surface-variant z-10 relative'>
<footer className='border-t border-outline/10 py-8 flex flex-col items-center gap-2 text-xs text-on-surface-variant z-10 relative'>
<a href='/privacy' className='hover:text-on-surface transition-colors'>
개인정보처리방침
</a>
<p>© 2026 Dookmark. All rights reserved.</p>
</footer>
</div>
Expand Down
143 changes: 143 additions & 0 deletions apps/web/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: '개인정보처리방침 · Dookmark',
description: 'Dookmark 개인정보처리방침',
};

const LAST_UPDATED = '2026년 7월 4일';
const CONTACT_EMAIL = 'hoygihoon@gmail.com';

export default function PrivacyPolicyPage() {
return (
<div className='min-h-screen w-full bg-background text-on-surface'>
<main className='max-w-3xl w-full mx-auto px-6 py-16'>
<a
href='/'
className='text-sm text-on-surface-variant hover:text-on-surface transition-colors'
>
← Dookmark 홈으로
</a>

<h1 className='mt-6 text-3xl font-extrabold tracking-tight'>개인정보처리방침</h1>
<p className='mt-2 text-sm text-on-surface-variant'>최종 업데이트: {LAST_UPDATED}</p>

<div className='mt-10 flex flex-col gap-10 text-[15px] leading-relaxed text-on-surface-variant'>
<section className='flex flex-col gap-3'>
<p>
Dookmark(이하 &ldquo;서비스&rdquo;)는 여러 브라우저에 흩어진 북마크를 한곳에서
관리하고 실시간으로 동기화하는 서비스입니다. 본 방침은 서비스가 이용자의 개인정보를
어떻게 수집·이용·보관·보호하는지 설명합니다.
</p>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>1. 수집하는 정보</h2>
<p>서비스는 아래 정보를 수집합니다.</p>
<ul className='flex flex-col gap-2 pl-5 list-disc'>
<li>
<strong className='text-on-surface'>계정 정보</strong> — Google 로그인 시
Google로부터 제공받는 이메일 주소, 이름, 프로필 이미지. 이용자 식별 및 인증에
사용됩니다.
</li>
<li>
<strong className='text-on-surface'>북마크 데이터</strong> — 이용자가
저장·동기화하는 북마크의 제목, URL, 폴더 구조, 정렬 순서, 즐겨찾기 여부.
</li>
<li>
<strong className='text-on-surface'>인증 토큰</strong> — 로그인 세션 유지를 위한
액세스/리프레시 토큰. 이용자의 브라우저 및 확장 프로그램 저장소에 보관됩니다.
</li>
</ul>
<p>
서비스는 이용자의 브라우징 기록(방문한 웹페이지 내용)이나 북마크 외의 브라우저 활동을
수집하지 않습니다.
</p>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>2. 정보의 이용 목적</h2>
<ul className='flex flex-col gap-2 pl-5 list-disc'>
<li>이용자 인증 및 계정 관리</li>
<li>웹 대시보드와 브라우저 확장 프로그램 간 북마크 실시간 동기화</li>
<li>여러 브라우저에 걸친 북마크 통합 관리 기능 제공</li>
<li>서비스 안정성 유지 및 오류 대응</li>
</ul>
<p>
수집한 정보는 위 목적에만 이용하며, 이용자에게 광고를 노출하거나 개인정보를
판매·대여하지 않습니다.
</p>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>3. 제3자 제공 및 위탁</h2>
<p>서비스는 아래의 경우를 제외하고 개인정보를 제3자에게 제공하지 않습니다.</p>
<ul className='flex flex-col gap-2 pl-5 list-disc'>
<li>
<strong className='text-on-surface'>Google OAuth</strong> — 로그인 인증을 위해
Google 계정 인증을 이용합니다. Google의 개인정보 처리에는 Google 개인정보처리방침이
적용됩니다.
</li>
<li>
<strong className='text-on-surface'>클라우드 인프라(AWS)</strong> — 서비스 데이터는
Amazon Web Services 클라우드 인프라에 저장·처리됩니다.
</li>
<li>법령에 의해 요구되거나 이용자의 별도 동의가 있는 경우</li>
</ul>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>4. 보관 및 파기</h2>
<p>
개인정보는 이용자가 계정을 유지하는 동안 보관되며, 이용자가 계정 삭제를 요청하면 관련
개인정보 및 북마크 데이터는 지체 없이 파기됩니다. 로그아웃 시 브라우저 및 확장
프로그램에 저장된 인증 토큰은 삭제됩니다.
</p>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>5. 이용자의 권리</h2>
<p>
이용자는 언제든지 본인의 개인정보 열람, 정정, 삭제 및 처리 정지를 요청할 수 있습니다.
요청은 아래 연락처를 통해 접수할 수 있으며, 서비스는 관련 법령에 따라 지체 없이
조치합니다.
</p>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>6. 보안</h2>
<p>
서비스는 전송 구간 암호화(HTTPS/TLS)를 적용하고, 인증 토큰과 데이터베이스 접근을
보호하기 위한 기술적 조치를 취합니다. 다만 인터넷을 통한 어떠한 전송 방법도 완전한
보안을 보장할 수는 없습니다.
</p>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>7. 방침의 변경</h2>
<p>
본 방침은 법령이나 서비스 변경에 따라 개정될 수 있으며, 개정 시 본 페이지를 통해
공지합니다.
</p>
</section>

<section className='flex flex-col gap-3'>
<h2 className='text-lg font-bold text-on-surface'>8. 문의처</h2>
<p>
개인정보 처리에 관한 문의는 아래로 연락해 주시기 바랍니다.
<br />
이메일:{' '}
<a href={`mailto:${CONTACT_EMAIL}`} className='text-primary hover:underline'>
{CONTACT_EMAIL}
</a>
</p>
</section>
</div>

<footer className='mt-16 pt-8 border-t border-outline/10 text-center text-xs text-on-surface-variant'>
© 2026 Dookmark. All rights reserved.
</footer>
</main>
</div>
);
}
Loading