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