1use std::path::PathBuf;
6
7use deno_core::op2;
8use deno_core::GarbageCollected;
9use deno_core::OpState;
10pub use rusqlite;
11use rusqlite::params;
12use rusqlite::Connection;
13use rusqlite::OptionalExtension;
14
15#[derive(Debug, thiserror::Error, deno_error::JsError)]
16pub enum WebStorageError {
17 #[class("DOMExceptionNotSupportedError")]
18 #[error("LocalStorage is not supported in this context.")]
19 ContextNotSupported,
20 #[class(generic)]
21 #[error(transparent)]
22 Sqlite(#[from] rusqlite::Error),
23 #[class(inherit)]
24 #[error(transparent)]
25 Io(std::io::Error),
26 #[class("DOMExceptionQuotaExceededError")]
27 #[error("Exceeded maximum storage size")]
28 StorageExceeded,
29}
30
31#[derive(Clone)]
32struct OriginStorageDir(PathBuf);
33
34const MAX_STORAGE_BYTES: usize = 10 * 1024 * 1024;
35
36deno_core::extension!(deno_webstorage,
37 deps = [ deno_webidl ],
38 ops = [
39 op_webstorage_iterate_keys,
40 ],
41 objects = [
42 Storage
43 ],
44 esm = [ "01_webstorage.js" ],
45 options = {
46 origin_storage_dir: Option<PathBuf>
47 },
48 state = |state, options| {
49 if let Some(origin_storage_dir) = options.origin_storage_dir {
50 state.put(OriginStorageDir(origin_storage_dir));
51 }
52 },
53);
54
55struct LocalStorage(Connection);
56struct SessionStorage(Connection);
57
58fn get_webstorage(
59 state: &mut OpState,
60 persistent: bool,
61) -> Result<&Connection, WebStorageError> {
62 let conn = if persistent {
63 if state.try_borrow::<LocalStorage>().is_none() {
64 let path = state
65 .try_borrow::<OriginStorageDir>()
66 .ok_or(WebStorageError::ContextNotSupported)?;
67 std::fs::create_dir_all(&path.0).map_err(WebStorageError::Io)?;
68 let conn = Connection::open(path.0.join("local_storage"))?;
69 let initial_pragmas = "
71 -- enable write-ahead-logging mode
72 PRAGMA journal_mode=WAL;
73 PRAGMA synchronous=NORMAL;
74 PRAGMA temp_store=memory;
75 PRAGMA page_size=4096;
76 PRAGMA mmap_size=6000000;
77 PRAGMA optimize;
78 ";
79
80 conn.execute_batch(initial_pragmas)?;
81 conn.set_prepared_statement_cache_capacity(128);
82 {
83 let mut stmt = conn.prepare_cached(
84 "CREATE TABLE IF NOT EXISTS data (key VARCHAR UNIQUE, value VARCHAR)",
85 )?;
86 stmt.execute(params![])?;
87 }
88 state.put(LocalStorage(conn));
89 }
90
91 &state.borrow::<LocalStorage>().0
92 } else {
93 if state.try_borrow::<SessionStorage>().is_none() {
94 let conn = Connection::open_in_memory()?;
95 {
96 let mut stmt = conn.prepare_cached(
97 "CREATE TABLE data (key VARCHAR UNIQUE, value VARCHAR)",
98 )?;
99 stmt.execute(params![])?;
100 }
101 state.put(SessionStorage(conn));
102 }
103
104 &state.borrow::<SessionStorage>().0
105 };
106
107 Ok(conn)
108}
109
110#[inline]
111fn size_check(input: usize) -> Result<(), WebStorageError> {
112 if input >= MAX_STORAGE_BYTES {
113 return Err(WebStorageError::StorageExceeded);
114 }
115
116 Ok(())
117}
118
119struct Storage {
120 persistent: bool,
121}
122
123impl GarbageCollected for Storage {}
124
125#[op2]
126impl Storage {
127 #[constructor]
128 #[cppgc]
129 fn new(persistent: bool) -> Storage {
130 Storage { persistent }
131 }
132
133 #[getter]
134 #[smi]
135 fn length(&self, state: &mut OpState) -> Result<u32, WebStorageError> {
136 let conn = get_webstorage(state, self.persistent)?;
137
138 let mut stmt = conn.prepare_cached("SELECT COUNT(*) FROM data")?;
139 let length: u32 = stmt.query_row(params![], |row| row.get(0))?;
140
141 Ok(length)
142 }
143
144 #[required(1)]
145 #[string]
146 fn key(
147 &self,
148 state: &mut OpState,
149 #[smi] index: u32,
150 ) -> Result<Option<String>, WebStorageError> {
151 let conn = get_webstorage(state, self.persistent)?;
152
153 let mut stmt =
154 conn.prepare_cached("SELECT key FROM data LIMIT 1 OFFSET ?")?;
155
156 let key: Option<String> = stmt
157 .query_row(params![index], |row| row.get(0))
158 .optional()?;
159
160 Ok(key)
161 }
162
163 #[fast]
164 #[required(2)]
165 fn set_item(
166 &self,
167 state: &mut OpState,
168 #[string] key: &str,
169 #[string] value: &str,
170 ) -> Result<(), WebStorageError> {
171 let conn = get_webstorage(state, self.persistent)?;
172
173 size_check(key.len() + value.len())?;
174
175 let mut stmt = conn
176 .prepare_cached("SELECT SUM(pgsize) FROM dbstat WHERE name = 'data'")?;
177 let size: u32 = stmt.query_row(params![], |row| row.get(0))?;
178
179 size_check(size as usize)?;
180
181 let mut stmt = conn.prepare_cached(
182 "INSERT OR REPLACE INTO data (key, value) VALUES (?, ?)",
183 )?;
184 stmt.execute(params![key, value])?;
185
186 Ok(())
187 }
188
189 #[required(1)]
190 #[string]
191 fn get_item(
192 &self,
193 state: &mut OpState,
194 #[string] key: &str,
195 ) -> Result<Option<String>, WebStorageError> {
196 let conn = get_webstorage(state, self.persistent)?;
197
198 let mut stmt =
199 conn.prepare_cached("SELECT value FROM data WHERE key = ?")?;
200 let val = stmt.query_row(params![key], |row| row.get(0)).optional()?;
201
202 Ok(val)
203 }
204
205 #[fast]
206 #[required(1)]
207 fn remove_item(
208 &self,
209 state: &mut OpState,
210 #[string] key: &str,
211 ) -> Result<(), WebStorageError> {
212 let conn = get_webstorage(state, self.persistent)?;
213
214 let mut stmt = conn.prepare_cached("DELETE FROM data WHERE key = ?")?;
215 stmt.execute(params![key])?;
216
217 Ok(())
218 }
219
220 #[fast]
221 fn clear(&self, state: &mut OpState) -> Result<(), WebStorageError> {
222 let conn = get_webstorage(state, self.persistent)?;
223
224 let mut stmt = conn.prepare_cached("DELETE FROM data")?;
225 stmt.execute(params![])?;
226
227 Ok(())
228 }
229}
230
231#[op2]
232#[serde]
233fn op_webstorage_iterate_keys(
234 #[cppgc] storage: &Storage,
235 state: &mut OpState,
236) -> Result<Vec<String>, WebStorageError> {
237 let conn = get_webstorage(state, storage.persistent)?;
238
239 let mut stmt = conn.prepare_cached("SELECT key FROM data")?;
240 let keys = stmt
241 .query_map(params![], |row| row.get::<_, String>(0))?
242 .map(|r| r.unwrap())
243 .collect();
244
245 Ok(keys)
246}