Repository: authts/oidc-client-ts (ts)

Issue:
Typing issue: WebStorageStateStore.store should be an async store
I'm trying to use my own custom store implementation where all the data has to be read/write in an async manner.

```ts
userStore: new WebStorageStateStore({
  store: new MyStore()
});
```

The issue I'm facing is that [WebStorageStateStore](https://github.com/authts/oidc-client-ts/blob/main/src/WebStorageStateStore.ts) uses async methods whereas [WebStorageStateStore.store](https://github.com/authts/oidc-client-ts/blob/main/src/WebStorageStateStore.ts#L13) uses sync ones as it is of type `Storage`.

**Solutions:**
1) I could make my own implementation of `WebStorageStateStore` but then I would loose or duplicate anything that this wrapper is doing -> so I don't really like that solution.
2) It seems that supporting an async store is already implemented as all the methods from `WebStorageStateStore` returns a `Promise.resolve` of the store's results (ex: [line 34](https://github.com/authts/oidc-client-ts/blob/main/src/WebStorageStateStore.ts#L34), [line 43](https://github.com/authts/oidc-client-ts/blob/main/src/WebStorageStateStore.ts#L43), ...).
-> so the ideal solution would be to replace the type of `WebStorageStateStore.store` and make it reflect an async `Storage` type instead

What do you think ?

Pull request:
Move Storage to Async
<!-- Please link relevant issue numbers or provide context for this change -->
Closes/fixes #609

### Checklist

- [X] This PR makes changes to the public API <!-- was the API report (docs/oidc-client-ts.api.md) updated by this PR? -->
- [X] I have included links for closing relevant issue numbers


Patch:
diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md
index 91d9c201..280f2fb1 100644
--- a/docs/oidc-client-ts.api.md
+++ b/docs/oidc-client-ts.api.md
@@ -24,6 +24,16 @@ export class AccessTokenEvents {
     unload(): void;
 }
 
+// @public (undocumented)
+export interface AsyncStorage {
+    clear(): Promise<void>;
+    getItem(key: string): Promise<string | null>;
+    key(index: number): Promise<string | null>;
+    readonly length: Promise<number>;
+    removeItem(key: string): Promise<void>;
+    setItem(key: string, value: string): Promise<void>;
+}
+
 // @internal (undocumented)
 export class CheckSessionIFrame {
     constructor(_callback: () => Promise<void>, _client_id: string, url: string, _intervalInSeconds: number, _stopOnError: boolean);
@@ -1068,9 +1078,9 @@ export const Version: string;
 
 // @public (undocumented)
 export class WebStorageStateStore implements StateStore {
-    constructor({ prefix, store }?: {
-        prefix?: string | undefined;
-        store?: Storage | undefined;
+    constructor({ prefix, store, }?: {
+        prefix?: string;
+        store?: AsyncStorage | Storage;
     });
     // (undocumented)
     get(key: string): Promise<string | null>;
diff --git a/src/AsyncStorage.ts b/src/AsyncStorage.ts
new file mode 100644
index 00000000..9f34edb0
--- /dev/null
+++ b/src/AsyncStorage.ts
@@ -0,0 +1,31 @@
+/**
+ * @public
+ */
+export interface AsyncStorage {
+    /** Returns the number of key/value pairs. */
+    readonly length: Promise<number>;
+    /**
+     * Removes all key/value pairs, if there are any.
+     *
+     * Dispatches a storage event on Window objects holding an equivalent Storage object.
+     */
+    clear(): Promise<void>;
+    /** Returns the current value associated with the given key, or null if the given key does not exist. */
+    getItem(key: string): Promise<string | null>;
+    /** Returns the name of the nth key, or null if n is greater than or equal to the number of key/value pairs. */
+    key(index: number): Promise<string | null>;
+    /**
+     * Removes the key/value pair with the given key, if a key/value pair with the given key exists.
+     *
+     * Dispatches a storage event on Window objects holding an equivalent Storage object.
+     */
+    removeItem(key: string): Promise<void>;
+    /**
+     * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
+     *
+     * Throws a "QuotaExceededError" DOMException exception if the new value couldn't be set. (Setting could fail if, e.g., the user has disabled storage for the site, or if the quota has been exceeded.)
+     *
+     * Dispatches a storage event on Window objects holding an equivalent Storage object.
+     */
+    setItem(key: string, value: string): Promise<void>;
+}
diff --git a/src/index.ts b/src/index.ts
index 10e8d906..987283f8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,6 +11,7 @@ export { AccessTokenEvents } from "./AccessTokenEvents";
 export type { AccessTokenCallback } from "./AccessTokenEvents";
 export { CheckSessionIFrame } from "./CheckSessionIFrame";
 export { InMemoryWebStorage } from "./InMemoryWebStorage";
+export type { AsyncStorage } from "./AsyncStorage";
 export { MetadataService } from "./MetadataService";
 export * from "./OidcClient";
 export { OidcClientSettingsStore } from "./OidcClientSettings";


Test patch:
diff --git a/src/WebStorageStateStore.ts b/src/WebStorageStateStore.ts
index abf07144..11240894 100644
--- a/src/WebStorageStateStore.ts
+++ b/src/WebStorageStateStore.ts
@@ -3,6 +3,7 @@
 
 import { Logger } from "./utils";
 import type { StateStore } from "./StateStore";
+import type { AsyncStorage } from "./AsyncStorage";
 
 /**
  * @public
@@ -10,49 +11,52 @@ import type { StateStore } from "./StateStore";
 export class WebStorageStateStore implements StateStore {
     private readonly _logger = new Logger("WebStorageStateStore");
 
-    private readonly _store: Storage;
+    private readonly _store: AsyncStorage | Storage;
     private readonly _prefix: string;
 
-    public constructor({ prefix = "oidc.", store = localStorage } = {}) {
+    public constructor({
+        prefix = "oidc.",
+        store = localStorage,
+    }: { prefix?: string; store?: AsyncStorage | Storage } = {}) {
         this._store = store;
         this._prefix = prefix;
     }
 
-    public set(key: string, value: string): Promise<void> {
+    public async set(key: string, value: string): Promise<void> {
         this._logger.create(`set('${key}')`);
 
         key = this._prefix + key;
-        this._store.setItem(key, value);
-        return Promise.resolve();
+        await this._store.setItem(key, value);
     }
 
-    public get(key: string): Promise<string | null> {
+    public async get(key: string): Promise<string | null> {
         this._logger.create(`get('${key}')`);
 
         key = this._prefix + key;
-        const item = this._store.getItem(key);
-        return Promise.resolve(item);
+        const item = await this._store.getItem(key);
+        return item;
     }
 
-    public remove(key: string): Promise<string | null> {
+    public async remove(key: string): Promise<string | null> {
         this._logger.create(`remove('${key}')`);
 
         key = this._prefix + key;
-        const item = this._store.getItem(key);
-        this._store.removeItem(key);
-        return Promise.resolve(item);
+        const item = await this._store.getItem(key);
+        await this._store.removeItem(key);
+        return item;
     }
 
-    public getAllKeys(): Promise<string[]> {
+    public async getAllKeys(): Promise<string[]> {
         this._logger.create("getAllKeys");
+        const len = await this._store.length;
 
         const keys = [];
-        for (let index = 0; index < this._store.length; index++) {
-            const key = this._store.key(index);
+        for (let index = 0; index < len; index++) {
+            const key = await this._store.key(index);
             if (key && key.indexOf(this._prefix) === 0) {
                 keys.push(key.substr(this._prefix.length));
             }
         }
-        return Promise.resolve(keys);
+        return keys;
     }
 }