Skip to content

Commit

Permalink
refactor: reuse localstorage logic for session-storage (unjs#530)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 authored Dec 19, 2024
1 parent 4aab88b commit ace0701
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 117 deletions.
40 changes: 7 additions & 33 deletions docs/2.drivers/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ icon: ph:browser-thin

# Browser

> Browser based storages.
> Store data in `localStorage`, `sessionStorage` or `IndexedDB`
## Local Storage

Store data in localStorage.
## LocalStorage / SessionStorage

### Usage

::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage"}
Learn more about localStorage.
::
Store data in [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) or [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage.)

```js
import { createStorage } from "unstorage";
Expand All @@ -27,32 +23,10 @@ const storage = createStorage({

**Options:**

- `base`: Add `${base}:` to all keys to avoid collision
- `localStorage`: Optionally provide `localStorage` object
- `window`: Optionally provide `window` object

## Session Storage

> Store data in sessionStorage.
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage"}
Learn more about sessionStorage.
::

```js
import { createStorage } from "unstorage";
import sessionStorageDriver from "unstorage/drivers/session-storage";

const storage = createStorage({
driver: sessionStorageDriver({ base: "app:" }),
});
```

**Options:**

- `base`: Add `${base}:` to all keys to avoid collision
- `sessionStorage`: Optionally provide `sessionStorage` object
- `window`: Optionally provide `window` object
- `base`: Add base to all keys to avoid collision
- `storage`: (optional) provide `localStorage` or `sessionStorage` compatible object.
- `windowKey`: (optional) Can be `"localStorage"` (default) or `"sessionStorage"`
- `window`: (optional) provide `window` object

## IndexedDB

Expand Down
37 changes: 21 additions & 16 deletions src/drivers/localstorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ import { createRequiredError, defineDriver, normalizeKey } from "./utils";
export interface LocalStorageOptions {
base?: string;
window?: typeof window;
windowKey?: "localStorage" | "sessionStorage";
storage?: typeof window.localStorage | typeof window.sessionStorage;
/** @deprecated use `storage` option */
sessionStorage?: typeof window.sessionStorage;
/** @deprecated use `storage` option */
localStorage?: typeof window.localStorage;
}

const DRIVER_NAME = "localstorage";

export default defineDriver((opts: LocalStorageOptions = {}) => {
if (!opts.window) {
opts.window = typeof window === "undefined" ? undefined : window;
}
if (!opts.localStorage) {
opts.localStorage = opts.window?.localStorage;
}
if (!opts.localStorage) {
const storage: typeof window.localStorage | typeof window.sessionStorage =
opts.storage ||
opts.localStorage ||
opts.sessionStorage ||
(opts.window || globalThis.window)?.[opts.windowKey || "localStorage"];

if (!storage) {
throw createRequiredError(DRIVER_NAME, "localStorage");
}

Expand All @@ -33,21 +38,21 @@ export default defineDriver((opts: LocalStorageOptions = {}) => {
return {
name: DRIVER_NAME,
options: opts,
getInstance: () => opts.localStorage!,
getInstance: () => storage!,
hasItem(key) {
return Object.prototype.hasOwnProperty.call(opts.localStorage!, r(key));
return Object.prototype.hasOwnProperty.call(storage!, r(key));
},
getItem(key) {
return opts.localStorage!.getItem(r(key));
return storage!.getItem(r(key));
},
setItem(key, value) {
return opts.localStorage!.setItem(r(key), value);
return storage!.setItem(r(key), value);
},
removeItem(key) {
return opts.localStorage!.removeItem(r(key));
return storage!.removeItem(r(key));
},
getKeys() {
const allKeys = Object.keys(opts.localStorage!);
const allKeys = Object.keys(storage!);
return base
? allKeys
.filter((key) => key.startsWith(`${base}:`))
Expand All @@ -57,13 +62,13 @@ export default defineDriver((opts: LocalStorageOptions = {}) => {
clear(prefix) {
const _base = [base, prefix].filter(Boolean).join(":");
if (_base) {
for (const key of Object.keys(opts.localStorage!)) {
for (const key of Object.keys(storage!)) {
if (key.startsWith(`${_base}:`)) {
opts.localStorage?.removeItem(key);
storage?.removeItem(key);
}
}
} else {
opts.localStorage!.clear();
storage!.clear();
}
},
dispose() {
Expand Down
75 changes: 7 additions & 68 deletions src/drivers/session-storage.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,16 @@
import { createRequiredError, defineDriver } from "./utils";
import { defineDriver } from "./utils";
import localstorage, { type LocalStorageOptions } from "./localstorage";

export interface SessionStorageOptions {
base?: string;
window?: typeof window;
sessionStorage?: typeof window.sessionStorage;
}
export interface SessionStorageOptions extends LocalStorageOptions {}

const DRIVER_NAME = "session-storage";

export default defineDriver((opts: SessionStorageOptions = {}) => {
if (!opts.window) {
opts.window = typeof window === "undefined" ? undefined : window;
}
if (!opts.sessionStorage) {
opts.sessionStorage = opts.window?.sessionStorage;
}
if (!opts.sessionStorage) {
throw createRequiredError(DRIVER_NAME, "sessionStorage");
}

const r = (key: string) => (opts.base ? opts.base + ":" : "") + key;

let _storageListener: undefined | ((ev: StorageEvent) => void);
const _unwatch = () => {
if (_storageListener) {
opts.window!.removeEventListener("storage", _storageListener);
}
_storageListener = undefined;
};

return {
name: DRIVER_NAME,
options: opts,
getInstance: () => opts.sessionStorage!,
hasItem(key) {
return Object.prototype.hasOwnProperty.call(opts.sessionStorage, r(key));
},
getItem(key) {
return opts.sessionStorage!.getItem(r(key));
},
setItem(key, value) {
return opts.sessionStorage!.setItem(r(key), value);
},
removeItem(key) {
return opts.sessionStorage!.removeItem(r(key));
},
getKeys() {
return Object.keys(opts.sessionStorage!);
},
clear() {
if (opts.base) {
for (const key of Object.keys(opts.sessionStorage!)) {
opts.sessionStorage?.removeItem(key);
}
} else {
opts.sessionStorage!.clear();
}
if (opts.window && _storageListener) {
opts.window.removeEventListener("storage", _storageListener);
}
},
watch(callback) {
if (!opts.window) {
return _unwatch;
}
_storageListener = ({ key, newValue }: StorageEvent) => {
if (key) {
callback(newValue ? "update" : "remove", key);
}
};
opts.window!.addEventListener("storage", _storageListener);

return _unwatch;
},
...localstorage({
windowKey: "sessionStorage",
...opts,
}),
};
});

0 comments on commit ace0701

Please sign in to comment.