@kood/claude-code 0.6.6 → 0.7.0
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/dist/index.js +7 -1
- package/package.json +1 -1
- package/templates/.claude/agents/analyst.md +5 -0
- package/templates/.claude/agents/architect.md +5 -0
- package/templates/.claude/agents/build-fixer.md +1 -0
- package/templates/.claude/agents/code-reviewer.md +1 -0
- package/templates/.claude/agents/critic.md +4 -0
- package/templates/.claude/agents/deep-executor.md +1 -0
- package/templates/.claude/agents/dependency-manager.md +2 -0
- package/templates/.claude/agents/deployment-validator.md +2 -0
- package/templates/.claude/agents/designer.md +2 -0
- package/templates/.claude/agents/document-writer.md +3 -0
- package/templates/.claude/agents/explore.md +1 -0
- package/templates/.claude/agents/git-operator.md +2 -0
- package/templates/.claude/agents/implementation-executor.md +2 -0
- package/templates/.claude/agents/ko-to-en-translator.md +3 -0
- package/templates/.claude/agents/lint-fixer.md +2 -0
- package/templates/.claude/agents/planner.md +3 -0
- package/templates/.claude/agents/pm.md +349 -0
- package/templates/.claude/agents/qa-tester.md +1 -0
- package/templates/.claude/agents/refactor-advisor.md +4 -0
- package/templates/.claude/agents/researcher.md +9 -1
- package/templates/.claude/agents/scientist.md +1 -0
- package/templates/.claude/agents/security-reviewer.md +1 -0
- package/templates/.claude/agents/tdd-guide.md +1 -0
- package/templates/.claude/agents/vision.md +1 -0
- package/templates/.claude/instructions/agent-patterns/agent-teams-usage.md +376 -0
- package/templates/.claude/instructions/sourcing/reliable-search.md +49 -2
- package/templates/.claude/scripts/agent-teams/check-availability.sh +238 -0
- package/templates/.claude/scripts/agent-teams/setup-tmux.sh +125 -0
- package/templates/.claude/skills/agent-teams-setup/SKILL.md +460 -0
- package/templates/.claude/skills/brainstorm/SKILL.md +1 -0
- package/templates/.claude/skills/bug-fix/SKILL.md +1 -0
- package/templates/.claude/skills/crawler/SKILL.md +2 -0
- package/templates/.claude/skills/docs-creator/SKILL.md +1 -0
- package/templates/.claude/skills/docs-fetch/SKILL.md +6 -4
- package/templates/.claude/skills/docs-refactor/SKILL.md +1 -0
- package/templates/.claude/skills/elon-musk/SKILL.md +1 -0
- package/templates/.claude/skills/execute/SKILL.md +1 -0
- package/templates/.claude/skills/feedback/SKILL.md +1 -0
- package/templates/.claude/skills/figma-to-code/SKILL.md +1 -0
- package/templates/.claude/skills/genius-thinking/SKILL.md +1 -0
- package/templates/.claude/skills/global-uiux-design/SKILL.md +1 -0
- package/templates/.claude/skills/korea-uiux-design/SKILL.md +1 -0
- package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +1 -0
- package/templates/.claude/skills/plan/SKILL.md +1 -0
- package/templates/.claude/skills/prd/SKILL.md +1 -0
- package/templates/.claude/skills/project-optimizer/AGENTS.md +275 -0
- package/templates/.claude/skills/project-optimizer/SKILL.md +375 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-config-centralize.md +66 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-hot-path.md +35 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-interface-segregation.md +51 -0
- package/templates/.claude/skills/project-optimizer/rules/arch-module-boundary.md +42 -0
- package/templates/.claude/skills/project-optimizer/rules/build-cache.md +57 -0
- package/templates/.claude/skills/project-optimizer/rules/build-code-split.md +56 -0
- package/templates/.claude/skills/project-optimizer/rules/build-incremental.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/build-minify.md +61 -0
- package/templates/.claude/skills/project-optimizer/rules/build-tree-shake.md +60 -0
- package/templates/.claude/skills/project-optimizer/rules/code-complexity.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/code-dead-elimination.md +32 -0
- package/templates/.claude/skills/project-optimizer/rules/code-duplication.md +54 -0
- package/templates/.claude/skills/project-optimizer/rules/code-error-handling.md +75 -0
- package/templates/.claude/skills/project-optimizer/rules/code-naming.md +52 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-defer-await.md +54 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-parallel.md +90 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-pipeline.md +68 -0
- package/templates/.claude/skills/project-optimizer/rules/concurrency-pool.md +68 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-lightweight-alt.md +37 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-peer-align.md +44 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-security-audit.md +45 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-unused-removal.md +25 -0
- package/templates/.claude/skills/project-optimizer/rules/deps-version-pin.md +40 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-ci-speed.md +47 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-dev-server.md +35 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-lint-config.md +36 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-test-coverage.md +34 -0
- package/templates/.claude/skills/project-optimizer/rules/dx-type-safety.md +49 -0
- package/templates/.claude/skills/project-optimizer/rules/io-batch-queries.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-cache-layer.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-connection-reuse.md +67 -0
- package/templates/.claude/skills/project-optimizer/rules/io-serialize-minimal.md +61 -0
- package/templates/.claude/skills/project-optimizer/rules/io-stream.md +75 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-bounded-cache.md +65 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-large-data.md +64 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-lazy-init.md +78 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-leak-prevention.md +79 -0
- package/templates/.claude/skills/project-optimizer/rules/memory-pool-reuse.md +70 -0
- package/templates/.claude/skills/ralph/SKILL.md +1 -0
- package/templates/.claude/skills/refactor/SKILL.md +1 -0
- package/templates/.claude/skills/research/SKILL.md +1 -0
- package/templates/.claude/skills/sql-optimizer/SKILL.md +438 -0
- package/templates/.claude/skills/sql-optimizer/orm-patterns.md +218 -0
- package/templates/.claude/skills/startup-validator/SKILL.md +1 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/AGENTS.md +53 -14
- package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +94 -27
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-defer-third-party.md +42 -19
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-optimistic-updates.md +109 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-suspense-query.md +74 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-use-hook.md +81 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-react-compiler.md +81 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-beforeload-auth.md +121 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-file-conventions.md +104 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-link-navigation.md +119 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-nested-layouts.md +155 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-path-params.md +89 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-pending-component.md +110 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-preload-strategy.md +91 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-router-context.md +120 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-search-params.md +114 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-deferred-data.md +1 -1
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-error-boundaries.md +79 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-middleware.md +85 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md +56 -21
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-streaming.md +84 -0
- package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-validator.md +71 -0
- package/templates/.claude/skills/tauri-react-best-practices/AGENTS.md +527 -0
- package/templates/.claude/skills/tauri-react-best-practices/SKILL.md +571 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-barrel-imports.md +140 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-cargo-profile.md +96 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-frontend-treeshake.md +242 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-lazy-components.md +255 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-remove-unused-commands.md +160 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-ci-pipeline.md +269 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-signing.md +207 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-updater.md +226 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-async-commands.md +172 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-batch-commands.md +133 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-binary-response.md +198 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-channel-streaming.md +186 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-error-handling.md +250 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-type-safe.md +227 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-derived-state.md +231 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-functional-setstate.md +191 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-index-maps.md +276 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/perf-lazy-state-init.md +196 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-lifecycle.md +265 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-mobile-compat.md +199 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-permission-scope.md +193 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-error-boundary.md +239 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-event-listener.md +151 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-file-src.md +155 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-invoke-hook.md +139 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/react-optimistic-update.md +211 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-capability-split.md +205 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-csp.md +207 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-least-privilege.md +106 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-no-wildcard.md +253 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/security-scope-paths.md +160 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-async-mutex.md +270 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-mutex-pattern.md +265 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-react-sync.md +375 -0
- package/templates/.claude/skills/tauri-react-best-practices/rules/state-single-container.md +275 -0
- package/templates/tanstack-start/docs/architecture.md +238 -167
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +777 -38
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +549 -37
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +895 -111
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +641 -43
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +889 -38
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +891 -29
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +972 -36
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +1525 -881
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +1099 -20
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +796 -30
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +953 -35
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +371 -15
- package/templates/tauri/CLAUDE.md +189 -0
- package/templates/tauri/docs/guides/distribution.md +261 -0
- package/templates/tauri/docs/guides/getting-started.md +302 -0
- package/templates/tauri/docs/guides/mobile.md +288 -0
- package/templates/tauri/docs/library/tauri/index.md +510 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# setup/on_event/on_drop 라이프사이클 관리
|
|
2
|
+
|
|
3
|
+
## 왜 중요한가
|
|
4
|
+
|
|
5
|
+
Tauri 플러그인의 리소스(DB 연결, 파일 핸들, 소켓 등)를 제대로 초기화하고 정리하지 않으면 메모리 누수, 파일 잠금, 좀비 프로세스가 발생합니다. 플러그인 라이프사이클을 명확히 관리해야 앱 안정성을 보장할 수 있습니다.
|
|
6
|
+
|
|
7
|
+
## ❌ 잘못된 패턴
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
|
|
11
|
+
|
|
12
|
+
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
13
|
+
Builder::new("my-plugin")
|
|
14
|
+
.invoke_handler(tauri::generate_handler![my_command])
|
|
15
|
+
.build()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[tauri::command]
|
|
19
|
+
fn my_command(db: State<Database>) -> Result<Vec<Item>, String> {
|
|
20
|
+
// ❌ Database 초기화 없음
|
|
21
|
+
// ❌ 앱 종료 시 DB 연결 정리 없음
|
|
22
|
+
db.query_items().map_err(|e| e.to_string())
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**문제점:**
|
|
27
|
+
- 리소스 초기화 로직 없음
|
|
28
|
+
- 앱 종료 시 cleanup 없음 (DB 연결 유지, 파일 잠금)
|
|
29
|
+
- 상태 관리 누락
|
|
30
|
+
|
|
31
|
+
## ✅ 올바른 패턴
|
|
32
|
+
|
|
33
|
+
```rust
|
|
34
|
+
use tauri::{
|
|
35
|
+
plugin::{Builder, TauriPlugin},
|
|
36
|
+
AppHandle, Manager, Runtime, State
|
|
37
|
+
};
|
|
38
|
+
use std::sync::Mutex;
|
|
39
|
+
|
|
40
|
+
pub struct Database {
|
|
41
|
+
conn: Mutex<rusqlite::Connection>,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl Database {
|
|
45
|
+
fn new(path: &str) -> Result<Self, rusqlite::Error> {
|
|
46
|
+
let conn = rusqlite::Connection::open(path)?;
|
|
47
|
+
Ok(Database {
|
|
48
|
+
conn: Mutex::new(conn),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
54
|
+
Builder::new("my-plugin")
|
|
55
|
+
.invoke_handler(tauri::generate_handler![my_command])
|
|
56
|
+
.setup(|app_handle, _api| {
|
|
57
|
+
// ✅ 플러그인 초기화 시점
|
|
58
|
+
let app_data = app_handle.path().app_data_dir().unwrap();
|
|
59
|
+
let db_path = app_data.join("data.db");
|
|
60
|
+
let db = Database::new(db_path.to_str().unwrap())
|
|
61
|
+
.expect("Failed to init database");
|
|
62
|
+
|
|
63
|
+
app_handle.manage(db);
|
|
64
|
+
println!("Plugin initialized");
|
|
65
|
+
|
|
66
|
+
Ok(())
|
|
67
|
+
})
|
|
68
|
+
.on_event(|app_handle, event| {
|
|
69
|
+
// ✅ 앱 이벤트 핸들러
|
|
70
|
+
match event {
|
|
71
|
+
tauri::RunEvent::Exit => {
|
|
72
|
+
println!("App exiting, cleaning up...");
|
|
73
|
+
}
|
|
74
|
+
tauri::RunEvent::ExitRequested { .. } => {
|
|
75
|
+
println!("Exit requested");
|
|
76
|
+
}
|
|
77
|
+
_ => {}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.build()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[tauri::command]
|
|
84
|
+
fn my_command(db: State<Database>) -> Result<Vec<Item>, String> {
|
|
85
|
+
// ✅ 초기화된 Database 사용
|
|
86
|
+
let conn = db.conn.lock().unwrap();
|
|
87
|
+
conn.query_items().map_err(|e| e.to_string())
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**파일 핸들 정리 예시:**
|
|
92
|
+
|
|
93
|
+
```rust
|
|
94
|
+
use std::fs::File;
|
|
95
|
+
use std::sync::Mutex;
|
|
96
|
+
|
|
97
|
+
pub struct FileCache {
|
|
98
|
+
handles: Mutex<HashMap<String, File>>,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl FileCache {
|
|
102
|
+
fn new() -> Self {
|
|
103
|
+
FileCache {
|
|
104
|
+
handles: Mutex::new(HashMap::new()),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn cleanup(&self) {
|
|
109
|
+
let mut handles = self.handles.lock().unwrap();
|
|
110
|
+
for (path, _file) in handles.drain() {
|
|
111
|
+
println!("Closing file: {}", path);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
impl Drop for FileCache {
|
|
117
|
+
fn drop(&mut self) {
|
|
118
|
+
// ✅ 자동 cleanup
|
|
119
|
+
self.cleanup();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
124
|
+
Builder::new("file-cache")
|
|
125
|
+
.setup(|app_handle, _api| {
|
|
126
|
+
app_handle.manage(FileCache::new());
|
|
127
|
+
Ok(())
|
|
128
|
+
})
|
|
129
|
+
.on_event(|app_handle, event| {
|
|
130
|
+
if let tauri::RunEvent::Exit = event {
|
|
131
|
+
if let Some(cache) = app_handle.try_state::<FileCache>() {
|
|
132
|
+
cache.cleanup();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
.build()
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**비동기 리소스 정리 예시:**
|
|
141
|
+
|
|
142
|
+
```rust
|
|
143
|
+
use tokio::sync::RwLock;
|
|
144
|
+
use reqwest::Client;
|
|
145
|
+
|
|
146
|
+
pub struct ApiClient {
|
|
147
|
+
client: Client,
|
|
148
|
+
pending_requests: RwLock<Vec<tokio::task::JoinHandle<()>>>,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl ApiClient {
|
|
152
|
+
fn new() -> Self {
|
|
153
|
+
ApiClient {
|
|
154
|
+
client: Client::new(),
|
|
155
|
+
pending_requests: RwLock::new(Vec::new()),
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async fn shutdown(&self) {
|
|
160
|
+
let mut pending = self.pending_requests.write().await;
|
|
161
|
+
for handle in pending.drain(..) {
|
|
162
|
+
handle.abort();
|
|
163
|
+
}
|
|
164
|
+
println!("All pending requests aborted");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
169
|
+
Builder::new("api-client")
|
|
170
|
+
.setup(|app_handle, _api| {
|
|
171
|
+
app_handle.manage(ApiClient::new());
|
|
172
|
+
Ok(())
|
|
173
|
+
})
|
|
174
|
+
.on_event(|app_handle, event| {
|
|
175
|
+
if let tauri::RunEvent::Exit = event {
|
|
176
|
+
if let Some(client) = app_handle.try_state::<ApiClient>() {
|
|
177
|
+
tauri::async_runtime::block_on(client.shutdown());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
.build()
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**커스텀 플러그인 구조 템플릿:**
|
|
186
|
+
|
|
187
|
+
```rust
|
|
188
|
+
use tauri::{plugin::{Builder, TauriPlugin}, Runtime, AppHandle, Manager};
|
|
189
|
+
|
|
190
|
+
pub struct MyPluginState {
|
|
191
|
+
// 플러그인 상태
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
impl MyPluginState {
|
|
195
|
+
fn new() -> Self {
|
|
196
|
+
// 초기화 로직
|
|
197
|
+
Self {}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn cleanup(&self) {
|
|
201
|
+
// 정리 로직
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
impl Drop for MyPluginState {
|
|
206
|
+
fn drop(&mut self) {
|
|
207
|
+
self.cleanup();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
212
|
+
Builder::new("my-plugin")
|
|
213
|
+
.invoke_handler(tauri::generate_handler![
|
|
214
|
+
my_command_1,
|
|
215
|
+
my_command_2
|
|
216
|
+
])
|
|
217
|
+
.setup(|app_handle, _api| {
|
|
218
|
+
// 플러그인 초기화
|
|
219
|
+
let state = MyPluginState::new();
|
|
220
|
+
app_handle.manage(state);
|
|
221
|
+
Ok(())
|
|
222
|
+
})
|
|
223
|
+
.on_event(|app_handle, event| {
|
|
224
|
+
// 이벤트 핸들러
|
|
225
|
+
match event {
|
|
226
|
+
tauri::RunEvent::Exit => {
|
|
227
|
+
if let Some(state) = app_handle.try_state::<MyPluginState>() {
|
|
228
|
+
state.cleanup();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
_ => {}
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
.build()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#[tauri::command]
|
|
238
|
+
fn my_command_1(state: State<MyPluginState>) -> Result<String, String> {
|
|
239
|
+
// 커맨드 로직
|
|
240
|
+
Ok("Success".to_string())
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## 추가 컨텍스트
|
|
245
|
+
|
|
246
|
+
**라이프사이클 단계:**
|
|
247
|
+
1. `setup`: 플러그인 초기화 (앱 시작 시 한 번)
|
|
248
|
+
2. `invoke_handler`: 커맨드 실행 (프론트엔드 호출 시)
|
|
249
|
+
3. `on_event`: 앱 이벤트 처리 (Exit, ExitRequested 등)
|
|
250
|
+
4. `Drop`: 자동 정리 (플러그인 상태가 drop될 때)
|
|
251
|
+
|
|
252
|
+
**RunEvent 종류:**
|
|
253
|
+
- `Exit`: 앱 종료 직전
|
|
254
|
+
- `ExitRequested`: 앱 종료 요청 시 (취소 가능)
|
|
255
|
+
- `WindowEvent`: 윈도우 이벤트
|
|
256
|
+
- `Ready`: 앱 준비 완료
|
|
257
|
+
|
|
258
|
+
**Tauri State 관리:**
|
|
259
|
+
- `app_handle.manage()`: 전역 상태 등록
|
|
260
|
+
- `State<T>`: 커맨드에서 상태 접근
|
|
261
|
+
- `app_handle.try_state::<T>()`: 상태 가져오기 (Option)
|
|
262
|
+
|
|
263
|
+
**참고:** [Tauri Plugin Development](https://beta.tauri.app/develop/plugins/)
|
|
264
|
+
|
|
265
|
+
영향도: HIGH - 메모리 누수, 리소스 잠금, 앱 안정성
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# 모바일 호환성 확인
|
|
2
|
+
|
|
3
|
+
## 왜 중요한가
|
|
4
|
+
|
|
5
|
+
Tauri v2는 iOS/Android를 지원하지만, 일부 플러그인과 API는 데스크톱 전용입니다. 모바일에서 미지원 API를 호출하면 런타임 에러나 크래시가 발생합니다. 플랫폼별 조건부 코드를 작성해야 크로스 플랫폼 앱을 안정적으로 배포할 수 있습니다.
|
|
6
|
+
|
|
7
|
+
## ❌ 잘못된 패턴
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { invoke } from '@tauri-apps/api/core'
|
|
11
|
+
import { open } from '@tauri-apps/plugin-dialog'
|
|
12
|
+
|
|
13
|
+
function FilePicker() {
|
|
14
|
+
const selectFile = async () => {
|
|
15
|
+
// ❌ 모바일에서 dialog 플러그인 미지원 (크래시)
|
|
16
|
+
const file = await open({
|
|
17
|
+
multiple: false,
|
|
18
|
+
directory: false
|
|
19
|
+
})
|
|
20
|
+
console.log(file)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return <button onClick={selectFile}>Select File</button>
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**문제점:**
|
|
28
|
+
- 플랫폼 구분 없이 데스크톱 전용 API 사용
|
|
29
|
+
- 모바일에서 런타임 에러 발생
|
|
30
|
+
- 플랫폼별 대체 로직 없음
|
|
31
|
+
|
|
32
|
+
## ✅ 올바른 패턴
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { invoke } from '@tauri-apps/api/core'
|
|
36
|
+
import { open } from '@tauri-apps/plugin-dialog'
|
|
37
|
+
import { platform } from '@tauri-apps/plugin-os'
|
|
38
|
+
|
|
39
|
+
function FilePicker() {
|
|
40
|
+
const selectFile = async () => {
|
|
41
|
+
const os = await platform()
|
|
42
|
+
|
|
43
|
+
if (os === 'android' || os === 'ios') {
|
|
44
|
+
// ✅ 모바일: 네이티브 파일 피커 사용
|
|
45
|
+
const result = await invoke<string>('mobile_pick_file')
|
|
46
|
+
console.log(result)
|
|
47
|
+
} else {
|
|
48
|
+
// ✅ 데스크톱: dialog 플러그인 사용
|
|
49
|
+
const file = await open({
|
|
50
|
+
multiple: false,
|
|
51
|
+
directory: false
|
|
52
|
+
})
|
|
53
|
+
console.log(file)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return <button onClick={selectFile}>Select File</button>
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Rust 측 플랫폼 조건부 컴파일:**
|
|
62
|
+
|
|
63
|
+
```rust
|
|
64
|
+
#[tauri::command]
|
|
65
|
+
fn get_system_info() -> String {
|
|
66
|
+
#[cfg(target_os = "android")]
|
|
67
|
+
{
|
|
68
|
+
"Android".to_string()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[cfg(target_os = "ios")]
|
|
72
|
+
{
|
|
73
|
+
"iOS".to_string()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
77
|
+
{
|
|
78
|
+
"Desktop".to_string()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**모바일 전용 플러그인 사용:**
|
|
84
|
+
|
|
85
|
+
```rust
|
|
86
|
+
// Cargo.toml
|
|
87
|
+
[dependencies]
|
|
88
|
+
tauri-plugin-barcode-scanner = "2.0.0-beta" // 모바일 전용
|
|
89
|
+
|
|
90
|
+
// src/lib.rs
|
|
91
|
+
#[cfg(mobile)]
|
|
92
|
+
use tauri_plugin_barcode_scanner::BarcodeScanner;
|
|
93
|
+
|
|
94
|
+
fn main() {
|
|
95
|
+
tauri::Builder::default()
|
|
96
|
+
.plugin(
|
|
97
|
+
#[cfg(mobile)]
|
|
98
|
+
tauri_plugin_barcode_scanner::init()
|
|
99
|
+
)
|
|
100
|
+
.run(tauri::generate_context!())
|
|
101
|
+
.expect("error while running tauri application");
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**React Native 스타일 플랫폼 분기:**
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { platform } from '@tauri-apps/plugin-os'
|
|
109
|
+
import { useEffect, useState } from 'react'
|
|
110
|
+
|
|
111
|
+
function usePlatform() {
|
|
112
|
+
const [os, setOs] = useState<string>('unknown')
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
platform().then(setOs)
|
|
116
|
+
}, [])
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
os,
|
|
120
|
+
isDesktop: ['windows', 'macos', 'linux'].includes(os),
|
|
121
|
+
isMobile: ['android', 'ios'].includes(os),
|
|
122
|
+
isAndroid: os === 'android',
|
|
123
|
+
isIOS: os === 'ios'
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function App() {
|
|
128
|
+
const { isDesktop, isMobile } = usePlatform()
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div>
|
|
132
|
+
{isDesktop && <DesktopMenu />}
|
|
133
|
+
{isMobile && <MobileTabBar />}
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**파일 경로 처리:**
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { appDataDir, join } from '@tauri-apps/api/path'
|
|
143
|
+
import { platform } from '@tauri-apps/plugin-os'
|
|
144
|
+
|
|
145
|
+
async function getConfigPath() {
|
|
146
|
+
const os = await platform()
|
|
147
|
+
const dataDir = await appDataDir()
|
|
148
|
+
|
|
149
|
+
if (os === 'android') {
|
|
150
|
+
// Android: /data/data/com.example.app/files
|
|
151
|
+
return await join(dataDir, 'config.json')
|
|
152
|
+
} else if (os === 'ios') {
|
|
153
|
+
// iOS: /var/mobile/Containers/Data/Application/.../Documents
|
|
154
|
+
return await join(dataDir, 'config.json')
|
|
155
|
+
} else {
|
|
156
|
+
// Desktop: ~/.local/share/com.example.app
|
|
157
|
+
return await join(dataDir, 'config.json')
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**모바일 미지원 플러그인 목록:**
|
|
163
|
+
|
|
164
|
+
| 플러그인 | 데스크톱 | 모바일 | 대체 방법 |
|
|
165
|
+
|---------|---------|--------|----------|
|
|
166
|
+
| dialog | ✅ | ❌ | 네이티브 UI 사용 |
|
|
167
|
+
| global-shortcut | ✅ | ❌ | 모바일은 제스처 사용 |
|
|
168
|
+
| updater | ✅ | ❌ | Play Store/App Store 업데이트 |
|
|
169
|
+
| window (일부) | ✅ | ⚠️ | 일부 API만 지원 |
|
|
170
|
+
| shell (open) | ✅ | ✅ | URL 열기만 지원 |
|
|
171
|
+
| fs | ✅ | ✅ | 전체 지원 |
|
|
172
|
+
| http | ✅ | ✅ | 전체 지원 |
|
|
173
|
+
|
|
174
|
+
## 추가 컨텍스트
|
|
175
|
+
|
|
176
|
+
**모바일 개발 시 체크리스트:**
|
|
177
|
+
1. `platform()` API로 플랫폼 구분
|
|
178
|
+
2. 데스크톱 전용 플러그인 조건부 사용
|
|
179
|
+
3. 파일 경로 플랫폼별 처리
|
|
180
|
+
4. UI/UX 모바일 최적화 (터치, 작은 화면)
|
|
181
|
+
5. 권한 요청 (카메라, 위치 등)
|
|
182
|
+
|
|
183
|
+
**Rust cfg 속성:**
|
|
184
|
+
```rust
|
|
185
|
+
#[cfg(target_os = "android")] // Android 전용
|
|
186
|
+
#[cfg(target_os = "ios")] // iOS 전용
|
|
187
|
+
#[cfg(mobile)] // Android + iOS
|
|
188
|
+
#[cfg(desktop)] // Windows + macOS + Linux
|
|
189
|
+
#[cfg(not(mobile))] // 데스크톱만
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**권장 사항:**
|
|
193
|
+
- 플랫폼별 빌드 후 실제 기기에서 테스트
|
|
194
|
+
- 에뮬레이터/시뮬레이터에서 미지원 API 확인
|
|
195
|
+
- `try/catch`로 런타임 에러 처리
|
|
196
|
+
|
|
197
|
+
**참고:** [Tauri Mobile Guide](https://beta.tauri.app/develop/mobile/)
|
|
198
|
+
|
|
199
|
+
영향도: HIGH - 모바일 앱 안정성, 크로스 플랫폼 호환성
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# 플러그인별 최소 권한 + Scope
|
|
2
|
+
|
|
3
|
+
## 왜 중요한가
|
|
4
|
+
|
|
5
|
+
Tauri 플러그인은 기본적으로 모든 권한을 허용하지 않습니다. 필요한 커맨드만 명시하고 scope를 제한해야 보안 취약점을 방지할 수 있습니다. 과도한 권한은 악의적인 코드나 XSS 공격 시 시스템 접근을 허용할 수 있습니다.
|
|
6
|
+
|
|
7
|
+
## ❌ 잘못된 패턴
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
// tauri.conf.json
|
|
11
|
+
{
|
|
12
|
+
"plugins": {
|
|
13
|
+
"fs": {
|
|
14
|
+
"all": true, // ❌ 모든 파일 시스템 접근 허용
|
|
15
|
+
"scope": ["**"] // ❌ 전체 디렉토리 접근 가능
|
|
16
|
+
},
|
|
17
|
+
"shell": {
|
|
18
|
+
"all": true // ❌ 모든 셸 커맨드 실행 허용
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**문제점:**
|
|
25
|
+
- 불필요한 권한 부여 (최소 권한 원칙 위반)
|
|
26
|
+
- 악의적인 코드가 시스템 전체 접근 가능
|
|
27
|
+
- XSS 공격 시 파일 삭제, 임의 명령 실행 가능
|
|
28
|
+
|
|
29
|
+
## ✅ 올바른 패턴
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
// tauri.conf.json
|
|
33
|
+
{
|
|
34
|
+
"plugins": {
|
|
35
|
+
"fs": {
|
|
36
|
+
"scope": [
|
|
37
|
+
"$APPDATA/user-data/**", // ✅ 앱 데이터 폴더만
|
|
38
|
+
"$RESOURCE/assets/**" // ✅ 앱 리소스 폴더만
|
|
39
|
+
],
|
|
40
|
+
"commands": {
|
|
41
|
+
"readFile": true,
|
|
42
|
+
"writeFile": true,
|
|
43
|
+
"readDir": true,
|
|
44
|
+
"exists": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"shell": {
|
|
48
|
+
"scope": [
|
|
49
|
+
{
|
|
50
|
+
"name": "ffmpeg",
|
|
51
|
+
"cmd": "ffmpeg",
|
|
52
|
+
"args": [
|
|
53
|
+
"-i", { "validator": "\\.(mp4|avi|mov)$" },
|
|
54
|
+
"-o", { "validator": "^[^/\\\\]+$" }
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"http": {
|
|
60
|
+
"scope": [
|
|
61
|
+
"https://api.example.com/*" // ✅ 특정 도메인만
|
|
62
|
+
],
|
|
63
|
+
"commands": {
|
|
64
|
+
"fetch": true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**파일 시스템 플러그인 권한 예시:**
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"plugins": {
|
|
76
|
+
"fs": {
|
|
77
|
+
"scope": [
|
|
78
|
+
// 읽기 전용 리소스
|
|
79
|
+
{
|
|
80
|
+
"path": "$RESOURCE/**",
|
|
81
|
+
"permissions": ["read"]
|
|
82
|
+
},
|
|
83
|
+
// 읽기/쓰기 가능한 사용자 데이터
|
|
84
|
+
{
|
|
85
|
+
"path": "$APPDATA/config.json",
|
|
86
|
+
"permissions": ["read", "write"]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"path": "$APPDATA/cache/**",
|
|
90
|
+
"permissions": ["read", "write", "delete"]
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"commands": {
|
|
94
|
+
"readFile": true,
|
|
95
|
+
"writeFile": true,
|
|
96
|
+
"readDir": true,
|
|
97
|
+
"exists": true
|
|
98
|
+
// removeFile, renameFile 등 불필요한 커맨드 비활성화
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**HTTP 플러그인 권한 예시:**
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"plugins": {
|
|
110
|
+
"http": {
|
|
111
|
+
"scope": [
|
|
112
|
+
"https://api.example.com/*",
|
|
113
|
+
"https://cdn.example.com/assets/*"
|
|
114
|
+
],
|
|
115
|
+
"commands": {
|
|
116
|
+
"fetch": true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Shell 플러그인 권한 예시:**
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"plugins": {
|
|
128
|
+
"shell": {
|
|
129
|
+
"scope": [
|
|
130
|
+
{
|
|
131
|
+
"name": "video-convert",
|
|
132
|
+
"cmd": "ffmpeg",
|
|
133
|
+
"args": [
|
|
134
|
+
"-i", { "validator": "^[a-zA-Z0-9_-]+\\.(mp4|avi)$" },
|
|
135
|
+
"-codec", "copy",
|
|
136
|
+
{ "validator": "^output\\.mp4$" }
|
|
137
|
+
],
|
|
138
|
+
"sidecar": false
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**런타임 권한 요청 (Tauri v2.1+):**
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { invoke } from '@tauri-apps/api/core'
|
|
150
|
+
import { requestPermission } from '@tauri-apps/plugin-fs'
|
|
151
|
+
|
|
152
|
+
async function writeUserConfig(data: Config) {
|
|
153
|
+
const granted = await requestPermission({
|
|
154
|
+
path: '$APPDATA/config.json',
|
|
155
|
+
permissions: ['write']
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (!granted) {
|
|
159
|
+
throw new Error('Permission denied')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await invoke('write_config', { data })
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 추가 컨텍스트
|
|
167
|
+
|
|
168
|
+
**Scope 패턴:**
|
|
169
|
+
- `$APPDATA`: 사용자 앱 데이터 디렉토리
|
|
170
|
+
- `$RESOURCE`: 앱 리소스 디렉토리 (번들된 파일)
|
|
171
|
+
- `$HOME`: 사용자 홈 디렉토리 (가급적 피하기)
|
|
172
|
+
- `**`: 하위 디렉토리 포함 (주의해서 사용)
|
|
173
|
+
|
|
174
|
+
**공식 플러그인 권한 체크리스트:**
|
|
175
|
+
|
|
176
|
+
| 플러그인 | 최소 권한 예시 |
|
|
177
|
+
|---------|---------------|
|
|
178
|
+
| fs | 특정 디렉토리만 + read/write 구분 |
|
|
179
|
+
| http | 특정 도메인만 허용 |
|
|
180
|
+
| shell | 화이트리스트 커맨드 + args 검증 |
|
|
181
|
+
| notification | 기본 권한 (OS 수준 허용 필요) |
|
|
182
|
+
| clipboard | 기본 권한 |
|
|
183
|
+
| dialog | 기본 권한 |
|
|
184
|
+
|
|
185
|
+
**보안 원칙:**
|
|
186
|
+
1. 최소 권한 원칙: 필요한 권한만 부여
|
|
187
|
+
2. Scope 제한: 전체 파일 시스템 접근 금지
|
|
188
|
+
3. 화이트리스트: 허용 목록 기반 권한 관리
|
|
189
|
+
4. Validator: args에 정규식 검증 추가
|
|
190
|
+
|
|
191
|
+
**참고:** [Tauri Security Best Practices](https://beta.tauri.app/security/best-practices/)
|
|
192
|
+
|
|
193
|
+
영향도: CRITICAL - 보안, 권한 관리, XSS 공격 방어
|