Skip to content

Commit 6ec1f15

Browse files
committed
chore: update-pr-review
1 parent 50fd7fa commit 6ec1f15

8 files changed

Lines changed: 141 additions & 53 deletions

File tree

eslint.config.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
17
import { FlatCompat } from '@eslint/eslintrc';
28
import { dirname } from 'path';
39
import { fileURLToPath } from 'url';

packages/sdk-effects/storage/eslint.config.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
17
import baseConfig from '../../../eslint.config.mjs';
28

39
export default [
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
17
export * from './lib/storage.effects.js';

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

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
/*
3+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
4+
*
5+
* This software may be modified and distributed under the terms
6+
* of the MIT license. See the LICENSE file for details.
7+
*/
28
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
39
import { createStorage, type StorageConfig } from './storage.effects.js';
4-
import type { TokenStoreObject } from '@forgerock/sdk-types';
10+
import type { CustomStorageObject } from '@forgerock/sdk-types';
511

612
const localStorageMock = (() => {
713
let store: Record<string, string> = {};
@@ -13,7 +19,7 @@ const localStorageMock = (() => {
1319
}
1420
return Promise.resolve(value);
1521
}),
16-
setItem: vi.fn((key: string, value: string | Record<any, any> | any[]) => {
22+
setItem: vi.fn((key: string, value: any) => {
1723
const valueIsString = typeof value === 'string';
1824
store[key] = valueIsString ? value : JSON.stringify(value);
1925
return Promise.resolve();
@@ -64,7 +70,7 @@ const sessionStorageMock = (() => {
6470

6571
Object.defineProperty(global, 'sessionStorage', { value: sessionStorageMock });
6672

67-
const mockCustomStore: TokenStoreObject = {
73+
const mockCustomStore: CustomStorageObject = {
6874
get: vi.fn((key: string) => Promise.resolve<string | null>(null)),
6975
set: vi.fn((key: string, value: unknown) => Promise.resolve()),
7076
remove: vi.fn((key: string) => Promise.resolve()),
@@ -73,6 +79,7 @@ const mockCustomStore: TokenStoreObject = {
7379
describe('storage Effect', () => {
7480
const storageName = 'MyStorage';
7581
const baseConfig: Omit<StorageConfig, 'tokenStore'> = {
82+
storeType: 'localStorage',
7683
prefix: 'testPrefix',
7784
};
7885
const expectedKey = `${baseConfig.prefix}-${storageName}`;
@@ -91,14 +98,14 @@ describe('storage Effect', () => {
9198
describe('with localStorage', () => {
9299
const config: StorageConfig = {
93100
...baseConfig,
94-
tokenStore: 'localStorage',
101+
storeType: 'localStorage',
95102
};
96103

97104
const storageInstance = createStorage(config, storageName);
98105

99106
it('should call localStorage.getItem with the correct key and return value', async () => {
100-
localStorageMock.setItem(expectedKey, testValue);
101-
const result = await storageInstance.get<string>();
107+
localStorageMock.setItem(expectedKey, JSON.stringify(testValue));
108+
const result = await storageInstance.get();
102109
expect(localStorageMock.getItem).toHaveBeenCalledTimes(1);
103110
expect(localStorageMock.getItem).toHaveBeenCalledWith(expectedKey);
104111
expect(result).toBe(testValue);
@@ -107,7 +114,7 @@ describe('storage Effect', () => {
107114
});
108115

109116
it('should return null if localStorage.getItem finds no value', async () => {
110-
const result = await storageInstance.get<null>();
117+
const result = await storageInstance.get();
111118
expect(localStorageMock.getItem).toHaveBeenCalledTimes(1);
112119
expect(localStorageMock.getItem).toHaveBeenCalledWith(expectedKey);
113120
expect(result).toBeNull();
@@ -116,8 +123,8 @@ describe('storage Effect', () => {
116123
it('should call localStorage.setItem with the correct key and value', async () => {
117124
await storageInstance.set(testValue);
118125
expect(localStorageMock.setItem).toHaveBeenCalledTimes(1);
119-
expect(localStorageMock.setItem).toHaveBeenCalledWith(expectedKey, testValue);
120-
expect(await localStorageMock.getItem(expectedKey)).toBe(testValue);
126+
expect(localStorageMock.setItem).toHaveBeenCalledWith(expectedKey, JSON.stringify(testValue));
127+
expect(await localStorageMock.getItem(expectedKey)).toBe(JSON.stringify(testValue));
121128
expect(sessionStorageMock.setItem).not.toHaveBeenCalled();
122129
expect(mockCustomStore.set).not.toHaveBeenCalled();
123130
});
@@ -145,7 +152,7 @@ describe('storage Effect', () => {
145152
const testObject = { a: 1, b: 'test' };
146153
storageInstance.set(testObject);
147154

148-
const result = await storageInstance.get<typeof testObject>();
155+
const result = await storageInstance.get();
149156
console.log(result);
150157

151158
expect(localStorageMock.getItem).toHaveBeenCalledTimes(1);
@@ -158,13 +165,13 @@ describe('storage Effect', () => {
158165
describe('with sessionStorage', () => {
159166
const config: StorageConfig = {
160167
...baseConfig,
161-
tokenStore: 'sessionStorage',
168+
storeType: 'sessionStorage',
162169
};
163170
const storageName = 'MyStorage';
164171
const storageInstance = createStorage(config, storageName);
165172

166173
it('should call sessionStorage.getItem with the correct key and return value', async () => {
167-
sessionStorageMock.setItem(expectedKey, testValue);
174+
sessionStorageMock.setItem(expectedKey, JSON.stringify(testValue));
168175
const result = await storageInstance.get();
169176
expect(sessionStorageMock.getItem).toHaveBeenCalledTimes(1);
170177
expect(sessionStorageMock.getItem).toHaveBeenCalledWith(expectedKey);
@@ -182,7 +189,7 @@ describe('storage Effect', () => {
182189
it('should return parsed value if sessionStorage.getItem returns object or array', async () => {
183190
const obj = { tokens: 123 };
184191
await storageInstance.set(obj);
185-
const result = await storageInstance.get<typeof obj>();
192+
const result = await storageInstance.get();
186193
if (!result) {
187194
// we should not hit this expect
188195
expect(false).toBe(true);
@@ -221,13 +228,13 @@ describe('storage Effect', () => {
221228
describe('with custom TokenStoreObject', () => {
222229
const config: StorageConfig = {
223230
...baseConfig,
224-
tokenStore: 'localStorage',
231+
storeType: 'localStorage',
225232
};
226233
const myStorage = 'MyStorage';
227234
const storageInstance = createStorage(config, myStorage, mockCustomStore);
228235

229236
it('should call customStore.get with the correct key and return its value', async () => {
230-
(mockCustomStore.get as Mock).mockResolvedValueOnce(testValue);
237+
(mockCustomStore.get as Mock).mockResolvedValueOnce(JSON.stringify(testValue));
231238
const result = await storageInstance.get();
232239
expect(mockCustomStore.get).toHaveBeenCalledTimes(1);
233240
expect(mockCustomStore.get).toHaveBeenCalledWith(expectedKey);
@@ -248,7 +255,7 @@ describe('storage Effect', () => {
248255
const jsonString = JSON.stringify(testObject);
249256
(mockCustomStore.get as Mock).mockResolvedValueOnce(jsonString); // Mock returns JSON string
250257

251-
const result = await storageInstance.get<typeof testObject>();
258+
const result = await storageInstance.get();
252259

253260
expect(mockCustomStore.get).toHaveBeenCalledTimes(1);
254261
expect(mockCustomStore.get).toHaveBeenCalledWith(expectedKey);
@@ -258,7 +265,7 @@ describe('storage Effect', () => {
258265
it('should call customStore.set with the correct key and value', async () => {
259266
await storageInstance.set(testValue);
260267
expect(mockCustomStore.set).toHaveBeenCalledTimes(1);
261-
expect(mockCustomStore.set).toHaveBeenCalledWith(expectedKey, testValue);
268+
expect(mockCustomStore.set).toHaveBeenCalledWith(expectedKey, JSON.stringify(testValue));
262269
expect(localStorageMock.setItem).not.toHaveBeenCalled();
263270
expect(sessionStorageMock.setItem).not.toHaveBeenCalled();
264271
});
@@ -282,7 +289,7 @@ describe('storage Effect', () => {
282289
});
283290

284291
it('should return a function that returns the storage interface', () => {
285-
const config: StorageConfig = { ...baseConfig, tokenStore: 'localStorage' };
292+
const config: StorageConfig = { ...baseConfig, storeType: 'localStorage' };
286293
const myStorage = 'MyStorage';
287294
const storageInterface = createStorage(config, myStorage);
288295
expect(storageInterface).toHaveProperty('get');

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

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,128 @@
1-
import { TokenStoreObject } from '@forgerock/sdk-types';
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
import { CustomStorageObject } from '@forgerock/sdk-types';
28

39
export interface StorageConfig {
4-
tokenStore: TokenStoreObject | 'localStorage' | 'sessionStorage';
5-
prefix: string;
10+
storeType: CustomStorageObject | 'localStorage' | 'sessionStorage';
11+
prefix?: string;
612
}
713

8-
export function createStorage(
14+
interface GenericError {
15+
code?: string | number;
16+
message: string;
17+
type:
18+
| 'argument_error'
19+
| 'davinci_error'
20+
| 'internal_error'
21+
| 'network_error'
22+
| 'state_error'
23+
| 'unknown_error';
24+
}
25+
26+
export function createStorage<Value>(
927
config: StorageConfig,
1028
storageName: string,
11-
customTokenStore?: TokenStoreObject,
29+
customStore?: CustomStorageObject,
1230
) {
13-
const { tokenStore, prefix } = config;
31+
const { storeType, prefix = 'pic' } = config;
1432

1533
const key = `${prefix}-${storageName}`;
1634
return {
17-
get: async function storageGet<ReturnValue>(): Promise<ReturnValue | string | null> {
18-
if (customTokenStore) {
19-
const value = await customTokenStore.get(key);
35+
get: async function storageGet(): Promise<Value | GenericError | null> {
36+
if (customStore) {
37+
const value = await customStore.get(key);
2038
if (value === null) {
2139
return value;
2240
}
23-
if (value.startsWith('[') || value.startsWith('{')) {
41+
try {
2442
const parsed = JSON.parse(value);
25-
return parsed as ReturnValue;
43+
return parsed as Value;
44+
} catch {
45+
return {
46+
code: 'Parse_Error',
47+
message: 'Eror parsing value from provided storage',
48+
type: 'unknown_error',
49+
};
2650
}
27-
return value;
2851
}
29-
if (tokenStore === 'sessionStorage') {
52+
if (storeType === 'sessionStorage') {
3053
const value = await sessionStorage.getItem(key);
3154
if (value === null) {
3255
return value;
3356
}
34-
if (value.startsWith('[') || value.startsWith('{')) {
57+
try {
3558
const parsed = JSON.parse(value);
36-
return parsed as ReturnValue;
59+
return parsed as Value;
60+
} catch {
61+
return {
62+
code: 'Parse_Error',
63+
message: 'Eror parsing value from session storage',
64+
type: 'unknown_error',
65+
};
3766
}
38-
return value;
3967
}
4068
const value = await localStorage.getItem(key);
4169

4270
if (value === null) {
4371
return value;
4472
}
45-
46-
if (value.startsWith('[') || value.startsWith('{')) {
73+
try {
4774
const parsed = JSON.parse(value);
48-
return parsed as ReturnValue;
75+
return parsed as Value;
76+
} catch {
77+
return {
78+
code: 'Parse_Error',
79+
message: 'Eror parsing value from local storage',
80+
type: 'unknown_error',
81+
};
4982
}
50-
return value;
5183
},
52-
set: async function storageSet(value: string | Record<any, any> | any[]) {
53-
const valueIsString = typeof value === 'string';
54-
const valueToStore = valueIsString ? value : JSON.stringify(value);
55-
56-
if (customTokenStore) {
57-
return await customTokenStore.set(key, valueToStore);
84+
set: async function storageSet(value: Value) {
85+
const valueToStore = JSON.stringify(value);
86+
if (customStore) {
87+
try {
88+
await customStore.set(key, valueToStore);
89+
return Promise.resolve();
90+
} catch {
91+
return {
92+
code: 'Storing_Error',
93+
message: 'Eror storing value in custom storage',
94+
type: 'unknown_error',
95+
};
96+
}
97+
}
98+
if (storeType === 'sessionStorage') {
99+
try {
100+
await sessionStorage.setItem(key, valueToStore);
101+
return Promise.resolve();
102+
} catch {
103+
return {
104+
code: 'Storing_Error',
105+
message: 'Eror storing value in session storage',
106+
type: 'unknown_error',
107+
};
108+
}
58109
}
59-
if (tokenStore === 'sessionStorage') {
60-
return await sessionStorage.setItem(key, valueToStore);
110+
try {
111+
await localStorage.setItem(key, valueToStore);
112+
return Promise.resolve();
113+
} catch {
114+
return {
115+
code: 'Storing_Error',
116+
message: 'Eror storing value in local storage',
117+
type: 'unknown_error',
118+
};
61119
}
62-
return await localStorage.setItem(key, valueToStore);
63120
},
64121
remove: async function storageSet() {
65-
if (customTokenStore) {
66-
return await customTokenStore.remove(key);
122+
if (customStore) {
123+
return await customStore.remove(key);
67124
}
68-
if (tokenStore === 'sessionStorage') {
125+
if (storeType === 'sessionStorage') {
69126
return await sessionStorage.removeItem(key);
70127
}
71128
return await localStorage.removeItem(key);

packages/sdk-effects/storage/vite.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
17
import { defineConfig } from 'vite';
28

39
export default defineConfig(() => ({

packages/sdk-types/src/lib/legacy-config.types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import type { Callback } from './am-callback.types.js';
1313
import type { LegacyRequestMiddleware } from './legacy-mware.types.js';
14-
import { TokenStoreObject } from './tokens.types.js';
14+
import { CustomStorageObject } from './tokens.types.js';
1515

1616
/**
1717
* Optional configuration for custom paths for actions
@@ -109,7 +109,7 @@ export interface LegacyConfigOptions {
109109
redirectUri?: string;
110110
scope?: string;
111111
serverConfig?: ServerConfig;
112-
tokenStore?: TokenStoreObject | 'sessionStorage' | 'localStorage';
112+
tokenStore?: CustomStorageObject | 'sessionStorage' | 'localStorage';
113113
tree?: string;
114114
type?: string;
115115
oauthThreshold?: number;

packages/sdk-types/src/lib/tokens.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface Tokens {
1515
/**
1616
* API for implementing a custom token store
1717
*/
18-
export interface TokenStoreObject {
18+
export interface CustomStorageObject {
1919
get: (key: string) => Promise<string | null>;
2020
set: (key: string, valueToSet: string) => Promise<void>;
2121
remove: (key: string) => Promise<void>;

0 commit comments

Comments
 (0)