Skip to content

Commit

Permalink
fix(core): undefined window bug with setItem on server (#10871)
Browse files Browse the repository at this point in the history
  • Loading branch information
AllanZhengYP authored Jan 18, 2023
1 parent f939c44 commit 9713a81
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 1 deletion.
139 changes: 139 additions & 0 deletions packages/core/__tests__/UniversalStorage-browser-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const mockCookiesRemove = jest.fn();
const mockCookiesSet = jest.fn();
const mockCookiesGet = jest.fn();
const mockCookiesGetAll = jest.fn();
const mockCookies = jest.fn().mockImplementation(function () {
return {
remove: mockCookiesRemove,
set: mockCookiesSet,
get: mockCookiesGet,
getAll: mockCookiesGetAll,
};
});
jest.mock('universal-cookie', () => ({
default: mockCookies,
}));
jest.mock('../src/JS', () => ({
browserOrNode: jest.fn().mockReturnValue({
isBrowser: true,
isNode: false,
}),
}));

import { UniversalStorage } from '../src/UniversalStorage';

describe(UniversalStorage.name, () => {
describe('on client side', () => {
let originalLocalStorage;
let universalStorage: UniversalStorage;

beforeEach(() => {
jest.clearAllMocks();
mockCookiesGetAll.mockReturnValue({});
window.localStorage.clear();
universalStorage = new UniversalStorage();
});

afterEach(() => {
window.localStorage = originalLocalStorage;
});

describe('constructor', () => {
test('initiates store with cookies', () => {
mockCookiesGetAll.mockReturnValue({ bar: 'barz' });
const universalStorage = new UniversalStorage();
expect(universalStorage.store).toMatchObject({ bar: 'barz' });
});
});

describe('setItem', () => {
test('sets item in local storage', () => {
universalStorage.setItem('foo', 'bar');
expect(universalStorage.store).toMatchObject({ foo: 'bar' });
});

test.each([
['LastAuthUser'],
['accessToken'],
['refreshToken'],
['idToken'],
])('sets session token %s to permenent cookie', tokenType => {
const key = `ProviderName.someid.someid.${tokenType}`;
const value = `${tokenType}-value`;
universalStorage.setItem(key, value);
expect(mockCookiesSet).toBeCalledWith(
key,
value,
expect.objectContaining({ path: '/', sameSite: true, secure: false })
);
expect(mockCookiesSet.mock.calls.length).toBe(1);
const expiresParam = mockCookiesSet.mock.calls[0]?.[2]?.expires;
expect(expiresParam).toBeInstanceOf(Date);
expect(expiresParam.valueOf()).toBeGreaterThan(Date.now());
});

test.each([
['LastAuthUser'],
['accessToken'],
['refreshToken'],
['idToken'],
])('sets session token %s to secure cookie(not localhost)', tokenType => {
// @ts-ignore
delete window.location;
// @ts-ignore
window.location = new URL('http://domain');
const key = `ProviderName.someid.someid.${tokenType}`;
const value = `${tokenType}-value`;
universalStorage.setItem(key, value);
window.location.hostname = 'http://domain';
expect(mockCookiesSet).toBeCalledWith(
key,
value,
expect.objectContaining({ secure: true })
);
});
});

describe('getItem', () => {
test('returns corresponding item from store', () => {
universalStorage.store['foo'] = 'bar';
expect(universalStorage.getItem('foo')).toBe('bar');
});
});

describe('key', () => {
test('returns key from store in insertion order', () => {
universalStorage.store['foo'] = 'bar';
universalStorage.store['baz'] = 'qux';
expect(universalStorage.key(0)).toBe('foo');
expect(universalStorage.key(1)).toBe('baz');
});
});

describe('removeItem', () => {
test('should remove item from local store', () => {
universalStorage.setItem('foo', 'bar');
universalStorage.removeItem('foo');
expect(Object.keys(universalStorage.store).length).toBe(0);
});

test('should remove item from cookies', () => {
universalStorage.setItem('foo', 'bar');
universalStorage.removeItem('foo');
expect(mockCookiesRemove).toBeCalledWith('foo', { path: '/' });
});
});

describe('clear', () => {
test('removes all items in store', () => {
const mockRemoveItem = spyOn(universalStorage, 'removeItem');
universalStorage.setItem('foo', 'bar');
universalStorage.setItem('quz', 'baz');
universalStorage.clear();
expect(mockRemoveItem).toBeCalledTimes(2);
expect(mockRemoveItem).toHaveBeenNthCalledWith(1, 'foo');
expect(mockRemoveItem).toHaveBeenNthCalledWith(2, 'quz');
});
});
});
});
119 changes: 119 additions & 0 deletions packages/core/__tests__/UniversalStorage-node-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @jest-environment node
*/
const mockCookiesRemove = jest.fn();
const mockCookiesSet = jest.fn();
const mockCookiesGet = jest.fn();
const mockCookiesGetAll = jest.fn();
const mockCookies = jest.fn().mockImplementation(function () {
return {
remove: mockCookiesRemove,
set: mockCookiesSet,
get: mockCookiesGet,
getAll: mockCookiesGetAll,
};
});
jest.mock('universal-cookie', () => ({
default: mockCookies,
}));
jest.mock('../src/JS', () => ({
browserOrNode: jest.fn().mockReturnValue({
isBrowser: false,
isNode: true,
}),
}));

import { UniversalStorage } from '../src/UniversalStorage';

describe(UniversalStorage.name, () => {
describe('on server side', () => {
let universalStorage: UniversalStorage;
let mockStore = {};

beforeEach(() => {
jest.clearAllMocks();
mockCookiesGetAll.mockReturnValue({});
universalStorage = new UniversalStorage();
mockStore = {};
universalStorage.store = mockStore;
});

describe('constructor', () => {
test('initiate store with cookies', () => {
mockCookiesGetAll.mockReturnValue({ bar: 'barz' });
const universalStorage = new UniversalStorage();
expect(universalStorage.store).toMatchObject({ bar: 'barz' });
});
});

describe('setItem', () => {
test('sets item in local storage', () => {
universalStorage.setItem('foo', 'bar');
expect(universalStorage.store).toMatchObject({ foo: 'bar' });
});

// Unlike in browser, cookies on server side are always secure.
test.each([
['LastAuthUser'],
['accessToken'],
['refreshToken'],
['idToken'],
])('sets session token %s to secure permenent cookie', tokenType => {
const key = `ProviderName.someid.someid.${tokenType}`;
const value = `${tokenType}-value`;
universalStorage.setItem(key, value);
expect(mockCookiesSet).toBeCalledWith(
key,
value,
expect.objectContaining({ path: '/', sameSite: true, secure: true })
);
expect(mockCookiesSet.mock.calls.length).toBe(1);
const expiresParam = mockCookiesSet.mock.calls[0]?.[2]?.expires;
expect(expiresParam).toBeInstanceOf(Date);
expect(expiresParam.valueOf()).toBeGreaterThan(Date.now());
});
});

describe('getItem', () => {
test('returns corresponding item from store', () => {
universalStorage.store['foo'] = 'bar';
expect(universalStorage.getItem('foo')).toBe('bar');
});
});

describe('key', () => {
test('returns key from store in insertion order', () => {
universalStorage.store['foo'] = 'bar';
universalStorage.store['baz'] = 'qux';
expect(universalStorage.key(0)).toBe('foo');
expect(universalStorage.key(1)).toBe('baz');
});
});

describe('removeItem', () => {
test('should remove item from local store', () => {
universalStorage.setItem('foo', 'bar');
universalStorage.removeItem('foo');
expect(Object.keys(universalStorage.store).length).toBe(0);
});

test('should remove item from cookies', () => {
universalStorage.setItem('foo', 'bar');
universalStorage.removeItem('foo');
expect(mockCookiesRemove).toBeCalledWith('foo', { path: '/' });
});
});

describe('clear', () => {
test('removes all items in store', () => {
const mockRemoveItem = spyOn(universalStorage, 'removeItem');
universalStorage.setItem('foo', 'bar');
universalStorage.setItem('quz', 'baz');
universalStorage.clear();
expect(mockRemoveItem).toBeCalledTimes(2);
expect(mockRemoveItem).toHaveBeenNthCalledWith(1, 'foo');
expect(mockRemoveItem).toHaveBeenNthCalledWith(2, 'quz');
});
});
});
});
3 changes: 2 additions & 1 deletion packages/core/src/UniversalStorage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ export class UniversalStorage implements Storage {
// `httpOnly` cannot be set via JavaScript: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#JavaScript_access_using_Document.cookie
sameSite: true,
// Allow unsecure requests to http://localhost:3000/ when in development.
secure: window.location.hostname === 'localhost' ? false : true,
secure:
isBrowser && window.location.hostname === 'localhost' ? false : true,
});
}
}

0 comments on commit 9713a81

Please sign in to comment.