Skip to content

Commit

Permalink
feat(drivers): added session storage driver
Browse files Browse the repository at this point in the history
  • Loading branch information
CorentinTh committed Mar 8, 2023
1 parent e36cee8 commit 813c8f9
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
22 changes: 22 additions & 0 deletions docs/content/6.drivers/session-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
navigation.title: Session Storage
---

# Session Storage

Store data in [sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/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
71 changes: 71 additions & 0 deletions src/drivers/session-storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { defineDriver } from "./utils";

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

export default defineDriver((opts: SessionStorageOptions = {}) => {
if (!opts.window) {
opts.window = typeof window !== "undefined" ? window : undefined;
}
if (!opts.sessionStorage) {
opts.sessionStorage = opts.window?.sessionStorage;
}
if (!opts.sessionStorage) {
throw new Error("sessionStorage not available");
}

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

let _storageListener: (ev: StorageEvent) => void;

return {
name: "session-storage",
options: opts,
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) {
opts.sessionStorage!.clear();
} else {
for (const key of Object.keys(opts.sessionStorage)) {
opts.sessionStorage?.removeItem(key);
}
}
if (opts.window && _storageListener) {
opts.window.removeEventListener("storage", _storageListener);
}
},
watch(callback) {
if (!opts.window) {
return;
}
_storageListener = ({ key, newValue }: StorageEvent) => {
if (key) {
callback(newValue ? "update" : "remove", key);
}
};
opts.window.addEventListener("storage", _storageListener);

return () => {
opts.window.removeEventListener("storage", _storageListener);
_storageListener = undefined;
};
},
};
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const builtinDrivers = {
planetscale: "unstorage/drivers/planetscale",
redis: "unstorage/drivers/redis",
azureKeyVault: "unstorage/drivers/azure-key-vault",
sessionStorage: "unstorage/drivers/session-storage",
};

export type BuiltinDriverName = keyof typeof builtinDrivers;
64 changes: 64 additions & 0 deletions test/drivers/session-storage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { JSDOM } from "jsdom";
import { describe, expect, it, vi } from "vitest";
import driver from "../../src/drivers/session-storage";
import { testDriver } from "./utils";

describe("drivers: session-storage", () => {
const jsdom = new JSDOM("", {
url: "http://localhost",
});
jsdom.virtualConsole.sendTo(console);

testDriver({
driver: driver({ window: jsdom.window as unknown as typeof window }),
additionalTests: (ctx) => {
it("check session storage", () => {
expect(jsdom.window.sessionStorage.getItem("s1:a")).toBe("test_data");
});
it("watch session storage", async () => {
const watcher = vi.fn();
await ctx.storage.watch(watcher);

// Emulate
// jsdom.window.sessionStorage.setItem('s1:random_file', 'random')
const ev = jsdom.window.document.createEvent("CustomEvent");
ev.initEvent("storage", true);
// @ts-ignore
ev.key = "s1:random_file";
// @ts-ignore
ev.newValue = "random";
jsdom.window.dispatchEvent(ev);

expect(watcher).toHaveBeenCalledWith("update", "s1:random_file");
});
it("unwatch session storage", async () => {
const watcher = vi.fn();
const unwatch = await ctx.storage.watch(watcher);

// Emulate
// jsdom.window.sessionStorage.setItem('s1:random_file', 'random')
const ev = jsdom.window.document.createEvent("CustomEvent");
ev.initEvent("storage", true);
// @ts-ignore
ev.key = "s1:random_file";
// @ts-ignore
ev.newValue = "random";
const ev2 = jsdom.window.document.createEvent("CustomEvent");
ev2.initEvent("storage", true);
// @ts-ignore
ev2.key = "s1:random_file2";
// @ts-ignore
ev2.newValue = "random";

jsdom.window.dispatchEvent(ev);

await unwatch();

jsdom.window.dispatchEvent(ev2);

expect(watcher).toHaveBeenCalledWith("update", "s1:random_file");
expect(watcher).toHaveBeenCalledTimes(1);
});
},
});
});

0 comments on commit 813c8f9

Please sign in to comment.