@kood/claude-code 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/tauri/CLAUDE.md +1 -0
- package/templates/tauri/docs/architecture.md +300 -0
- package/templates/tauri/docs/library/tauri/index.md +44 -356
- package/templates/tauri/docs/library/tauri/references/configuration.md +46 -0
- package/templates/tauri/docs/library/tauri/references/ipc-guide.md +131 -0
- package/templates/tauri/docs/library/tauri/references/plugins-guide.md +47 -0
- package/templates/tauri/docs/library/tauri/references/security-guide.md +64 -0
- package/templates/tauri/docs/library/tauri/references/state-guide.md +49 -0
- package/templates/tauri/docs/references/async-execution.md +66 -0
- package/templates/tauri/docs/references/data-flow.md +46 -0
- package/templates/tauri/docs/references/error-handling.md +113 -0
- package/templates/tauri/docs/references/mobile-architecture.md +64 -0
- package/templates/tauri/docs/references/security-model.md +85 -0
- package/templates/tauri/docs/references/state-management.md +117 -0
- package/templates/tauri/docs/references/tech-stack.md +31 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Security Guide
|
|
2
|
+
|
|
3
|
+
> Tauri v2 보안 상세 가이드
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Capabilities
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
// src-tauri/capabilities/default.json
|
|
11
|
+
{
|
|
12
|
+
"identifier": "default",
|
|
13
|
+
"description": "메인 윈도우 기본 권한",
|
|
14
|
+
"windows": ["main"],
|
|
15
|
+
"permissions": [
|
|
16
|
+
"core:default",
|
|
17
|
+
"core:window:allow-set-title",
|
|
18
|
+
"core:window:allow-close",
|
|
19
|
+
"shell:allow-open"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
// 플랫폼별 Capability
|
|
26
|
+
{
|
|
27
|
+
"identifier": "mobile-features",
|
|
28
|
+
"description": "모바일 전용 권한",
|
|
29
|
+
"windows": ["main"],
|
|
30
|
+
"platforms": ["iOS", "android"],
|
|
31
|
+
"permissions": [
|
|
32
|
+
"nfc:default",
|
|
33
|
+
"biometric:default"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Permission 패턴
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
core:default # Core 기본 권한
|
|
44
|
+
core:window:allow-<command> # Window 개별 허용
|
|
45
|
+
core:event:allow-listen # Event 리스닝 허용
|
|
46
|
+
<plugin>:default # Plugin 기본 권한
|
|
47
|
+
<plugin>:allow-<command> # Plugin 개별 허용
|
|
48
|
+
<plugin>:deny-<command> # Plugin 개별 거부
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## CSP 설정
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
// tauri.conf.json > app > security > csp
|
|
57
|
+
{
|
|
58
|
+
"default-src": "'self' customprotocol: asset:",
|
|
59
|
+
"connect-src": "ipc: http://ipc.localhost",
|
|
60
|
+
"font-src": ["https://fonts.gstatic.com"],
|
|
61
|
+
"img-src": "'self' asset: http://asset.localhost blob: data:",
|
|
62
|
+
"style-src": "'unsafe-inline' 'self'"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# State Management Guide
|
|
2
|
+
|
|
3
|
+
> Tauri Rust 상태 관리 상세 가이드
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## State 패턴
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
use std::sync::Mutex;
|
|
11
|
+
use tauri::{Builder, Manager, State};
|
|
12
|
+
|
|
13
|
+
struct AppState {
|
|
14
|
+
counter: u32,
|
|
15
|
+
db_url: String,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[tauri::command]
|
|
19
|
+
async fn increment(state: State<'_, Mutex<AppState>>) -> Result<u32, String> {
|
|
20
|
+
let mut s = state.lock().map_err(|e| e.to_string())?;
|
|
21
|
+
s.counter += 1;
|
|
22
|
+
Ok(s.counter)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn main() {
|
|
26
|
+
Builder::default()
|
|
27
|
+
.setup(|app| {
|
|
28
|
+
// 불변 상태
|
|
29
|
+
app.manage(String::from("config-value"));
|
|
30
|
+
// 가변 상태
|
|
31
|
+
app.manage(Mutex::new(AppState { counter: 0, db_url: String::new() }));
|
|
32
|
+
Ok(())
|
|
33
|
+
})
|
|
34
|
+
.invoke_handler(tauri::generate_handler![increment])
|
|
35
|
+
.run(tauri::generate_context!())
|
|
36
|
+
.unwrap();
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 규칙
|
|
43
|
+
|
|
44
|
+
| 규칙 | 설명 |
|
|
45
|
+
|------|------|
|
|
46
|
+
| **Arc 불필요** | State<T>가 내부 처리 |
|
|
47
|
+
| **가변 상태** | `Mutex<T>` 사용 (std::sync::Mutex 권장) |
|
|
48
|
+
| **await 제한** | await 포인트 넘어 lock 유지 금지 |
|
|
49
|
+
| **타입 별칭** | 잘못된 타입 접근 → 런타임 패닉 |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Async Execution Model
|
|
2
|
+
|
|
3
|
+
> Tauri Command의 비동기 실행 원리
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## async vs non-async Command
|
|
8
|
+
|
|
9
|
+
| 유형 | 실행 위치 | 사용 시점 |
|
|
10
|
+
|------|----------|----------|
|
|
11
|
+
| `async fn` | Tokio async runtime | IO 작업 (DB, HTTP, File) |
|
|
12
|
+
| `fn` (non-async) | 별도 스레드풀 | CPU 작업 (계산, 변환) |
|
|
13
|
+
|
|
14
|
+
```rust
|
|
15
|
+
// ✅ async Command: Tokio runtime에서 실행
|
|
16
|
+
// - await 사용 가능
|
|
17
|
+
// - IO 작업에 적합 (non-blocking)
|
|
18
|
+
#[tauri::command]
|
|
19
|
+
async fn fetch_data(url: String) -> Result<String, String> {
|
|
20
|
+
let response = reqwest::get(&url).await.map_err(|e| e.to_string())?;
|
|
21
|
+
response.text().await.map_err(|e| e.to_string())
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ✅ non-async Command: 스레드풀에서 실행
|
|
25
|
+
// - CPU 집약적 작업에 적합
|
|
26
|
+
// - 메인 스레드 블로킹 방지
|
|
27
|
+
#[tauri::command]
|
|
28
|
+
fn heavy_computation(data: Vec<u8>) -> Vec<u8> {
|
|
29
|
+
// CPU 집약적 작업
|
|
30
|
+
data.iter().map(|x| x.wrapping_mul(2)).collect()
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Tokio Runtime 동작
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
40
|
+
│ Tauri Application │
|
|
41
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
42
|
+
│ │ Tokio Runtime │ │
|
|
43
|
+
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
44
|
+
│ │ │ async Task 1│ │ async Task 2│ │ async Task 3│ │ │
|
|
45
|
+
│ │ │ (fetch_data)│ │ (read_file) │ │ (db_query) │ │ │
|
|
46
|
+
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
47
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
48
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
49
|
+
│ │ Thread Pool (sync commands) │ │
|
|
50
|
+
│ │ ┌─────────────┐ ┌─────────────┐ │ │
|
|
51
|
+
│ │ │ sync Task 1 │ │ sync Task 2 │ │ │
|
|
52
|
+
│ │ │ (compute) │ │ (transform) │ │ │
|
|
53
|
+
│ │ └─────────────┘ └─────────────┘ │ │
|
|
54
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
55
|
+
└─────────────────────────────────────────────────────────────┘
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 주의사항
|
|
61
|
+
|
|
62
|
+
| 패턴 | 설명 |
|
|
63
|
+
|------|------|
|
|
64
|
+
| **async에서 blocking 금지** | `std::thread::sleep()` 대신 `tokio::time::sleep()` 사용 |
|
|
65
|
+
| **sync에서 await 불가** | non-async 함수에서는 `.await` 사용 불가 |
|
|
66
|
+
| **spawn_blocking** | async 내에서 blocking 작업 필요 시 `tokio::task::spawn_blocking` |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Data Flow
|
|
2
|
+
|
|
3
|
+
> Tauri 데이터 흐름 패턴
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Command Flow (읽기)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────┐ invoke ┌─────────┐ validate ┌─────────┐ execute ┌─────────┐
|
|
11
|
+
│ React │───────────▶│ IPC │─────────────▶│Capability│────────────▶│ Command │
|
|
12
|
+
│ Hook │ │ Bridge │ │ Check │ │ Handler │
|
|
13
|
+
└─────────┘ └─────────┘ └─────────┘ └────┬────┘
|
|
14
|
+
▲ │
|
|
15
|
+
│ ▼
|
|
16
|
+
│ ┌─────────┐
|
|
17
|
+
│◀────────────────────────────────────────────────────────────────│ State │
|
|
18
|
+
│ Result<T> │ / DB │
|
|
19
|
+
└─────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Event Flow (실시간 업데이트)
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌─────────┐ ┌─────────┐
|
|
28
|
+
│ Rust │ app.emit("event", data) │ Frontend│
|
|
29
|
+
│ Core │─────────────────────────────▶│ listen()│
|
|
30
|
+
└─────────┘ └─────────┘
|
|
31
|
+
│
|
|
32
|
+
│ Background Task (download, sync, etc.)
|
|
33
|
+
│
|
|
34
|
+
└──▶ Progress Event ──▶ UI Update
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Plugin Data Flow
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Frontend ──▶ invoke("plugin:fs|read") ──▶ Capability Check ──▶ Plugin Handler ──▶ OS API
|
|
43
|
+
│
|
|
44
|
+
◀─────────────────────┘
|
|
45
|
+
Result/Error
|
|
46
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
> Rust Error → JSON → TypeScript Error 전파 메커니즘
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 커스텀 Error 타입 정의
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// src-tauri/src/error.rs
|
|
11
|
+
use serde::Serialize;
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, thiserror::Error)]
|
|
14
|
+
pub enum AppError {
|
|
15
|
+
#[error("Not found: {0}")]
|
|
16
|
+
NotFound(String),
|
|
17
|
+
|
|
18
|
+
#[error("Unauthorized: {0}")]
|
|
19
|
+
Unauthorized(String),
|
|
20
|
+
|
|
21
|
+
#[error("Database error: {0}")]
|
|
22
|
+
Database(String),
|
|
23
|
+
|
|
24
|
+
#[error("Internal error: {0}")]
|
|
25
|
+
Internal(String),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// serde::Serialize 구현 필수 (JSON 직렬화)
|
|
29
|
+
impl Serialize for AppError {
|
|
30
|
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
31
|
+
where
|
|
32
|
+
S: serde::Serializer,
|
|
33
|
+
{
|
|
34
|
+
serializer.serialize_str(self.to_string().as_ref())
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// From 변환 구현 (다른 에러 타입 자동 변환)
|
|
39
|
+
impl From<sqlx::Error> for AppError {
|
|
40
|
+
fn from(e: sqlx::Error) -> Self {
|
|
41
|
+
AppError::Database(e.to_string())
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Command에서 에러 반환
|
|
49
|
+
|
|
50
|
+
```rust
|
|
51
|
+
#[tauri::command]
|
|
52
|
+
async fn get_user(id: u32, db: State<'_, DbPool>) -> Result<User, AppError> {
|
|
53
|
+
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
|
|
54
|
+
.fetch_optional(&**db)
|
|
55
|
+
.await? // sqlx::Error → AppError 자동 변환
|
|
56
|
+
.ok_or_else(|| AppError::NotFound(format!("User {} not found", id)))?;
|
|
57
|
+
|
|
58
|
+
Ok(user)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Frontend 에러 처리
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { invoke } from '@tauri-apps/api/core';
|
|
68
|
+
|
|
69
|
+
interface User {
|
|
70
|
+
id: number;
|
|
71
|
+
name: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function getUser(id: number): Promise<User | null> {
|
|
75
|
+
try {
|
|
76
|
+
return await invoke<User>('get_user', { id });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
// error는 Rust에서 직렬화된 문자열
|
|
79
|
+
const errorMessage = error as string;
|
|
80
|
+
|
|
81
|
+
if (errorMessage.includes('Not found')) {
|
|
82
|
+
console.warn('User not found:', id);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (errorMessage.includes('Unauthorized')) {
|
|
87
|
+
// 인증 에러 처리
|
|
88
|
+
window.location.href = '/login';
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 기타 에러
|
|
93
|
+
console.error('Failed to get user:', errorMessage);
|
|
94
|
+
throw new Error(errorMessage);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 에러 전파 흐름
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
┌─────────────┐ Result<T,E> ┌─────────────┐ JSON ┌─────────────┐
|
|
105
|
+
│ Rust │ ─────────────────▶│ IPC │ ──────────▶ │ Frontend │
|
|
106
|
+
│ Command │ Err(AppError) │ Bridge │ serialize │ catch() │
|
|
107
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
108
|
+
│ │
|
|
109
|
+
│ #[error("...")] 메시지 │ error as string
|
|
110
|
+
│ │
|
|
111
|
+
▼ ▼
|
|
112
|
+
"Not found: User 1 not found" "Not found: User 1 not found"
|
|
113
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Mobile Architecture
|
|
2
|
+
|
|
3
|
+
> Tauri v2 모바일 아키텍처 상세 가이드
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 엔트리포인트 구조
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// lib.rs - 공유 로직 (데스크톱 + 모바일)
|
|
11
|
+
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
12
|
+
pub fn run() {
|
|
13
|
+
tauri::Builder::default()
|
|
14
|
+
.invoke_handler(tauri::generate_handler![...])
|
|
15
|
+
.run(tauri::generate_context!())
|
|
16
|
+
.expect("error");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// main.rs - 데스크톱 전용
|
|
20
|
+
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
21
|
+
fn main() {
|
|
22
|
+
my_app_lib::run()
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Mobile Plugin 구조
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
plugin/
|
|
32
|
+
├── src/
|
|
33
|
+
│ ├── lib.rs # 공유 로직
|
|
34
|
+
│ ├── desktop.rs # 데스크톱 구현
|
|
35
|
+
│ └── mobile.rs # 모바일 브릿지 (Kotlin/Swift 호출)
|
|
36
|
+
├── android/
|
|
37
|
+
│ └── src/.../Plugin.kt # Kotlin 구현
|
|
38
|
+
└── ios/
|
|
39
|
+
└── Sources/Plugin.swift # Swift 구현
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 플랫폼 설정
|
|
45
|
+
|
|
46
|
+
| 파일 | 용도 |
|
|
47
|
+
|------|------|
|
|
48
|
+
| `tauri.conf.json` | 공통 설정 |
|
|
49
|
+
| `tauri.android.conf.json` | Android 오버라이드 |
|
|
50
|
+
| `tauri.ios.conf.json` | iOS 오버라이드 |
|
|
51
|
+
| `tauri.windows.conf.json` | Windows 오버라이드 |
|
|
52
|
+
| `tauri.macos.conf.json` | macOS 오버라이드 |
|
|
53
|
+
| `tauri.linux.conf.json` | Linux 오버라이드 |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 모바일 개발 주의사항
|
|
58
|
+
|
|
59
|
+
| 항목 | 설명 |
|
|
60
|
+
|------|------|
|
|
61
|
+
| **iOS 개발** | macOS에서만 가능 |
|
|
62
|
+
| **Android 메인 스레드** | 블로킹 I/O → UI 프리징, Coroutine 사용 필수 |
|
|
63
|
+
| **최소 Android SDK** | 24 (Android 7.0) |
|
|
64
|
+
| **lib.rs 엔트리포인트** | 모바일은 라이브러리로 로드 |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Security Model
|
|
2
|
+
|
|
3
|
+
> Tauri v2 보안 모델 상세 가이드
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Trust Boundaries
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ UNTRUSTED ZONE │
|
|
12
|
+
│ ┌───────────────────────────────────────────────────────┐ │
|
|
13
|
+
│ │ WebView │ │
|
|
14
|
+
│ │ • HTML/CSS/JS │ │
|
|
15
|
+
│ │ • 사용자 입력 │ │
|
|
16
|
+
│ │ • 외부 콘텐츠 (제한적) │ │
|
|
17
|
+
│ └───────────────────────────────────────────────────────┘ │
|
|
18
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
19
|
+
│ IPC (Capabilities 검증)
|
|
20
|
+
▼
|
|
21
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ TRUSTED ZONE │
|
|
23
|
+
│ ┌───────────────────────────────────────────────────────┐ │
|
|
24
|
+
│ │ Rust Core │ │
|
|
25
|
+
│ │ • 시스템 API 접근 │ │
|
|
26
|
+
│ │ • 파일 시스템 │ │
|
|
27
|
+
│ │ • 네트워크 │ │
|
|
28
|
+
│ │ • 민감 데이터 │ │
|
|
29
|
+
│ └───────────────────────────────────────────────────────┘ │
|
|
30
|
+
└─────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Capabilities System
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
// src-tauri/capabilities/default.json
|
|
39
|
+
{
|
|
40
|
+
"identifier": "default",
|
|
41
|
+
"description": "메인 윈도우 기본 권한",
|
|
42
|
+
"windows": ["main"],
|
|
43
|
+
"permissions": [
|
|
44
|
+
"core:default",
|
|
45
|
+
"core:window:allow-set-title",
|
|
46
|
+
"shell:allow-open"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Permission 패턴 | 설명 |
|
|
52
|
+
|----------------|------|
|
|
53
|
+
| `core:default` | Core 기본 권한 |
|
|
54
|
+
| `core:window:allow-<cmd>` | Window 개별 허용 |
|
|
55
|
+
| `<plugin>:default` | Plugin 기본 권한 |
|
|
56
|
+
| `<plugin>:allow-<cmd>` | Plugin 개별 허용 |
|
|
57
|
+
| `<plugin>:deny-<cmd>` | Plugin 개별 거부 |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## CSP (Content Security Policy)
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
// tauri.conf.json > app > security > csp
|
|
65
|
+
{
|
|
66
|
+
"default-src": "'self' customprotocol: asset:",
|
|
67
|
+
"connect-src": "ipc: http://ipc.localhost",
|
|
68
|
+
"img-src": "'self' asset: http://asset.localhost blob: data:",
|
|
69
|
+
"style-src": "'unsafe-inline' 'self'"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### CSP 프로토콜 설명
|
|
74
|
+
|
|
75
|
+
| 프로토콜 | 설명 |
|
|
76
|
+
|---------|------|
|
|
77
|
+
| `'self'` | 같은 origin 리소스만 허용 |
|
|
78
|
+
| `customprotocol:` | Tauri 커스텀 프로토콜 (앱 내부 통신) |
|
|
79
|
+
| `asset:` | 로컬 파일 접근 프로토콜 (Tauri v2) |
|
|
80
|
+
| `ipc:` | Frontend ↔ Rust IPC 통신 프로토콜 |
|
|
81
|
+
| `http://ipc.localhost` | IPC 브릿지 localhost 접근 |
|
|
82
|
+
| `http://asset.localhost` | 로컬 에셋 서빙 |
|
|
83
|
+
| `'unsafe-inline'` | 인라인 스타일 허용 (CSS-in-JS 라이브러리 호환, style-src만) |
|
|
84
|
+
| `blob:` | Blob URL 허용 (동적 이미지 생성) |
|
|
85
|
+
| `data:` | Data URL 허용 (Base64 이미지) |
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# State Management
|
|
2
|
+
|
|
3
|
+
> Tauri Rust 상태 관리 상세 가이드
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Rust State 패턴
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
use std::sync::Mutex;
|
|
11
|
+
use tauri::{Builder, Manager, State};
|
|
12
|
+
|
|
13
|
+
// 상태 정의
|
|
14
|
+
struct AppState {
|
|
15
|
+
counter: u32,
|
|
16
|
+
db_url: String,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Command에서 사용
|
|
20
|
+
#[tauri::command]
|
|
21
|
+
async fn increment(state: State<'_, Mutex<AppState>>) -> Result<u32, String> {
|
|
22
|
+
let mut s = state.lock().map_err(|e| e.to_string())?;
|
|
23
|
+
s.counter += 1;
|
|
24
|
+
Ok(s.counter)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 등록
|
|
28
|
+
fn main() {
|
|
29
|
+
Builder::default()
|
|
30
|
+
.setup(|app| {
|
|
31
|
+
app.manage(Mutex::new(AppState { counter: 0, db_url: String::new() }));
|
|
32
|
+
Ok(())
|
|
33
|
+
})
|
|
34
|
+
.invoke_handler(tauri::generate_handler![increment])
|
|
35
|
+
.run(tauri::generate_context!())
|
|
36
|
+
.unwrap();
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## State 규칙
|
|
43
|
+
|
|
44
|
+
| 규칙 | 설명 |
|
|
45
|
+
|------|------|
|
|
46
|
+
| **Arc 불필요** | State<T>가 내부적으로 Arc 처리 |
|
|
47
|
+
| **가변 상태** | `Mutex<T>` 사용 |
|
|
48
|
+
| **await 제한** | await 포인트 넘어 lock 유지 금지 (데드락) |
|
|
49
|
+
| **타입 별칭** | 잘못된 타입 접근 → 런타임 패닉 |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Mutex 선택 기준
|
|
54
|
+
|
|
55
|
+
| Mutex 종류 | 사용 시점 | 예시 |
|
|
56
|
+
|-----------|----------|------|
|
|
57
|
+
| `std::sync::Mutex` | CPU 작업 (빠른 연산) | counter, config, cache |
|
|
58
|
+
| `tokio::sync::Mutex` | IO 작업 (await 필요) | DB, HTTP, File |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## await 걸쳐 lock 유지 금지
|
|
63
|
+
|
|
64
|
+
```rust
|
|
65
|
+
// ❌ 잘못된 예시: await 걸쳐 lock 유지 → 데드락 위험
|
|
66
|
+
#[tauri::command]
|
|
67
|
+
async fn bad_example(state: State<'_, std::sync::Mutex<AppState>>) -> Result<(), String> {
|
|
68
|
+
let s = state.lock().unwrap(); // lock 획득
|
|
69
|
+
some_async_operation().await; // await 포인트 → 다른 스레드 블로킹
|
|
70
|
+
s.counter += 1; // lock 아직 유지 중
|
|
71
|
+
Ok(())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ✅ 올바른 예시 1: lock 범위 최소화
|
|
75
|
+
#[tauri::command]
|
|
76
|
+
async fn good_example_1(state: State<'_, std::sync::Mutex<AppState>>) -> Result<(), String> {
|
|
77
|
+
let data = {
|
|
78
|
+
let s = state.lock().unwrap();
|
|
79
|
+
s.get_data() // lock 범위 내에서 필요한 작업만
|
|
80
|
+
}; // lock 해제
|
|
81
|
+
some_async_operation(data).await; // await는 lock 밖에서
|
|
82
|
+
Ok(())
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ✅ 올바른 예시 2: IO 작업 시 tokio::sync::Mutex
|
|
86
|
+
#[tauri::command]
|
|
87
|
+
async fn good_example_2(state: State<'_, tokio::sync::Mutex<DbConnection>>) -> Result<(), String> {
|
|
88
|
+
let mut db = state.lock().await; // tokio Mutex는 await 가능
|
|
89
|
+
db.query("SELECT * FROM users").await?;
|
|
90
|
+
Ok(())
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Frontend State (Zustand)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// stores/app.ts
|
|
100
|
+
import { create } from 'zustand';
|
|
101
|
+
import { persist } from 'zustand/middleware';
|
|
102
|
+
|
|
103
|
+
export const useAppStore = create(
|
|
104
|
+
persist(
|
|
105
|
+
(set) => ({
|
|
106
|
+
theme: 'dark' as 'light' | 'dark',
|
|
107
|
+
setTheme: (theme) => set({ theme }),
|
|
108
|
+
}),
|
|
109
|
+
{ name: 'app-store' }
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| 상태 유형 | 위치 | 사용 |
|
|
115
|
+
|----------|------|------|
|
|
116
|
+
| **시스템 상태** | Rust State | DB 연결, 설정, 캐시 |
|
|
117
|
+
| **UI 상태** | Zustand | 테마, 사이드바, 임시 데이터 |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Technology Stack
|
|
2
|
+
|
|
3
|
+
> Tauri v2 기술 스택
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Technologies
|
|
8
|
+
|
|
9
|
+
| Layer | Technology | Version |
|
|
10
|
+
|-------|------------|---------|
|
|
11
|
+
| Framework | Tauri | 2.x |
|
|
12
|
+
| Backend | Rust | stable |
|
|
13
|
+
| Frontend | React | 19.x |
|
|
14
|
+
| Bundler | Vite | 6.x |
|
|
15
|
+
| TypeScript | TS | 5.x |
|
|
16
|
+
| State (Client) | Zustand | latest |
|
|
17
|
+
| IPC API | @tauri-apps/api | 2.x |
|
|
18
|
+
| Window | TAO | - |
|
|
19
|
+
| WebView | WRY | - |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## WebView Engine
|
|
24
|
+
|
|
25
|
+
| 플랫폼 | WebView |
|
|
26
|
+
|--------|---------|
|
|
27
|
+
| Windows | WebView2 (Chromium) |
|
|
28
|
+
| macOS | WKWebView (WebKit) |
|
|
29
|
+
| Linux | webkitgtk (WebKit) |
|
|
30
|
+
| iOS | WKWebView (WebKit) |
|
|
31
|
+
| Android | WebView (Chromium) |
|