diff --git a/packages/commerce-sdk-react/src/auth/index.ts b/packages/commerce-sdk-react/src/auth/index.ts index 671ec326bf..03f20a0ae6 100644 --- a/packages/commerce-sdk-react/src/auth/index.ts +++ b/packages/commerce-sdk-react/src/auth/index.ts @@ -13,9 +13,16 @@ import { } from 'commerce-sdk-isomorphic' import {jwtDecode, JwtPayload} from 'jwt-decode' import {ApiClientConfigParams, Prettify, RemoveStringIndex} from '../hooks/types' -import {BaseStorage, LocalStorage, CookieStorage, MemoryStorage, StorageType} from './storage' +import { + BaseStorage, + LocalStorage, + CookieStorage, + MemoryStorage, + StorageType, + LocalAndCookieStorage +} from './storage' import {CustomerType} from '../hooks/useCustomerType' -import {getParentOrigin, isOriginTrusted, onClient} from '../utils' +import {onClient} from '../utils' type TokenResponse = ShopperLoginTypes.TokenResponse type Helpers = typeof helpers @@ -106,14 +113,14 @@ const DATA_MAP: AuthDataMap = { key: 'token_type' }, refresh_token_guest: { - storageType: isOriginTrusted(getParentOrigin()) ? 'local' : 'cookie', + storageType: 'localandcookie', key: 'cc-nx-g', callback: (store) => { store.delete('cc-nx') } }, refresh_token_registered: { - storageType: isOriginTrusted(getParentOrigin()) ? 'local' : 'cookie', + storageType: 'localandcookie', key: 'cc-nx', callback: (store) => { store.delete('cc-nx-g') @@ -199,6 +206,9 @@ class Auth { this.stores = { cookie: onClient() ? new CookieStorage(options) : new MemoryStorage(options), local: onClient() ? new LocalStorage(options) : new MemoryStorage(options), + localandcookie: onClient() + ? new LocalAndCookieStorage(options) + : new MemoryStorage(options), memory: new MemoryStorage(options) } diff --git a/packages/commerce-sdk-react/src/auth/storage/index.ts b/packages/commerce-sdk-react/src/auth/storage/index.ts index f45630408e..9a8465a835 100644 --- a/packages/commerce-sdk-react/src/auth/storage/index.ts +++ b/packages/commerce-sdk-react/src/auth/storage/index.ts @@ -5,8 +5,9 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -export type StorageType = 'cookie' | 'local' | 'memory' +export type StorageType = 'cookie' | 'local' | 'localandcookie' | 'memory' export * from './base' export * from './cookie' export * from './local' +export * from './localandcookie' export * from './memory' diff --git a/packages/commerce-sdk-react/src/auth/storage/localandcookie.ts b/packages/commerce-sdk-react/src/auth/storage/localandcookie.ts new file mode 100644 index 0000000000..da06879c1a --- /dev/null +++ b/packages/commerce-sdk-react/src/auth/storage/localandcookie.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import {BaseStorage, BaseStorageOptions} from './base' +import Cookies from 'js-cookie' +import {getDefaultCookieAttributes} from '../../utils' + +/** + * A normalized implementation for LocalStorage. It implements the BaseStorage interface + * which allows developers to easily switch between Cookie, LocalStorage, Memory store + * or a customized storage. This class is mainly used for commerce-sdk-react library + * to store authentication tokens. + */ +export class LocalAndCookieStorage extends BaseStorage { + constructor(options?: BaseStorageOptions) { + // TODO: Use detectLocalStorageAvailable when app can better handle clients without storage + if (typeof window === 'undefined' || typeof document === 'undefined') { + throw new Error('LocalAndCookieStorage is not available on the current environment.') + } + super(options) + } + set(key: string, value: string) { + const oldValue = this.get(key) + const suffixedKey = this.getSuffixedKey(key) + window.localStorage.setItem(suffixedKey, value) + Cookies.set(suffixedKey, value, { + ...getDefaultCookieAttributes() + }) + // Changes to localStorage automatically dispatch a storage event in every tab where a site + // is loaded, *except* the original tab that made the change. To allow our `useLocalStorage` + // hook to work in the originating tab, we must dispatch a copy of the event. This event is + // only seen by the originating tab. A key difference with this event is that `isTrusted` is + // false, but that should not impact our use case. + const event = new StorageEvent('storage', { + key: suffixedKey, + oldValue: oldValue, + newValue: value + }) + window.dispatchEvent(event) + } + get(key: string) { + const suffixedKey = this.getSuffixedKey(key) + return window.localStorage.getItem(suffixedKey) || '' + } + delete(key: string) { + const suffixedKey = this.getSuffixedKey(key) + const oldValue = this.get(suffixedKey) + window.localStorage.removeItem(suffixedKey) + Cookies.remove(suffixedKey, { + ...getDefaultCookieAttributes() + }) + // Changes to localStorage automatically dispatch a storage event in every tab where a site + // is loaded, *except* the original tab that made the change. To allow our `useLocalStorage` + // hook to work in the originating tab, we must dispatch a copy of the event. This event is + // only seen by the originating tab. A key difference with this event is that `isTrusted` is + // false, but that should not impact our use case. + const event = new StorageEvent('storage', { + key: suffixedKey, + oldValue: oldValue, + newValue: null + }) + window.dispatchEvent(event) + } +}