diff --git a/examples/spec/sessionstorage.js b/examples/spec/sessionstorage.js new file mode 100644 index 0000000..dbd3042 --- /dev/null +++ b/examples/spec/sessionstorage.js @@ -0,0 +1,84 @@ +import { test, summary } from './helpers.js'; + +console.log('sessionStorage Tests\n'); + +test('sessionStorage exists', typeof sessionStorage, 'object'); + +sessionStorage.setItem('key1', 'value1'); +test('setItem/getItem basic', sessionStorage.getItem('key1'), 'value1'); + +sessionStorage.setItem('key1', 'newValue'); +test('setItem overwrites', sessionStorage.getItem('key1'), 'newValue'); + +test('getItem non-existent', sessionStorage.getItem('nonExistent'), null); + +sessionStorage.clear(); +test('length after clear', sessionStorage.length, 0); + +sessionStorage.setItem('a', '1'); +test('length after 1 item', sessionStorage.length, 1); + +sessionStorage.setItem('b', '2'); +test('length after 2 items', sessionStorage.length, 2); + +sessionStorage.setItem('c', '3'); +test('length after 3 items', sessionStorage.length, 3); + +let keys = []; +for (let i = 0; i < sessionStorage.length; i++) { + keys.push(sessionStorage.key(i)); +} +test('key() returns keys', keys.length, 3); +test('key() includes a', keys.includes('a'), true); +test('key() includes b', keys.includes('b'), true); +test('key() includes c', keys.includes('c'), true); + +test('key() out of bounds', sessionStorage.key(100), null); +test('key() negative index', sessionStorage.key(-1), null); + +sessionStorage.removeItem('b'); +test('removeItem decreases length', sessionStorage.length, 2); +test('removeItem removes key', sessionStorage.getItem('b'), null); +test('removeItem keeps others', sessionStorage.getItem('a'), '1'); + +sessionStorage.removeItem('nonExistent'); +test('removeItem non-existent key', sessionStorage.length, 2); + +sessionStorage.clear(); +test('clear removes all', sessionStorage.length, 0); +test('clear removes a', sessionStorage.getItem('a'), null); +test('clear removes c', sessionStorage.getItem('c'), null); + +sessionStorage.setItem('number', '42'); +test('store number string', sessionStorage.getItem('number'), '42'); + +sessionStorage.setItem('bool', 'true'); +test('store bool string', sessionStorage.getItem('bool'), 'true'); + +sessionStorage.setItem('empty', ''); +test('store empty string', sessionStorage.getItem('empty'), ''); + +sessionStorage.setItem('key-with-dash', 'dash'); +test('key with dash', sessionStorage.getItem('key-with-dash'), 'dash'); + +sessionStorage.setItem('key_with_underscore', 'underscore'); +test('key with underscore', sessionStorage.getItem('key_with_underscore'), 'underscore'); + +sessionStorage.setItem('key.with.dot', 'dot'); +test('key with dot', sessionStorage.getItem('key.with.dot'), 'dot'); + +sessionStorage.setItem('special', 'hello\nworld'); +test('value with newline', sessionStorage.getItem('special'), 'hello\nworld'); + +sessionStorage.setItem('unicode', 'Hello'); +test('value with unicode', sessionStorage.getItem('unicode'), 'Hello'); + +const obj = { name: 'John', age: 30 }; +sessionStorage.setItem('user', JSON.stringify(obj)); +const retrieved = JSON.parse(sessionStorage.getItem('user')); +test('JSON storage name', retrieved.name, 'John'); +test('JSON storage age', retrieved.age, 30); + +sessionStorage.clear(); + +summary(); diff --git a/include/modules/sessionstorage.h b/include/modules/sessionstorage.h new file mode 100644 index 0000000..0f55fab --- /dev/null +++ b/include/modules/sessionstorage.h @@ -0,0 +1,6 @@ +#ifndef SESSIONSTORAGE_H +#define SESSIONSTORAGE_H + +void init_sessionstorage_module(void); + +#endif diff --git a/meson.build b/meson.build index 3c78ace..11cac7c 100644 --- a/meson.build +++ b/meson.build @@ -75,7 +75,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.2.0.9') +version_conf.set('ANT_VERSION', '0.2.0.10') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/main.c b/src/main.c index 41779bc..96397ab 100644 --- a/src/main.c +++ b/src/main.c @@ -36,6 +36,7 @@ #include "modules/reflect.h" #include "modules/symbol.h" #include "modules/textcodec.h" +#include "modules/sessionstorage.h" int js_result = EXIT_SUCCESS; @@ -206,6 +207,7 @@ int main(int argc, char *argv[]) { init_url_module(); init_reflect_module(); init_textcodec_module(); + init_sessionstorage_module(); ant_register_library(shell_library, "ant:shell", NULL); ant_register_library(ffi_library, "ant:ffi", NULL); diff --git a/src/modules/sessionstorage.c b/src/modules/sessionstorage.c new file mode 100644 index 0000000..6426629 --- /dev/null +++ b/src/modules/sessionstorage.c @@ -0,0 +1,195 @@ +#include +#include +#include + +#include "runtime.h" +#include "modules/sessionstorage.h" +#include "modules/symbol.h" + +typedef struct storage_entry { + char *key; + char *value; + UT_hash_handle hh; +} storage_entry_t; + +static storage_entry_t *session_storage = NULL; + +static void storage_clear(void) { + storage_entry_t *entry, *tmp; + HASH_ITER(hh, session_storage, entry, tmp) { + HASH_DEL(session_storage, entry); + free(entry->key); + free(entry->value); + free(entry); + } +} + +static void storage_set_item(const char *key, size_t key_len, const char *value, size_t value_len) { + storage_entry_t *entry = NULL; + + HASH_FIND(hh, session_storage, key, key_len, entry); + + if (entry) { + free(entry->value); + entry->value = malloc(value_len + 1); + if (entry->value) { + memcpy(entry->value, value, value_len); + entry->value[value_len] = '\0'; + } + } else { + entry = malloc(sizeof(storage_entry_t)); + if (!entry) return; + + entry->key = malloc(key_len + 1); + entry->value = malloc(value_len + 1); + + if (!entry->key || !entry->value) { + free(entry->key); + free(entry->value); + free(entry); + return; + } + + memcpy(entry->key, key, key_len); + entry->key[key_len] = '\0'; + memcpy(entry->value, value, value_len); + entry->value[value_len] = '\0'; + + HASH_ADD_KEYPTR(hh, session_storage, entry->key, key_len, entry); + } +} + +static char *storage_get_item(const char *key, size_t key_len) { + storage_entry_t *entry = NULL; + HASH_FIND(hh, session_storage, key, key_len, entry); + return entry ? entry->value : NULL; +} + +static void storage_remove_item(const char *key, size_t key_len) { + storage_entry_t *entry = NULL; + HASH_FIND(hh, session_storage, key, key_len, entry); + + if (entry) { + HASH_DEL(session_storage, entry); + free(entry->key); + free(entry->value); + free(entry); + } +} + +static size_t storage_length(void) { + return HASH_COUNT(session_storage); +} + +static char *storage_key(size_t index) { + storage_entry_t *entry; + size_t i = 0; + + for (entry = session_storage; entry != NULL; entry = entry->hh.next) { + if (i == index) return entry->key; + i++; + } + + return NULL; +} + +// sessionStorage.setItem(key, value) +static jsval_t js_sessionstorage_setItem(struct js *js, jsval_t *args, int nargs) { + if (nargs < 2) { + return js_mkerr(js, "Failed to execute 'setItem' on 'Storage': 2 arguments required"); + } + + size_t key_len, value_len; + char *key = js_getstr(js, args[0], &key_len); + char *value = js_getstr(js, args[1], &value_len); + + storage_set_item(key, key_len, value, value_len); + + return js_mkundef(); +} + +// sessionStorage.getItem(key) +static jsval_t js_sessionstorage_getItem(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) { + return js_mkerr(js, "Failed to execute 'getItem' on 'Storage': 1 argument required"); + } + + size_t key_len; + char *key = js_getstr(js, args[0], &key_len); + char *value = storage_get_item(key, key_len); + + if (!value) return js_mknull(); + + return js_mkstr(js, value, strlen(value)); +} + +// sessionStorage.removeItem(key) +static jsval_t js_sessionstorage_removeItem(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) { + return js_mkerr(js, "Failed to execute 'removeItem' on 'Storage': 1 argument required"); + } + + size_t key_len; + char *key = js_getstr(js, args[0], &key_len); + storage_remove_item(key, key_len); + + return js_mkundef(); +} + +// sessionStorage.clear() +static jsval_t js_sessionstorage_clear(struct js *js, jsval_t *args, int nargs) { + (void)js; (void)args; (void)nargs; + storage_clear(); + return js_mkundef(); +} + +// sessionStorage.key(index) +static jsval_t js_sessionstorage_key(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) { + return js_mkerr(js, "Failed to execute 'key' on 'Storage': 1 argument required"); + } + + if (js_type(args[0]) != JS_NUM) { + return js_mknull(); + } + + double idx = js_getnum(args[0]); + if (idx < 0) return js_mknull(); + + size_t index = (size_t)idx; + char *key = storage_key(index); + + if (!key) return js_mknull(); + + return js_mkstr(js, key, strlen(key)); +} + +// sessionStorage.length +static jsval_t js_sessionstorage_length(struct js *js, jsval_t *args, int nargs) { + (void)js; (void)args; (void)nargs; + return js_mknum((double)storage_length()); +} + +void init_sessionstorage_module(void) { + struct js *js = rt->js; + jsval_t glob = js_glob(js); + + jsval_t storage_obj = js_mkobj(js); + + js_set(js, storage_obj, "setItem", js_mkfun(js_sessionstorage_setItem)); + js_set(js, storage_obj, "getItem", js_mkfun(js_sessionstorage_getItem)); + js_set(js, storage_obj, "removeItem", js_mkfun(js_sessionstorage_removeItem)); + js_set(js, storage_obj, "clear", js_mkfun(js_sessionstorage_clear)); + js_set(js, storage_obj, "key", js_mkfun(js_sessionstorage_key)); + + jsval_t length_getter = js_mkfun(js_sessionstorage_length); + jsval_t desc = js_mkobj(js); + js_set(js, desc, "get", length_getter); + js_set(js, desc, "enumerable", js_mktrue()); + js_set(js, desc, "configurable", js_mkfalse()); + js_set(js, storage_obj, "__desc_length", desc); + js_set(js, storage_obj, "__getter", js_mktrue()); + + js_set(js, storage_obj, get_toStringTag_sym_key(), js_mkstr(js, "Storage", 7)); + js_set(js, glob, "sessionStorage", storage_obj); +}