Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add expo-secure-store #5

Merged
merged 4 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe("index exports", () => {
"MemoryStorage",
"ChromeStore",
"storageSettings",
"ExpoSecureStore",
];

expect(actualExports.sort()).toEqual(expectedExports.sort());
Expand Down
1 change: 1 addition & 0 deletions lib/sessionManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const storageSettings: StorageSettingsType = {

export { MemoryStorage } from "./stores/memory.js";
export { ChromeStore } from "./stores/chromeStore.js";
export { ExpoSecureStore } from "./stores/expoSecureStore.js";
export * from "./types.ts";
8 changes: 4 additions & 4 deletions lib/sessionManager/stores/chromeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function getStorageValue(key: string): unknown | undefined {
}

/**
* Provides a memory based session manager implementation for the browser.
* Provides a chrome.store.local based session manager implementation for the browser.
* @class ChromeStore
*/
export class ChromeStore<V = StorageKeys> implements SessionManager<V> {
Expand All @@ -28,7 +28,7 @@ export class ChromeStore<V = StorageKeys> implements SessionManager<V> {
}

/**
* Sets the provided key-value store to the memory cache.
* Sets the provided key-value store to the chrome.store.local.
* @param {string} itemKey
* @param {unknown} itemValue
* @returns {void}
Expand Down Expand Up @@ -56,7 +56,7 @@ export class ChromeStore<V = StorageKeys> implements SessionManager<V> {
}

/**
* Gets the item for the provided key from the memory cache.
* Gets the item for the provided key from the chrome.store.local cache.
* @param {string} itemKey
* @returns {unknown | null}
*/
Expand All @@ -78,7 +78,7 @@ export class ChromeStore<V = StorageKeys> implements SessionManager<V> {
}

/**
* Removes the item for the provided key from the memory cache.
* Removes the item for the provided key from the chrome.store.local cache.
* @param {string} itemKey
* @returns {void}
*/
Expand Down
112 changes: 112 additions & 0 deletions lib/sessionManager/stores/expoSecureStore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { describe, it, expect, beforeEach } from "vitest";
import { ExpoSecureStore } from "./expoSecureStore";
import { StorageKeys } from "../types";

enum ExtraKeys {
testKey = "testKey2",
}

describe.skip("ExpoSecureStore standard keys", () => {
let sessionManager: ExpoSecureStore;

beforeEach(() => {
sessionManager = new ExpoSecureStore();
});

it("should set and get an item in session storage", async () => {
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();
});
});

describe.skip("ExpoSecureStore keys: storageKeys", () => {
let sessionManager: ExpoSecureStore<ExtraKeys>;

beforeEach(() => {
sessionManager = new ExpoSecureStore<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();
});
});
111 changes: 111 additions & 0 deletions lib/sessionManager/stores/expoSecureStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { storageSettings } from "../index.js";
import { StorageKeys, type SessionManager } from "../types.js";
import { splitString } from "../utils.js";

let expoSecureStore: typeof import("expo-secure-store") | undefined = undefined;

/**
* Provides a expo local store based session manager implementation for the browser.
* @class ExpoSecureStore
*/
export class ExpoSecureStore<V = StorageKeys> implements SessionManager<V> {
constructor() {
this.loadExpoStore();
}

Check warning on line 14 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L13-L14

Added lines #L13 - L14 were not covered by tests

private async loadExpoStore() {
try {
expoSecureStore = await import("expo-secure-store");
} catch (error) {
console.error("Error loading dependency expo storage:", error);
}
}

Check warning on line 22 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L17-L22

Added lines #L17 - L22 were not covered by tests
Comment on lines +5 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Handling of dynamic import for expo-secure-store.

The dynamic import of expo-secure-store is not consistently resolving, leading to expoSecureStore being undefined in some cases. This issue is evident from the static analysis failures and needs to be addressed to ensure reliable operation.

Consider initializing expoSecureStore in a more robust manner, possibly by ensuring the module is fully loaded before any session management methods are called.

Would you like me to help refactor the initialization process to ensure reliability?


/**
* Clears all items from session store.
* @returns {void}
*/
async destroySession(): Promise<void> {
const keys = Object.values(StorageKeys);
keys.forEach(async (key) => {
await this.removeSessionItem(key);
});
}

Check warning on line 33 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L29-L33

Added lines #L29 - L33 were not covered by tests

/**
* Sets the provided key-value store to ExpoSecureStore.
* @param {string} itemKey
* @param {unknown} itemValue
* @returns {void}
*/
async setSessionItem(
itemKey: V | StorageKeys,
itemValue: unknown,
): Promise<void> {

Check warning on line 44 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L42-L44

Added lines #L42 - L44 were not covered by tests
// clear items first
await this.removeSessionItem(itemKey);

Check warning on line 46 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L46

Added line #L46 was not covered by tests

if (typeof itemValue === "string") {
splitString(itemValue, Math.min(storageSettings.maxLength, 2048)).forEach(
async (splitValue, index) => {
await expoSecureStore!.setItemAsync(
`${storageSettings.keyPrefix}${itemKey}${index}`,
splitValue,
);
},
);
return;
} else {
throw new Error("Item value must be a string");
}
}

Check warning on line 61 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L48-L61

Added lines #L48 - L61 were not covered by tests
DanielRivers marked this conversation as resolved.
Show resolved Hide resolved

/**
* Gets the item for the provided key from the ExpoSecureStore.
* @param {string} itemKey
* @returns {unknown | null}
*/
async getSessionItem(itemKey: V | StorageKeys): Promise<unknown | null> {
const chunks = [];
let index = 0;

Check warning on line 70 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L69-L70

Added lines #L69 - L70 were not covered by tests

let chunk = await expoSecureStore!.getItemAsync(
`${storageSettings.keyPrefix}${String(itemKey)}${index}`,
);

Check warning on line 74 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L72-L74

Added lines #L72 - L74 were not covered by tests

while (chunk) {
chunks.push(chunk);
index++;

Check warning on line 78 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L76-L78

Added lines #L76 - L78 were not covered by tests

chunk = await expoSecureStore!.getItemAsync(
`${storageSettings.keyPrefix}${String(itemKey)}${index}`,
);
}

Check warning on line 83 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L80-L83

Added lines #L80 - L83 were not covered by tests

return chunks.join("");
}

Check warning on line 86 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L85-L86

Added lines #L85 - L86 were not covered by tests
DanielRivers marked this conversation as resolved.
Show resolved Hide resolved

/**
* Removes the item for the provided key from the ExpoSecureStore.
* @param {string} itemKey
* @returns {void}
*/
async removeSessionItem(itemKey: V | StorageKeys): Promise<void> {
let index = 0;

Check warning on line 94 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L94

Added line #L94 was not covered by tests

let chunk = await expoSecureStore!.getItemAsync(
`${storageSettings.keyPrefix}${String(itemKey)}${index}`,
);

Check warning on line 98 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L96-L98

Added lines #L96 - L98 were not covered by tests

while (chunk) {
await expoSecureStore!.deleteItemAsync(
`${storageSettings.keyPrefix}${String(itemKey)}${index}`,
);
index++;

Check warning on line 104 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L100-L104

Added lines #L100 - L104 were not covered by tests

chunk = await expoSecureStore!.getItemAsync(
`${storageSettings.keyPrefix}${String(itemKey)}${index}`,
);
}
}

Check warning on line 110 in lib/sessionManager/stores/expoSecureStore.ts

View check run for this annotation

Codecov / codecov/patch

lib/sessionManager/stores/expoSecureStore.ts#L106-L110

Added lines #L106 - L110 were not covered by tests
DanielRivers marked this conversation as resolved.
Show resolved Hide resolved
}
1 change: 1 addition & 0 deletions lib/sessionManager/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum StorageKeys {
idToken = "idToken",
refreshToken = "refreshToken",
state = "state",
nonce = "nonce",
}

export type StorageSettingsType = {
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@types/chrome": "^0.0.270",
"@types/node": "^22.5.1",
"@vitest/coverage-v8": "^2.0.5",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^9.9.1",
"globals": "^15.9.0",
"prettier": "^3.3.3",
Expand All @@ -41,5 +42,8 @@
"vite-plugin-dts": "^4.0.3",
"vitest": "^2.0.5"
},
"optionalDependencies": {
"expo-secure-store": "*"
},
"packageManager": "[email protected]+sha256.2df78e65d433d7693b9d3fbdaf431b2d96bb4f96a2ffecd51a50efe16e50a6a8"
}
Loading
Loading