-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from kinde-oss/feat/localstorage
feat: added localStorage store
- Loading branch information
Showing
4 changed files
with
250 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { describe, it, expect, beforeEach, vi } from "vitest"; | ||
import { LocalStorage } from "./localStorage"; | ||
import { StorageKeys } from "../types"; | ||
|
||
enum ExtraKeys { | ||
testKey = "testKey2", | ||
} | ||
|
||
const localStorageMock = (function () { | ||
let store: { [key: string]: string } = {}; | ||
|
||
return { | ||
getItem(key: string) { | ||
return store[key] || null; | ||
}, | ||
setItem(key: string, value: string) { | ||
store[key] = String(value); | ||
}, | ||
removeItem(key: string) { | ||
delete store[key]; | ||
}, | ||
clear() { | ||
store = {}; | ||
}, | ||
}; | ||
})(); | ||
vi.stubGlobal("localStorage", localStorageMock); | ||
|
||
describe("LocalStorage standard keys", () => { | ||
let sessionManager: LocalStorage; | ||
|
||
beforeEach(() => { | ||
sessionManager = new LocalStorage(); | ||
}); | ||
|
||
it("should set and get an item in session storage", async () => { | ||
console.log("here"); | ||
await sessionManager.setSessionItem(StorageKeys.accessToken, "testValue"); | ||
expect(await sessionManager.getSessionItem(StorageKeys.accessToken)).toBe( | ||
"testValue", | ||
); | ||
}); | ||
|
||
it("should remove an item from session storage", async () => { | ||
await sessionManager.setSessionItem(StorageKeys.accessToken, "testValue"); | ||
expect(await sessionManager.getSessionItem(StorageKeys.accessToken)).toBe( | ||
"testValue", | ||
); | ||
|
||
await sessionManager.removeSessionItem(StorageKeys.accessToken); | ||
expect( | ||
await sessionManager.getSessionItem(StorageKeys.accessToken), | ||
).toBeNull(); | ||
}); | ||
|
||
it("should clear all items from session storage", async () => { | ||
await sessionManager.setSessionItem(StorageKeys.accessToken, "testValue"); | ||
expect(await sessionManager.getSessionItem(StorageKeys.accessToken)).toBe( | ||
"testValue", | ||
); | ||
sessionManager.destroySession(); | ||
expect( | ||
await sessionManager.getSessionItem(StorageKeys.accessToken), | ||
).toBeNull(); | ||
}); | ||
|
||
it("should clear all items from session storage", async () => { | ||
await sessionManager.setSessionItem(StorageKeys.accessToken, true); | ||
expect(await sessionManager.getSessionItem(StorageKeys.accessToken)).toBe( | ||
"true", | ||
); | ||
sessionManager.destroySession(); | ||
expect( | ||
await sessionManager.getSessionItem(StorageKeys.accessToken), | ||
).toBeNull(); | ||
}); | ||
}); | ||
|
||
describe("LocalStorage keys: storageKeys", () => { | ||
let sessionManager: LocalStorage<ExtraKeys>; | ||
|
||
beforeEach(() => { | ||
sessionManager = new LocalStorage<ExtraKeys>(); | ||
}); | ||
|
||
it("should set and get an item in storage: StorageKeys", async () => { | ||
await sessionManager.setSessionItem(StorageKeys.accessToken, "testValue"); | ||
expect(await sessionManager.getSessionItem(StorageKeys.accessToken)).toBe( | ||
"testValue", | ||
); | ||
}); | ||
|
||
it("should remove an item from storage: StorageKeys", async () => { | ||
await sessionManager.setSessionItem(StorageKeys.accessToken, "testValue"); | ||
expect(await sessionManager.getSessionItem(StorageKeys.accessToken)).toBe( | ||
"testValue", | ||
); | ||
|
||
await sessionManager.removeSessionItem(StorageKeys.accessToken); | ||
expect( | ||
await sessionManager.getSessionItem(StorageKeys.accessToken), | ||
).toBeNull(); | ||
}); | ||
|
||
it("should clear all items from storage: StorageKeys", async () => { | ||
await sessionManager.setSessionItem(StorageKeys.accessToken, "testValue"); | ||
expect(await sessionManager.getSessionItem(StorageKeys.accessToken)).toBe( | ||
"testValue", | ||
); | ||
|
||
sessionManager.destroySession(); | ||
expect( | ||
await sessionManager.getSessionItem(StorageKeys.accessToken), | ||
).toBeNull(); | ||
}); | ||
|
||
it("should set and get an item in extra storage", async () => { | ||
await sessionManager.setSessionItem(ExtraKeys.testKey, "testValue"); | ||
expect(await sessionManager.getSessionItem(ExtraKeys.testKey)).toBe( | ||
"testValue", | ||
); | ||
}); | ||
|
||
it("should remove an item from extra storage", async () => { | ||
await sessionManager.setSessionItem(ExtraKeys.testKey, "testValue"); | ||
expect(await sessionManager.getSessionItem(ExtraKeys.testKey)).toBe( | ||
"testValue", | ||
); | ||
|
||
sessionManager.removeSessionItem(ExtraKeys.testKey); | ||
expect(await sessionManager.getSessionItem(ExtraKeys.testKey)).toBeNull(); | ||
}); | ||
|
||
it("should clear all items from extra storage", async () => { | ||
await sessionManager.setSessionItem(ExtraKeys.testKey, "testValue"); | ||
expect(await sessionManager.getSessionItem(ExtraKeys.testKey)).toBe( | ||
"testValue", | ||
); | ||
|
||
sessionManager.destroySession(); | ||
expect(await sessionManager.getSessionItem(ExtraKeys.testKey)).toBeNull(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { storageSettings } from "../index.js"; | ||
import { StorageKeys, type SessionManager } from "../types.js"; | ||
import { splitString } from "../utils.js"; | ||
|
||
/** | ||
* Provides a localStorage based session manager implementation for the browser. | ||
* @class LocalStorage | ||
*/ | ||
export class LocalStorage<V = StorageKeys> implements SessionManager<V> { | ||
constructor() { | ||
console.warn("LocalStorage store should not be used in production"); | ||
} | ||
|
||
setItems: Set<V | StorageKeys> = new Set<V>(); | ||
|
||
/** | ||
* Clears all items from session store. | ||
* @returns {void} | ||
*/ | ||
async destroySession(): Promise<void> { | ||
this.setItems.forEach((key) => { | ||
this.removeSessionItem(key); | ||
}); | ||
} | ||
|
||
/** | ||
* Sets the provided key-value store to the localStorage cache. | ||
* @param {V} itemKey | ||
* @param {unknown} itemValue | ||
* @returns {void} | ||
*/ | ||
async setSessionItem( | ||
itemKey: V | StorageKeys, | ||
itemValue: unknown, | ||
): Promise<void> { | ||
// clear items first | ||
await this.removeSessionItem(itemKey); | ||
this.setItems.add(itemKey); | ||
|
||
if (typeof itemValue === "string") { | ||
splitString(itemValue, storageSettings.maxLength).forEach( | ||
(splitValue, index) => { | ||
localStorage.setItem( | ||
`${storageSettings.keyPrefix}${itemKey}${index}`, | ||
splitValue, | ||
); | ||
}, | ||
); | ||
return; | ||
} | ||
localStorage.setItem( | ||
`${storageSettings.keyPrefix}${itemKey}0`, | ||
itemValue as string, | ||
); | ||
} | ||
|
||
/** | ||
* Gets the item for the provided key from the localStorage cache. | ||
* @param {string} itemKey | ||
* @returns {unknown | null} | ||
*/ | ||
async getSessionItem(itemKey: V | StorageKeys): Promise<unknown | null> { | ||
if ( | ||
localStorage.getItem(`${storageSettings.keyPrefix}${itemKey}0`) === null | ||
) { | ||
return null; | ||
} | ||
|
||
let itemValue = ""; | ||
let index = 0; | ||
let key = `${storageSettings.keyPrefix}${String(itemKey)}${index}`; | ||
while (localStorage.getItem(key) !== null) { | ||
itemValue += localStorage.getItem(key); | ||
index++; | ||
key = `${storageSettings.keyPrefix}${String(itemKey)}${index}`; | ||
} | ||
|
||
return itemValue; | ||
} | ||
|
||
/** | ||
* Removes the item for the provided key from the localStorage cache. | ||
* @param {V} itemKey | ||
* @returns {void} | ||
*/ | ||
async removeSessionItem(itemKey: V | StorageKeys): Promise<void> { | ||
// Remove all items with the key prefix | ||
let index = 0; | ||
while ( | ||
localStorage.getItem( | ||
`${storageSettings.keyPrefix}${String(itemKey)}${index}`, | ||
) !== null | ||
) { | ||
localStorage.removeItem( | ||
`${storageSettings.keyPrefix}${String(itemKey)}${index}`, | ||
); | ||
|
||
index++; | ||
} | ||
this.setItems.delete(itemKey); | ||
} | ||
} |