yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
wasm_secure_storage.cc
Go to the documentation of this file.
1#ifdef __EMSCRIPTEN__
2
4
5#include <emscripten.h>
6#include <emscripten/val.h>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10
11namespace yaze {
12namespace app {
13namespace platform {
14
15namespace {
16
17// JavaScript interop functions for sessionStorage
18EM_JS(void, js_session_storage_set, (const char* key, const char* value), {
19 try {
20 if (typeof(Storage) !== "undefined" && sessionStorage) {
21 sessionStorage.setItem(UTF8ToString(key), UTF8ToString(value));
22 }
23 } catch (e) {
24 console.error('Failed to set sessionStorage:', e);
25 }
26});
27
28EM_JS(char*, js_session_storage_get, (const char* key), {
29 try {
30 if (typeof(Storage) !== "undefined" && sessionStorage) {
31 const value = sessionStorage.getItem(UTF8ToString(key));
32 if (value === null) return null;
33 const len = lengthBytesUTF8(value) + 1;
34 const ptr = _malloc(len);
35 stringToUTF8(value, ptr, len);
36 return ptr;
37 }
38 } catch (e) {
39 console.error('Failed to get from sessionStorage:', e);
40 }
41 return null;
42});
43
44EM_JS(void, js_session_storage_remove, (const char* key), {
45 try {
46 if (typeof(Storage) !== "undefined" && sessionStorage) {
47 sessionStorage.removeItem(UTF8ToString(key));
48 }
49 } catch (e) {
50 console.error('Failed to remove from sessionStorage:', e);
51 }
52});
53
54EM_JS(int, js_session_storage_has, (const char* key), {
55 try {
56 if (typeof(Storage) !== "undefined" && sessionStorage) {
57 return sessionStorage.getItem(UTF8ToString(key)) !== null ? 1 : 0;
58 }
59 } catch (e) {
60 console.error('Failed to check sessionStorage:', e);
61 }
62 return 0;
63});
64
65// JavaScript interop functions for localStorage
66EM_JS(void, js_local_storage_set, (const char* key, const char* value), {
67 try {
68 if (typeof(Storage) !== "undefined" && localStorage) {
69 localStorage.setItem(UTF8ToString(key), UTF8ToString(value));
70 }
71 } catch (e) {
72 console.error('Failed to set localStorage:', e);
73 }
74});
75
76EM_JS(char*, js_local_storage_get, (const char* key), {
77 try {
78 if (typeof(Storage) !== "undefined" && localStorage) {
79 const value = localStorage.getItem(UTF8ToString(key));
80 if (value === null) return null;
81 const len = lengthBytesUTF8(value) + 1;
82 const ptr = _malloc(len);
83 stringToUTF8(value, ptr, len);
84 return ptr;
85 }
86 } catch (e) {
87 console.error('Failed to get from localStorage:', e);
88 }
89 return null;
90});
91
92EM_JS(void, js_local_storage_remove, (const char* key), {
93 try {
94 if (typeof(Storage) !== "undefined" && localStorage) {
95 localStorage.removeItem(UTF8ToString(key));
96 }
97 } catch (e) {
98 console.error('Failed to remove from localStorage:', e);
99 }
100});
101
102EM_JS(int, js_local_storage_has, (const char* key), {
103 try {
104 if (typeof(Storage) !== "undefined" && localStorage) {
105 return localStorage.getItem(UTF8ToString(key)) !== null ? 1 : 0;
106 }
107 } catch (e) {
108 console.error('Failed to check localStorage:', e);
109 }
110 return 0;
111});
112
113// Get all keys from storage
114EM_JS(char*, js_session_storage_keys, (), {
115 try {
116 if (typeof(Storage) !== "undefined" && sessionStorage) {
117 const keys = [];
118 for (let i = 0; i < sessionStorage.length; i++) {
119 keys.push(sessionStorage.key(i));
120 }
121 const keysStr = keys.join('|');
122 const len = lengthBytesUTF8(keysStr) + 1;
123 const ptr = _malloc(len);
124 stringToUTF8(keysStr, ptr, len);
125 return ptr;
126 }
127 } catch (e) {
128 console.error('Failed to get sessionStorage keys:', e);
129 }
130 return null;
131});
132
133EM_JS(char*, js_local_storage_keys, (), {
134 try {
135 if (typeof(Storage) !== "undefined" && localStorage) {
136 const keys = [];
137 for (let i = 0; i < localStorage.length; i++) {
138 keys.push(localStorage.key(i));
139 }
140 const keysStr = keys.join('|');
141 const len = lengthBytesUTF8(keysStr) + 1;
142 const ptr = _malloc(len);
143 stringToUTF8(keysStr, ptr, len);
144 return ptr;
145 }
146 } catch (e) {
147 console.error('Failed to get localStorage keys:', e);
148 }
149 return null;
150});
151
152// Clear all keys with prefix
153EM_JS(void, js_session_storage_clear_prefix, (const char* prefix), {
154 try {
155 if (typeof(Storage) !== "undefined" && sessionStorage) {
156 const prefixStr = UTF8ToString(prefix);
157 const keysToRemove = [];
158 for (let i = 0; i < sessionStorage.length; i++) {
159 const key = sessionStorage.key(i);
160 if (key && key.startsWith(prefixStr)) {
161 keysToRemove.push(key);
162 }
163 }
164 keysToRemove.forEach(key => sessionStorage.removeItem(key));
165 }
166 } catch (e) {
167 console.error('Failed to clear sessionStorage prefix:', e);
168 }
169});
170
171EM_JS(void, js_local_storage_clear_prefix, (const char* prefix), {
172 try {
173 if (typeof(Storage) !== "undefined" && localStorage) {
174 const prefixStr = UTF8ToString(prefix);
175 const keysToRemove = [];
176 for (let i = 0; i < localStorage.length; i++) {
177 const key = localStorage.key(i);
178 if (key && key.startsWith(prefixStr)) {
179 keysToRemove.push(key);
180 }
181 }
182 keysToRemove.forEach(key => localStorage.removeItem(key));
183 }
184 } catch (e) {
185 console.error('Failed to clear localStorage prefix:', e);
186 }
187});
188
189// Check if storage is available
190EM_JS(int, js_is_storage_available, (), {
191 try {
192 if (typeof(Storage) !== "undefined") {
193 // Test both storage types
194 const testKey = '__yaze_storage_test__';
195
196 // Test sessionStorage
197 if (sessionStorage) {
198 sessionStorage.setItem(testKey, 'test');
199 sessionStorage.removeItem(testKey);
200 }
201
202 // Test localStorage
203 if (localStorage) {
204 localStorage.setItem(testKey, 'test');
205 localStorage.removeItem(testKey);
206 }
207
208 return 1;
209 }
210 } catch (e) {
211 console.error('Storage not available:', e);
212 }
213 return 0;
214});
215
216// Helper to split string by delimiter
217std::vector<std::string> SplitString(const std::string& str, char delimiter) {
218 std::vector<std::string> result;
219 size_t start = 0;
220 size_t end = str.find(delimiter);
221
222 while (end != std::string::npos) {
223 result.push_back(str.substr(start, end - start));
224 start = end + 1;
225 end = str.find(delimiter, start);
226 }
227
228 if (start < str.length()) {
229 result.push_back(str.substr(start));
230 }
231
232 return result;
233}
234
235} // namespace
236
237// Public methods implementation
238
239absl::Status WasmSecureStorage::StoreApiKey(const std::string& service,
240 const std::string& key,
241 StorageType storage_type) {
242 if (service.empty()) {
243 return absl::InvalidArgumentError("Service name cannot be empty");
244 }
245 if (key.empty()) {
246 return absl::InvalidArgumentError("API key cannot be empty");
247 }
248
249 std::string storage_key = BuildApiKeyStorageKey(service);
250
251 if (storage_type == StorageType::kSession) {
252 js_session_storage_set(storage_key.c_str(), key.c_str());
253 } else {
254 js_local_storage_set(storage_key.c_str(), key.c_str());
255 }
256
257 return absl::OkStatus();
258}
259
260absl::StatusOr<std::string> WasmSecureStorage::RetrieveApiKey(
261 const std::string& service,
262 StorageType storage_type) {
263 if (service.empty()) {
264 return absl::InvalidArgumentError("Service name cannot be empty");
265 }
266
267 std::string storage_key = BuildApiKeyStorageKey(service);
268 char* value = nullptr;
269
270 if (storage_type == StorageType::kSession) {
271 value = js_session_storage_get(storage_key.c_str());
272 } else {
273 value = js_local_storage_get(storage_key.c_str());
274 }
275
276 if (value == nullptr) {
277 return absl::NotFoundError(
278 absl::StrFormat("No API key found for service: %s", service));
279 }
280
281 std::string result(value);
282 free(value); // Free the allocated memory from JS
283 return result;
284}
285
286absl::Status WasmSecureStorage::ClearApiKey(const std::string& service,
287 StorageType storage_type) {
288 if (service.empty()) {
289 return absl::InvalidArgumentError("Service name cannot be empty");
290 }
291
292 std::string storage_key = BuildApiKeyStorageKey(service);
293
294 if (storage_type == StorageType::kSession) {
295 js_session_storage_remove(storage_key.c_str());
296 } else {
297 js_local_storage_remove(storage_key.c_str());
298 }
299
300 return absl::OkStatus();
301}
302
303bool WasmSecureStorage::HasApiKey(const std::string& service,
304 StorageType storage_type) {
305 if (service.empty()) {
306 return false;
307 }
308
309 std::string storage_key = BuildApiKeyStorageKey(service);
310
311 if (storage_type == StorageType::kSession) {
312 return js_session_storage_has(storage_key.c_str()) != 0;
313 } else {
314 return js_local_storage_has(storage_key.c_str()) != 0;
315 }
316}
317
318absl::Status WasmSecureStorage::StoreSecret(const std::string& key,
319 const std::string& value,
320 StorageType storage_type) {
321 if (key.empty()) {
322 return absl::InvalidArgumentError("Key cannot be empty");
323 }
324 if (value.empty()) {
325 return absl::InvalidArgumentError("Value cannot be empty");
326 }
327
328 std::string storage_key = BuildSecretStorageKey(key);
329
330 if (storage_type == StorageType::kSession) {
331 js_session_storage_set(storage_key.c_str(), value.c_str());
332 } else {
333 js_local_storage_set(storage_key.c_str(), value.c_str());
334 }
335
336 return absl::OkStatus();
337}
338
339absl::StatusOr<std::string> WasmSecureStorage::RetrieveSecret(
340 const std::string& key,
341 StorageType storage_type) {
342 if (key.empty()) {
343 return absl::InvalidArgumentError("Key cannot be empty");
344 }
345
346 std::string storage_key = BuildSecretStorageKey(key);
347 char* value = nullptr;
348
349 if (storage_type == StorageType::kSession) {
350 value = js_session_storage_get(storage_key.c_str());
351 } else {
352 value = js_local_storage_get(storage_key.c_str());
353 }
354
355 if (value == nullptr) {
356 return absl::NotFoundError(absl::StrFormat("No secret found for key: %s", key));
357 }
358
359 std::string result(value);
360 free(value);
361 return result;
362}
363
364absl::Status WasmSecureStorage::ClearSecret(const std::string& key,
365 StorageType storage_type) {
366 if (key.empty()) {
367 return absl::InvalidArgumentError("Key cannot be empty");
368 }
369
370 std::string storage_key = BuildSecretStorageKey(key);
371
372 if (storage_type == StorageType::kSession) {
373 js_session_storage_remove(storage_key.c_str());
374 } else {
375 js_local_storage_remove(storage_key.c_str());
376 }
377
378 return absl::OkStatus();
379}
380
381std::vector<std::string> WasmSecureStorage::ListStoredApiKeys(
382 StorageType storage_type) {
383 std::vector<std::string> services;
384 char* keys_str = nullptr;
385
386 if (storage_type == StorageType::kSession) {
387 keys_str = js_session_storage_keys();
388 } else {
389 keys_str = js_local_storage_keys();
390 }
391
392 if (keys_str != nullptr) {
393 std::string keys(keys_str);
394 free(keys_str);
395
396 // Split keys by delimiter and filter for API keys
397 auto all_keys = SplitString(keys, '|');
398 std::string api_prefix = kApiKeyPrefix;
399
400 for (const auto& key : all_keys) {
401 if (key.find(api_prefix) == 0) {
402 // Extract service name from key
403 std::string service = ExtractServiceFromKey(key);
404 if (!service.empty()) {
405 services.push_back(service);
406 }
407 }
408 }
409 }
410
411 return services;
412}
413
414absl::Status WasmSecureStorage::ClearAllApiKeys(StorageType storage_type) {
415 if (storage_type == StorageType::kSession) {
416 js_session_storage_clear_prefix(kApiKeyPrefix);
417 } else {
418 js_local_storage_clear_prefix(kApiKeyPrefix);
419 }
420
421 return absl::OkStatus();
422}
423
425 return js_is_storage_available() != 0;
426}
427
428absl::StatusOr<WasmSecureStorage::StorageQuota> WasmSecureStorage::GetStorageQuota(
429 StorageType storage_type) {
430 // Browser storage APIs don't provide direct quota information
431 // We can estimate based on typical limits
432 StorageQuota quota;
433
434 if (storage_type == StorageType::kSession) {
435 // sessionStorage typically has 5-10MB limit
436 quota.available_bytes = 5 * 1024 * 1024; // 5MB estimate
437 } else {
438 // localStorage typically has 5-10MB limit
439 quota.available_bytes = 10 * 1024 * 1024; // 10MB estimate
440 }
441
442 // Estimate used bytes by summing all stored values
443 char* keys_str = nullptr;
444 if (storage_type == StorageType::kSession) {
445 keys_str = js_session_storage_keys();
446 } else {
447 keys_str = js_local_storage_keys();
448 }
449
450 size_t used = 0;
451 if (keys_str != nullptr) {
452 std::string keys(keys_str);
453 free(keys_str);
454
455 auto all_keys = SplitString(keys, '|');
456 for (const auto& key : all_keys) {
457 char* value = nullptr;
458 if (storage_type == StorageType::kSession) {
459 value = js_session_storage_get(key.c_str());
460 } else {
461 value = js_local_storage_get(key.c_str());
462 }
463
464 if (value != nullptr) {
465 used += strlen(value) + key.length();
466 free(value);
467 }
468 }
469 }
470
471 quota.used_bytes = used;
472 return quota;
473}
474
475// Private methods implementation
476
477std::string WasmSecureStorage::BuildApiKeyStorageKey(const std::string& service) {
478 return absl::StrCat(kApiKeyPrefix, service);
479}
480
481std::string WasmSecureStorage::BuildSecretStorageKey(const std::string& key) {
482 return absl::StrCat(kSecretPrefix, key);
483}
484
485std::string WasmSecureStorage::ExtractServiceFromKey(const std::string& storage_key) {
486 std::string prefix = kApiKeyPrefix;
487 if (storage_key.find(prefix) == 0) {
488 return storage_key.substr(prefix.length());
489 }
490 return "";
491}
492
493} // namespace platform
494} // namespace app
495} // namespace yaze
496
497#endif // __EMSCRIPTEN__
static absl::Status ClearApiKey(const std::string &, StorageType=StorageType::kSession)
static absl::Status StoreSecret(const std::string &, const std::string &, StorageType=StorageType::kSession)
static bool HasApiKey(const std::string &, StorageType=StorageType::kSession)
static absl::Status ClearSecret(const std::string &, StorageType=StorageType::kSession)
static absl::Status ClearAllApiKeys(StorageType=StorageType::kSession)
static std::vector< std::string > ListStoredApiKeys(StorageType=StorageType::kSession)
static absl::Status StoreApiKey(const std::string &, const std::string &, StorageType=StorageType::kSession)
static absl::StatusOr< std::string > RetrieveSecret(const std::string &, StorageType=StorageType::kSession)
static absl::StatusOr< std::string > RetrieveApiKey(const std::string &, StorageType=StorageType::kSession)
static absl::StatusOr< StorageQuota > GetStorageQuota(StorageType=StorageType::kSession)
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })