Skip to content

Commit

Permalink
Merge branch 'develop' into feature/template-extensibility
Browse files Browse the repository at this point in the history
* develop:
  Allow query hook parameters to be `null`. (#1046)
  Implement `updateCustomerPassword` as no-op. (#1031)
  Update Retail React App Page Designer integration README (#1041)
  BUG: Changed type of the phone number field to bring up numberic keyboard on mobile devices - W-9871940 (#1016)
  Move the MRT reference app to the SDKs, so that we can verify eg. Node support (#966)
  Update `develop` with `release-v2.7.0` (#1033)
  Remove unused dependencies. (#1020)
  Make some style decisions on padding (#1023)
  Update CHANGELOG.md (#1021)
  Support product-sets in retail-react-app (#1019)
  Simplify hooks logic (#959)
  Fix NPM dependencies (#1012)

# Conflicts:
#	lerna.json
#	package-lock.json
#	package.json
#	packages/commerce-sdk-react/CHANGELOG.md
#	packages/commerce-sdk-react/package-lock.json
#	packages/commerce-sdk-react/package.json
#	packages/internal-lib-build/package-lock.json
#	packages/internal-lib-build/package.json
#	packages/pwa-kit-create-app/package-lock.json
#	packages/pwa-kit-create-app/package.json
#	packages/pwa-kit-dev/CHANGELOG.md
#	packages/pwa-kit-dev/package-lock.json
#	packages/pwa-kit-dev/package.json
#	packages/pwa-kit-dev/src/configs/webpack/config.js
#	packages/pwa-kit-dev/src/ssr/server/build-dev-server.js
#	packages/pwa-kit-react-sdk/CHANGELOG.md
#	packages/pwa-kit-react-sdk/package-lock.json
#	packages/pwa-kit-react-sdk/package.json
#	packages/pwa-kit-runtime/CHANGELOG.md
#	packages/pwa-kit-runtime/package-lock.json
#	packages/pwa-kit-runtime/package.json
#	packages/template-express-minimal/package-lock.json
#	packages/template-express-minimal/package.json
#	packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js
#	packages/template-retail-react-app/app/partials/product-view/index.jsx
#	packages/template-retail-react-app/package-lock.json
#	packages/template-retail-react-app/package.json
#	packages/template-typescript-minimal/package-lock.json
#	packages/template-typescript-minimal/package.json
#	packages/test-commerce-sdk-react/package-lock.json
#	packages/test-commerce-sdk-react/package.json
  • Loading branch information
bfeister committed Mar 9, 2023
2 parents bde07f6 + fc789fe commit 669d1c7
Show file tree
Hide file tree
Showing 190 changed files with 11,715 additions and 4,024 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
uses: "./.github/actions/smoke_tests"

- name: Create MRT credentials file
if: env.IS_NOT_FORK == 'true' && env.IS_LATEST_NPM == 'true' && env.DEVELOP == 'true'
if: env.IS_NOT_FORK == 'true' && env.IS_LATEST_NPM == 'true' && ( env.DEVELOP == 'true' || env.RELEASE == 'true' )
uses: "./.github/actions/create_mrt"
with:
mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
"bump-version": "node ./scripts/bump-version.js",
"local-npm": "node packages/pwa-kit-create-app/scripts/create-mobify-app-dev.js --outputDir local-npm"
}
}
}
8 changes: 2 additions & 6 deletions packages/commerce-sdk-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
## v2.8.0-dev.0 (Feb 16, 2023)
## v2.8.0-dev.0 (Feb 16, 2023)
## v2.8.0-dev.0 (Feb 15, 2023)
## v2.8.0-dev (Feb 15, 2023)
## v2.8.0-dev (Feb 15, 2023)
## v2.7.0-dev (Jan 25, 2023)
## v2.8.0-dev (Mar 03, 2023)
## v2.7.0 (Mar 03, 2023)
- Add Page/Region/Component components for shopper experience/page designer page rendering [#963](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/963)
- Namespace `Auth` storage keys with site identifier to allow multi-site support [#911](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/911)
- Add Shopper Experience `usePage` and `usePages` hooks[#958](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/958)
Expand Down
4 changes: 3 additions & 1 deletion packages/commerce-sdk-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@
"@types/react-dom": "^17.0.2",
"@types/react-helmet": "^6.1.6",
"cross-env": "^5.2.0",
"internal-lib-build": "^2.7.0-dev",
"internal-lib-build": "^2.8.0-dev",
"jsonwebtoken": "^8.5.1",
"nock": "^13.2.9",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"semver": "^7.3.8",
"shelljs": "^0.8.5",
"typedoc": "^0.23.5",
"typescript": "^4.7.2",
"watch": "^1.0.2"
Expand Down
1 change: 0 additions & 1 deletion packages/commerce-sdk-react/setup-jest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import '@testing-library/jest-dom'
import {configure} from '@testing-library/dom'
import nock from 'nock'

class LocalStorageMock {
Expand Down
23 changes: 16 additions & 7 deletions packages/commerce-sdk-react/src/auth/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* 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 Auth from './'
import Auth, {AuthData} from './'
import jwt from 'jsonwebtoken'
import {helpers} from 'commerce-sdk-isomorphic'
import * as utils from '../utils'
Expand Down Expand Up @@ -39,6 +39,9 @@ jest.mock('../utils', () => ({
onClient: () => true
}))

/** The auth data we store has a slightly different shape than what we use. */
type StoredAuthData = Omit<AuthData, 'refresh_token'> & {refresh_token_guest?: string}

const config = {
clientId: 'clientId',
organizationId: 'organizationId',
Expand Down Expand Up @@ -85,7 +88,7 @@ describe('Auth', () => {
test('this.data returns the storage value', () => {
const auth = new Auth(config)

const sample = {
const sample: StoredAuthData = {
refresh_token_guest: 'refresh_token_guest',
access_token: 'access_token',
customer_id: 'customer_id',
Expand All @@ -97,7 +100,9 @@ describe('Auth', () => {
usid: 'usid',
customer_type: 'guest'
}
const {refresh_token_guest, ...result} = {...sample, refresh_token: 'refresh_token_guest'}
// Convert stored format to exposed format
const result = {...sample, refresh_token: 'refresh_token_guest'}
delete result.refresh_token_guest

Object.keys(sample).forEach((key) => {
// @ts-expect-error private method
Expand Down Expand Up @@ -161,7 +166,7 @@ describe('Auth', () => {
test('ready - re-use valid access token', () => {
const auth = new Auth(config)

const data = {
const data: StoredAuthData = {
refresh_token_guest: 'refresh_token_guest',
access_token: jwt.sign({exp: Math.floor(Date.now() / 1000) + 1000}, 'secret'),
customer_id: 'customer_id',
Expand All @@ -173,7 +178,9 @@ describe('Auth', () => {
usid: 'usid',
customer_type: 'guest'
}
const {refresh_token_guest, ...result} = {...data, refresh_token: 'refresh_token_guest'}
// Convert stored format to exposed format
const result = {...data, refresh_token: 'refresh_token_guest'}
delete result.refresh_token_guest

Object.keys(data).forEach((key) => {
// @ts-expect-error private method
Expand All @@ -192,7 +199,7 @@ describe('Auth', () => {
test('ready - use refresh token when access token is expired', async () => {
const auth = new Auth(config)

const data = {
const data: StoredAuthData = {
refresh_token_guest: 'refresh_token_guest',
access_token: jwt.sign({exp: Math.floor(Date.now() / 1000) - 1000}, 'secret'),
customer_id: 'customer_id',
Expand All @@ -204,7 +211,9 @@ describe('Auth', () => {
usid: 'usid',
customer_type: 'guest'
}
const {refresh_token_guest, ...result} = {...data, refresh_token: 'refresh_token_guest'}
// Convert stored format to exposed format
const result = {...data, refresh_token: 'refresh_token_guest'}
delete result.refresh_token_guest

Object.keys(data).forEach((key) => {
// @ts-expect-error private method
Expand Down
77 changes: 40 additions & 37 deletions packages/commerce-sdk-react/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Salesforce, Inc.
* 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
Expand All @@ -12,11 +12,12 @@ import {
ShopperCustomersTypes
} from 'commerce-sdk-isomorphic'
import jwtDecode from 'jwt-decode'
import {ApiClientConfigParams, Argument} from '../hooks/types'
import {ApiClientConfigParams, Prettify, RemoveStringIndex} from '../hooks/types'
import {BaseStorage, LocalStorage, CookieStorage, MemoryStorage, StorageType} from './storage'
import {CustomerType} from '../hooks/useCustomerType'
import {onClient} from '../utils'

type TokenResponse = ShopperLoginTypes.TokenResponse
type Helpers = typeof helpers
interface AuthConfig extends ApiClientConfigParams {
redirectURI: string
Expand All @@ -30,24 +31,25 @@ interface JWTHeaders {
iat: number
}

// this type is slightly different from ShopperLoginTypes.TokenResponse, reasons:
// 1. TokenResponse is too generic (with & {[key:string]: any}), we need a more
// restrictive type to make sure type safe
// 2. The refresh tokens are stored separately for guest and registered user. Instead
// of refresh_token, we have refresh_token_guest and refresh_token_registered
/**
* The extended field is not from api response, we manually store the auth type,
* so we don't need to make another API call when we already have the data.
* Plus, the getCustomer endpoint only works for registered user, it returns a 404 for a guest user,
* and it's not easy to grab this info in user land, so we add it into the Auth object, and expose it via a hook
*/
export type AuthData = Prettify<
RemoveStringIndex<TokenResponse> & {
customer_type: CustomerType
idp_access_token: string
}
>

/** A shopper could be guest or registered, so we store the refresh tokens individually. */
type AuthDataKeys =
| 'access_token'
| 'customer_id'
| 'enc_user_id'
| 'expires_in'
| 'id_token'
| 'idp_access_token'
| Exclude<keyof AuthData, 'refresh_token'>
| 'refresh_token_guest'
| 'refresh_token_registered'
| 'token_type'
| 'usid'
| 'site_id'
| 'customer_type'

type AuthDataMap = Record<
AuthDataKeys,
{
Expand All @@ -57,16 +59,6 @@ type AuthDataMap = Record<
}
>

/**
* The extended field is not from api response, we manually store the auth type,
* so we don't need to make another API call when we already have the data.
* Plus, the getCustomer endpoint only works for registered user, it returns a 404 for a guest user,
* and it's not easy to grab this info in user land, so we add it into the Auth object, and expose it via a hook
*/
type AuthData = ShopperLoginTypes.TokenResponse & {
customer_type: CustomerType
}

/**
* A map of the data that this auth module stores. This maps the name of the property to
* the storage type and the key when stored in that storage. You can also pass in a "callback"
Expand Down Expand Up @@ -119,10 +111,6 @@ const DATA_MAP: AuthDataMap = {
store.delete('cc-nx-g')
}
},
site_id: {
storageType: 'cookie',
key: 'cc-site-id'
},
customer_type: {
storageType: 'local',
key: 'customer_type'
Expand All @@ -141,7 +129,7 @@ class Auth {
private client: ShopperLogin<ApiClientConfigParams>
private shopperCustomersClient: ShopperCustomers<ApiClientConfigParams>
private redirectURI: string
private pendingToken: Promise<ShopperLoginTypes.TokenResponse> | undefined
private pendingToken: Promise<TokenResponse> | undefined
private REFRESH_TOKEN_EXPIRATION_DAYS = 90
private stores: Record<StorageType, BaseStorage>
private fetchedToken: string
Expand Down Expand Up @@ -208,9 +196,10 @@ class Auth {
}

private clearStorage() {
Object.keys(DATA_MAP).forEach((keyName) => {
type Key = keyof AuthDataMap
const {key, storageType} = DATA_MAP[keyName as Key]
// Type assertion because Object.keys is silly and limited :(
const keys = Object.keys(DATA_MAP) as AuthDataKeys[]
keys.forEach((keyName) => {
const {key, storageType} = DATA_MAP[keyName]
const store = this.stores[storageType]
store.delete(key)
})
Expand Down Expand Up @@ -248,7 +237,7 @@ class Auth {
* This method stores the TokenResponse object retrived from SLAS, and
* store the data in storage.
*/
private handleTokenResponse(res: ShopperLoginTypes.TokenResponse, isGuest: boolean) {
private handleTokenResponse(res: TokenResponse, isGuest: boolean) {
this.set('access_token', res.access_token)
this.set('customer_id', res.customer_id)
this.set('enc_user_id', res.enc_user_id)
Expand All @@ -272,7 +261,7 @@ class Auth {
*
* @Internal
*/
async queueRequest(fn: () => Promise<ShopperLoginTypes.TokenResponse>, isGuest: boolean) {
async queueRequest(fn: () => Promise<TokenResponse>, isGuest: boolean) {
const queue = this.pendingToken ?? Promise.resolve()
this.pendingToken = queue.then(async () => {
const token = await fn()
Expand Down Expand Up @@ -331,6 +320,20 @@ class Auth {
)
}

/**
* Creates a function that only executes after a session is initialized.
* @param fn Function that needs to wait until the session is initialized.
* @returns Wrapped function
*/
whenReady<Args extends unknown[], Data>(
fn: (...args: Args) => Promise<Data>
): (...args: Args) => Promise<Data> {
return async (...args) => {
await this.ready()
return await fn(...args)
}
}

/**
* A wrapper method for commerce-sdk-isomorphic helper: loginGuestUser.
*
Expand Down
Loading

0 comments on commit 669d1c7

Please sign in to comment.