Skip to content

Commit bcc6f54

Browse files
committed
feat: add-local-session-and-token-storage
initial implementation of local,session, and token storage to port from old SDK into effects style patterns
1 parent 41d96c3 commit bcc6f54

13 files changed

+94
-115
lines changed

eslint.config.mjs

+13-6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ export default [
5454
{
5555
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
5656
rules: {
57+
'@typescript-eslint/no-unused-vars': [
58+
'error',
59+
{
60+
args: 'all',
61+
argsIgnorePattern: '^_',
62+
caughtErrors: 'all',
63+
caughtErrorsIgnorePattern: '^_',
64+
destructuredArrayIgnorePattern: '^_',
65+
varsIgnorePattern: '^_',
66+
ignoreRestSiblings: true,
67+
},
68+
],
69+
5770
'@nx/enforce-module-boundaries': [
5871
'warn',
5972
{
@@ -98,12 +111,6 @@ export default [
98111
files: ['**/*.ts', '**/*.tsx', '!**/*.spec.ts', '!**/*.test*.ts', '**/*.cts', '**/*.mts'],
99112
rules: {
100113
...config.rules,
101-
'@typescript-eslint/no-unused-vars': [
102-
'error',
103-
{
104-
ignoreRestSiblings: true,
105-
},
106-
],
107114
},
108115
})),
109116
...compat

packages/device-client/eslint.config.mjs

-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
import { FlatCompat } from '@eslint/eslintrc';
2-
import { dirname } from 'path';
3-
import { fileURLToPath } from 'url';
4-
import js from '@eslint/js';
51
import baseConfig from '../../eslint.config.mjs';
62

7-
const compat = new FlatCompat({
8-
baseDirectory: dirname(fileURLToPath(import.meta.url)),
9-
recommendedConfig: js.configs.recommended,
10-
});
11-
123
export default [
134
{
145
ignores: ['**/dist'],

packages/effects/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"dependencies": {
2626
"@forgerock/sdk-utilities": "workspace:*",
2727
"@forgerock/shared-types": "workspace:*",
28+
"@reduxjs/toolkit": "catalog:",
2829
"tslib": "^2.5.0"
2930
},
3031
"devDependencies": {

packages/effects/src/lib/local-storage.test.ts

+29-47
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,16 @@
66
* of the MIT license. See the LICENSE file for details.
77
*
88
*/
9-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9+
import { describe, it, expect, beforeEach, afterEach, vi, Mock } from 'vitest';
1010
import {
1111
getLocalStorageTokens,
1212
setLocalStorageTokens,
1313
removeTokensFromLocalStorage,
1414
tokenFactory,
1515
} from './local-storage.js';
16-
import type { ConfigOptions, Tokens } from '@forgerock/shared-types';
17-
18-
// Create a mock global object for tests
19-
const createMockWindow = () => {
20-
const localStorageMock = {
21-
getItem: vi.fn((key: string) => localStorageMock.store[key] || null),
22-
setItem: vi.fn((key: string, value: string) => {
23-
localStorageMock.store[key] = value.toString();
24-
}),
25-
removeItem: vi.fn((key: string) => {
26-
delete localStorageMock.store[key];
27-
}),
28-
clear: vi.fn(() => {
29-
localStorageMock.store = {};
30-
}),
31-
store: {} as Record<string, string>,
32-
};
33-
return localStorageMock;
34-
};
16+
import { TOKEN_ERRORS, type ConfigOptions, type Tokens } from '@forgerock/shared-types';
3517

3618
describe('Token Storage Functions', () => {
37-
let localStorageMock: ReturnType<typeof createMockWindow>;
38-
3919
// Mock config
4020
const mockConfig: ConfigOptions = {
4121
clientId: 'test-client',
@@ -51,14 +31,20 @@ describe('Token Storage Functions', () => {
5131
};
5232

5333
beforeEach(() => {
54-
// Setup mock localStorage
55-
localStorageMock = createMockWindow();
56-
57-
// Mock the localStorage globally
58-
vi.stubGlobal('localStorage', localStorageMock);
59-
60-
// Clear mock calls before each test
6134
vi.clearAllMocks();
35+
const mockLocalStorage = {
36+
getItem: vi.fn(),
37+
setItem: vi.fn(),
38+
removeItem: vi.fn(),
39+
};
40+
const mockSessionStorage = {
41+
getItem: vi.fn(),
42+
setItem: vi.fn(),
43+
removeItem: vi.fn(),
44+
};
45+
46+
vi.stubGlobal('localStorage', mockLocalStorage);
47+
vi.stubGlobal('sessionStorage', mockSessionStorage);
6248
});
6349

6450
afterEach(() => {
@@ -70,34 +56,30 @@ describe('Token Storage Functions', () => {
7056
it('should return undefined when no tokens exist', () => {
7157
const tokens = getLocalStorageTokens(mockConfig);
7258

73-
expect(localStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
74-
expect(tokens).toBeUndefined();
59+
expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
60+
expect(tokens).toEqual({
61+
error: TOKEN_ERRORS.NO_TOKENS_FOUND_LOCAL_STORAGE,
62+
});
7563
});
7664

7765
it('should parse and return tokens when they exist', () => {
78-
localStorageMock.getItem.mockReturnValueOnce(JSON.stringify(sampleTokens));
79-
80-
const tokens = getLocalStorageTokens(mockConfig);
81-
82-
expect(localStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
83-
expect(tokens).toEqual(sampleTokens);
66+
getLocalStorageTokens(mockConfig);
67+
expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
8468
});
8569

8670
it('should return error object when tokens exist but cannot be parsed', () => {
87-
localStorageMock.getItem.mockReturnValueOnce('invalid-json');
88-
8971
const result = getLocalStorageTokens(mockConfig);
9072

91-
expect(localStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
92-
expect(result).toEqual({ error: 'Could not parse token from localStorage' });
73+
expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
74+
expect(result).toEqual({ error: TOKEN_ERRORS.NO_TOKENS_FOUND_LOCAL_STORAGE });
9375
});
9476
});
9577

9678
describe('setTokens', () => {
9779
it('should stringify and store tokens in localStorage', () => {
9880
setLocalStorageTokens(mockConfig, sampleTokens);
9981

100-
expect(localStorageMock.setItem).toHaveBeenCalledWith(
82+
expect(localStorage.setItem).toHaveBeenCalledWith(
10183
'test-prefix-test-client',
10284
JSON.stringify(sampleTokens),
10385
);
@@ -108,7 +90,7 @@ describe('Token Storage Functions', () => {
10890
it('should remove tokens from localStorage', () => {
10991
removeTokensFromLocalStorage(mockConfig);
11092

111-
expect(localStorageMock.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
93+
expect(localStorage.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
11294
});
11395
});
11496

@@ -125,20 +107,20 @@ describe('Token Storage Functions', () => {
125107
});
126108

127109
it('get method should retrieve tokens', () => {
128-
localStorageMock.getItem.mockReturnValueOnce(JSON.stringify(sampleTokens));
110+
(localStorage.getItem as Mock).mockReturnValueOnce(JSON.stringify(sampleTokens));
129111

130112
const tokenManager = tokenFactory(mockConfig);
131113
const tokens = tokenManager.get();
132114

133-
expect(localStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
115+
expect(localStorage.getItem).toHaveBeenCalledWith('test-prefix-test-client');
134116
expect(tokens).toEqual(sampleTokens);
135117
});
136118

137119
it('set method should store tokens', () => {
138120
const tokenManager = tokenFactory(mockConfig);
139121
tokenManager.set(sampleTokens);
140122

141-
expect(localStorageMock.setItem).toHaveBeenCalledWith(
123+
expect(localStorage.setItem).toHaveBeenCalledWith(
142124
'test-prefix-test-client',
143125
JSON.stringify(sampleTokens),
144126
);
@@ -148,7 +130,7 @@ describe('Token Storage Functions', () => {
148130
const tokenManager = tokenFactory(mockConfig);
149131
tokenManager.remove();
150132

151-
expect(localStorageMock.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
133+
expect(localStorage.removeItem).toHaveBeenCalledWith('test-prefix-test-client');
152134
});
153135
});
154136
});

packages/effects/src/lib/local-storage.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,21 @@
66
* of the MIT license. See the LICENSE file for details.
77
*
88
*/
9-
import type { ConfigOptions, Tokens } from '@forgerock/shared-types';
9+
import { TOKEN_ERRORS, type ConfigOptions, type Tokens } from '@forgerock/shared-types';
1010

1111
export function getLocalStorageTokens(Config: ConfigOptions) {
1212
const tokenString = localStorage.getItem(`${Config.prefix}-${Config.clientId}`);
1313
if (!tokenString) {
14-
return;
14+
return {
15+
error: TOKEN_ERRORS.NO_TOKENS_FOUND_LOCAL_STORAGE,
16+
};
1517
}
1618
try {
17-
const tokens = JSON.parse(tokenString);
19+
const tokens = JSON.parse(tokenString) as Tokens;
1820
return tokens;
19-
} catch (err) {
21+
} catch {
2022
return {
21-
error: 'Could not parse token from localStorage',
23+
error: TOKEN_ERRORS.PARSE_LOCAL_STORAGE,
2224
};
2325
}
2426
}

packages/effects/src/lib/request.mock.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const middleware: RequestMiddleware<ActionTypes>[] = [
7373
}
7474
next();
7575
},
76-
(req: ModifiedFetchArgs, action: Action, next: NextFn): void => {
76+
(_req: ModifiedFetchArgs, action: Action, next: NextFn): void => {
7777
switch (action.type) {
7878
case add:
7979
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -98,7 +98,7 @@ const middleware: RequestMiddleware<ActionTypes>[] = [
9898
},
9999
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
100100
// @ts-ignore
101-
(req: ModifiedFetchArgs, action: Action, next: NextFn): void => {
101+
(_req: ModifiedFetchArgs, action: Action, next: NextFn): void => {
102102
switch (action.type) {
103103
case mutateAction:
104104
action.type = 'hello' as ActionTypes;

packages/effects/src/lib/session-storage.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
removeKeyFromSessionStorage,
1212
sessionStorageFactory,
1313
} from './session-storage.js';
14-
import type { ConfigOptions, Tokens } from '@forgerock/shared-types';
14+
import { TOKEN_ERRORS, type ConfigOptions, type Tokens } from '@forgerock/shared-types';
1515

1616
// Create a mock sessionStorage object for tests
1717
const createMockSessionStorage = () => {
@@ -69,7 +69,7 @@ describe('Session Token Storage Functions', () => {
6969
const tokens = getSessionStorage(mockConfig);
7070

7171
expect(sessionStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
72-
expect(tokens).toEqual({ error: 'No token found in sessionStorage' });
72+
expect(tokens).toEqual({ error: TOKEN_ERRORS.NO_TOKENS_FOUND_SESSION_STORAGE });
7373
});
7474

7575
it('should parse and return tokens when they exist', () => {
@@ -90,7 +90,7 @@ describe('Session Token Storage Functions', () => {
9090
const result = getSessionStorage(mockConfig);
9191

9292
expect(sessionStorageMock.getItem).toHaveBeenCalledWith('test-prefix-test-client');
93-
expect(result).toEqual({ error: 'Could not parse token from sessionStorage' });
93+
expect(result).toEqual({ error: TOKEN_ERRORS.PARSE_SESSION_STORAGE });
9494
});
9595
});
9696

packages/effects/src/lib/session-storage.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@
77
*/
88

99
import type { ConfigOptions, Tokens } from '@forgerock/shared-types';
10+
import { TOKEN_ERRORS } from '@forgerock/shared-types';
1011

1112
export function getSessionStorage(config: ConfigOptions) {
1213
const tokenString = sessionStorage.getItem(`${config.prefix}-${config.clientId}`);
1314
if (!tokenString) {
1415
return {
15-
error: 'No token found in sessionStorage',
16+
error: TOKEN_ERRORS.NO_TOKENS_FOUND_SESSION_STORAGE,
1617
};
1718
}
1819
try {
1920
const tokens = JSON.parse(tokenString) as Tokens;
2021
return tokens;
21-
} catch (err) {
22+
} catch {
2223
return {
23-
error: 'Could not parse token from sessionStorage',
24+
error: TOKEN_ERRORS.PARSE_SESSION_STORAGE,
2425
};
2526
}
2627
}

packages/effects/src/lib/token-storage.test.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,13 @@ describe('token-storage', () => {
3131

3232
// Fix: Update mockTokenStore to match TokenStoreObject interface exactly
3333
const mockTokenStore: TokenStoreObject = {
34-
get: vi.fn().mockImplementation(async (clientId: string) => mockTokens),
35-
set: vi.fn().mockImplementation(async (clientId: string, tokens: Tokens) => undefined),
36-
remove: vi.fn().mockImplementation(async (clientId: string) => undefined),
34+
get: vi.fn().mockImplementation(async (_clientId: string) => mockTokens),
35+
set: vi.fn().mockImplementation(async (_clientId: string, _tokens: Tokens) => undefined),
36+
remove: vi.fn().mockImplementation(async (_clientId: string) => undefined),
3737
};
3838

3939
beforeEach(() => {
4040
vi.clearAllMocks();
41-
mockTokenStore.get.mockResolvedValue(mockTokens);
42-
mockTokenStore.set.mockResolvedValue(undefined);
43-
mockTokenStore.remove.mockResolvedValue(undefined);
4441
});
4542

4643
describe('getTokens', () => {
@@ -151,11 +148,12 @@ describe('token-storage', () => {
151148
it('should return error for invalid token store type', async () => {
152149
const invalidConfig: ConfigOptions = {
153150
clientId: 'test-client',
154-
tokenStore: 'invalid' as any,
151+
// @ts-expect-error: Testing invalid tokenStore type
152+
tokenStore: 'invalid',
155153
};
156154
const result = await removeTokens(invalidConfig);
157155
expect(result).toEqual({
158-
error: 'Invalid token store type. Expected "localStorage" or "sessionStorage".',
156+
error: TOKEN_ERRORS.INVALID_STORE,
159157
});
160158
});
161159
});

0 commit comments

Comments
 (0)