Skip to content

Commit

Permalink
@W-14758629@ SLAS Private Client Support (#1722)
Browse files Browse the repository at this point in the history
* add support private slas flow by using place holder value

* revert ssr js in template

* revert code in template

* remove console

* minor fix

* add test for refreshToken

* allow secret passed in as prop

* add silenceWarning prop and readme

* add changelog

* PR feedback

* PR feedback

* PR feedback

* PR feedback

* PR feedback

* lint

* @W-14758629@ - Add a custom proxy handler for SLAS private clients (#1664)

* Allow objects in commerceapi path

* Add custom endpoint for SLAS. Works on public client

* Get private client working

* Remove console log

* A more generic header replacement

* Slas credentials from env vars

* Add exclusions for some endpoints

* Custom middleware in ssr.js

* Add dependency

* Private SLAS client handler in runtime

* Update commerce-sdk-react with private client endpoint

* Cleanup

* Public client by default

* more cleanup

* Fix existing tests

* Runtime tests- WIP

* WIP test

* Apply some PR feedback

* Small refactor

* Working tests

* add getSlasEndpoint test

* Lint

* Change endpoint path and other minor adjustments

* Don't start server if env var not set

* Fix flaky test

* Add option for customizing more endpoints with private client

* Rename client secret env var

* Bump up a test timeout

* @W-14810956@ Update generator with options for slas private client (#1683)

* Allow objects in commerceapi path

* Add custom endpoint for SLAS. Works on public client

* Get private client working

* Remove console log

* add private slas/private question

* add private slas/private question

* A more generic header replacement

* Slas credentials from env vars

* Add exclusions for some endpoints

* add private slas/private question

* Custom middleware in ssr.js

* Add dependency

* Private SLAS client handler in runtime

* Update commerce-sdk-react with private client endpoint

* Cleanup

* Public client by default

* more cleanup

* Fix existing tests

* Runtime tests- WIP

* WIP test

* Apply some PR feedback

* Small refactor

* Working tests

* add getSlasEndpoint test

* Lint

* Change endpoint path and other minor adjustments

* Don't start server if env var not set

* Fix flaky test

* Add option for customizing more endpoints with private client

* Rename client secret env var

* Bump up a test timeout

* Update generator

* Lint

* Fix lint in generated projects

* Add comments on the original app config file

* Add templates for non-extensible projects

* More detailed developer note

---------

Co-authored-by: Alex Vuong <[email protected]>

* @W-14758629@ Private client handle missing env var (#1679)

* Allow objects in commerceapi path

* Add custom endpoint for SLAS. Works on public client

* Get private client working

* Remove console log

* add private slas/private question

* add private slas/private question

* A more generic header replacement

* Slas credentials from env vars

* Add exclusions for some endpoints

* add private slas/private question

* Custom middleware in ssr.js

* Add dependency

* Private SLAS client handler in runtime

* Update commerce-sdk-react with private client endpoint

* Cleanup

* Public client by default

* more cleanup

* Fix existing tests

* Runtime tests- WIP

* WIP test

* Apply some PR feedback

* Small refactor

* Working tests

* add getSlasEndpoint test

* Lint

* Change endpoint path and other minor adjustments

* Don't start server if env var not set

* Fix flaky test

* Add option for customizing more endpoints with private client

* Rename client secret env var

* Bump up a test timeout

* Update generator

* Lint

* Fix lint in generated projects

* Add comments on the original app config file

* Add templates for non-extensible projects

* Improve missing env var handling in remote environments

* More detailed developer note

* Remove brackets

---------

Co-authored-by: Alex Vuong <[email protected]>

* Private SLAS default to yes

* @W-14959704@ Set private client proxy headers (#1690)

* Add header injection to custom proxy

* Refactor to remove duplicate code

* Update private client preset config

* Update e2e workflow to test for private client apps

* Add conditional destructuring

* Add condition to evaluate generator prompts

* Add null checks to cli prompt destructuring

* remove console logs

* Disable npm cache for private clients

* Clear verdaccio storage in e2e test setup

* lerna force publish all packages

* lerna force publish all packages

* lerna force publish all packages

* remove force publish

* Bump versions

* Remove steps to clear verdaccio storage

* Test slack notification for private client

* Add responses for private client to generator test

* Restore notification on schedule

* Run private client generator linearly

* @W-15163165@ Reduce session churn / fix SFRA -> PWA session share on private (#1696)

* Replace SFRA->PWA session handoff logic

* Merge branch 'feature/pwa-kit-slas-private-support' into private-client-hybrid

* Remove console.logs

* Adjust cookie name

* Code refactor

* Return empty string

* Tests

* Rework suffix of cookie chunks

* Update more local storage values on SFRA token handoff

* Test local storage update

* Remove refresh token copies from local store

* Remove refresh token copy references in test

* Code cleanup

* Enable PWA private client via boolean (#1704)

* Replace SFRA->PWA session handoff logic

* Merge branch 'feature/pwa-kit-slas-private-support' into private-client-hybrid

* Remove console.logs

* Adjust cookie name

* Code refactor

* Return empty string

* Tests

* Rework suffix of cookie chunks

* Update more local storage values on SFRA token handoff

* Test local storage update

* Remove refresh token copies from local store

* Remove refresh token copy references in test

* Enable PWA private client via boolean

* Disable private client in template and update comments

* Move placeholder to constants

* Apply feedback

* Update comment

* Update comments

---------

Signed-off-by: vcua-mobify <[email protected]>

* E2E move preset definition to config

* Fix missing preset id

* Remove missing property from preset

* Remove console logs

* Update changelogs

* Add preset for private client hybrid CI

* Update name

---------

Signed-off-by: vcua-mobify <[email protected]>
Co-authored-by: Alex Vuong <[email protected]>
Co-authored-by: Ben Chypak <[email protected]>
Co-authored-by: Jainam Tushar Sheth <[email protected]>
Co-authored-by: Jainam Sheth <[email protected]>
  • Loading branch information
5 people authored Apr 2, 2024
1 parent 8ce2571 commit 83a2db2
Show file tree
Hide file tree
Showing 57 changed files with 1,395 additions and 248 deletions.
93 changes: 93 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,96 @@ jobs:
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run-generator-private-client:
strategy:
fail-fast: false
# Run one matrix env at a time because we need to deploy each app to MRT and run e2e tests there
max-parallel: 1
matrix:
# Run all matrix env at once because we will not deploy demo app to MRT.
node: [16, 18, 20]
npm: [8, 9, 10]
exclude: # node 16 is not compatible with npm 10
- node: 16
npm: 10
runs-on: ubuntu-latest
env:
# The "default" npm is the one that ships with a given version of node.
# For more: https://nodejs.org/en/download/releases/
# (We also use this env var for making sure a step runs once for the current node version)
# Note: For node 18, the default was npm 9 until v18.19.0, when it became npm 10
IS_DEFAULT_NPM: ${{ (matrix.node == 16 && matrix.npm == 8) || (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- name: Update NPM version
if: env.IS_DEFAULT_NPM == 'false'
run: |-
npm install -g npm@${{ matrix.npm }}
- name: Install Monorepo Dependencies
run: |-
# Install node dependencies
node ./scripts/gtime.js monorepo_install npm ci
- name: Generate Retail App Private Client
uses: ./.github/actions/e2e_generate_app
with:
PROJECT_KEY: "retail-app-private-client"

- name: Create MRT credentials file
uses: "./.github/actions/create_mrt"
with:
mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }}
mobify_api_key: ${{ secrets.MOBIFY_CLIENT_API_KEY }}

- name: Push Bundle to MRT
uses: "./.github/actions/push_to_mrt"
with:
CWD: "../generated-projects/retail-app-private-client"
TARGET: e2e-pwa-kit-private
FLAGS: --wait

- name: Set Retail App Private Client Home
run: export RETAIL_APP_HOME=https://scaffold-pwa-e2e-pwa-kit-private.mobify-storefront.com/

- name: Install Playwright Browsers
run: npx playwright install --with-deps

- name: Run Playwright tests
run: npx playwright test

notify-slack-pwa-private-client:
needs: [run-generator-private-client]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Send GitHub Action data to Slack workflow (Generated)
id: slack-success
if: ${{ github.event_name == 'schedule' && needs.run-generator-private-client.result == 'success' }}
uses: slackapi/[email protected]
with:
payload: |
{
"message": "✅ All PWA Kit Private Client E2E tests passed!"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

- name: Send GitHub Action data to Slack workflow (Generated)
id: slack-failure
if: ${{ github.event_name == 'schedule' && needs.run-generator-private-client.result != 'success' }}
uses: slackapi/[email protected]
with:
payload: |
{
"message": "❌ One or more PWA Kit Private Client E2E tests failed! (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
13 changes: 13 additions & 0 deletions e2e/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

module.exports = {
RETAIL_APP_HOME:
process.env.RETAIL_APP_HOME ||
"https://scaffold-pwa-e2e-tests-pwa-kit.mobify-storefront.com",
GENERATED_PROJECTS_DIR: "../generated-projects",
GENERATE_PROJECTS: ["retail-app-demo", "retail-app-ext", "retail-app-no-ext"],
Expand Down Expand Up @@ -40,6 +41,10 @@ module.exports = {
expectedPrompt: /What is your SLAS Client ID?/i,
response: "987fc116-d30c-4537-93cb-c2bd433c3b5a\n",
},
{
expectedPrompt: /Is your SLAS client private?/i,
response: "2\n",
},
{
expectedPrompt: /What is your Site ID in Business Manager?/i,
response: "RefArch\n",
Expand Down Expand Up @@ -76,6 +81,10 @@ module.exports = {
expectedPrompt: /What is your SLAS Client ID?/i,
response: "987fc116-d30c-4537-93cb-c2bd433c3b5a\n",
},
{
expectedPrompt: /Is your SLAS client private?/i,
response: "2\n",
},
{
expectedPrompt: /What is your Site ID in Business Manager?/i,
response: "RefArch\n",
Expand All @@ -91,6 +100,10 @@ module.exports = {
response: "kv7kzm78\n",
},
],
"retail-app-private-client": [],
},
PRESET: {
"retail-app-private-client": "retail-react-app-private-slas-client",
},
EXPECTED_GENERATED_ARTIFACTS: {
"retail-app-demo": [
Expand Down
8 changes: 6 additions & 2 deletions e2e/scripts/execute-shell-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
const { exec } = require("child_process");
const { isPrompt } = require("./utils.js");

const runGeneratorWithResponses = (cmd, cliResponses) => {
const runGeneratorWithResponses = (cmd, cliResponses = []) => {
const child = exec(cmd);
return new Promise((resolve, reject) => {
let { expectedPrompt, response } = cliResponses.shift();
let expectedPrompt, response;
if (cliResponses && cliResponses.length) {
({ expectedPrompt, response } = cliResponses.shift());
}
let isGenratorRunning = false;

child.stdout.on("data", (data) => {
console.log(data);
if (isPrompt(data, /Running the generator/i)) {
Expand Down
9 changes: 8 additions & 1 deletion e2e/scripts/generate-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ const main = async (opts) => {
await mkdirIfNotExists(config.GENERATED_PROJECTS_DIR);
const outputDir = `${config.GENERATED_PROJECTS_DIR}/${project}`;
// TODO: Update script to setup local verdaccio npm repo to allow running 'npx @salesforce/pwa-kit-create-app' to generate apps
const generateAppCommand = `${config.GENERATOR_CMD} ${outputDir}`;
let generateAppCommand = `${config.GENERATOR_CMD} ${outputDir}`;
const preset = config.PRESET[project];

if (preset) {
generateAppCommand = `${config.GENERATOR_CMD} ${outputDir} --preset ${preset}`
}

const stdout = await runGeneratorWithResponses(
generateAppCommand,
config.CLI_RESPONSES[project]
Expand All @@ -44,6 +50,7 @@ program.addArgument(
"retail-app-demo",
"retail-app-ext",
"retail-app-no-ext",
"retail-app-private-client",
])
);

Expand Down
2 changes: 1 addition & 1 deletion e2e/scripts/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const statAsync = promisify(fs.stat);
const mkdirAsync = promisify(fs.mkdir);

const isPrompt = (streamData, expectedText) => {
if (!streamData || !expectedText) return;
if (!streamData || !expectedText) return false;

if (types.isRegExp(expectedText)) {
return streamData.match(expectedText);
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "3.5.0-dev",
"version": "3.5.0-dev.1",
"packages": [
"packages/*"
]
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pwa-kit",
"version": "3.5.0-dev",
"version": "3.5.0-dev.1",
"scripts": {
"bump-version": "node ./scripts/bump-version/index.js",
"bump-version:retail-react-app": "node ./scripts/bump-version/index.js --package=@salesforce/retail-react-app",
Expand Down
5 changes: 5 additions & 0 deletions packages/commerce-sdk-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
## v1.4.0-dev.1 (Mar 22, 2024)
## v3.5.0-dev.1 (Mar 22, 2024)
## v1.4.0-dev.1 (Mar 22, 2024)
## v1.4.0-dev (Jan 22, 2024)

- Add Support for SLAS private flow [#1722](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1722)
- Fix invalid query params warnings and allow custom query [#1655](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1655)
- Fix cannot read properties of undefined (reading 'unshift') [#1689](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1689)
- Add Shopper SEO hook [#1688](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1688)
Expand Down
63 changes: 62 additions & 1 deletion packages/commerce-sdk-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,67 @@ _💡 This section assumes you have read and completed the [Authorization for Sh

To help reduce boilerplate code for managing shopper authentication, by default, this library automatically initializes shopper session and manages the tokens for developers. Currently, the library supports the [Public Client login flow](https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-public-client.html).

Commerce-react-sdk supports both public and private flow of the [Authorization for Shopper APIs](https://developer.salesforce.com/docs/commerce/commerce-api/guide/authorization-for-shopper-apis.html) guide._
You can choose to use either public or private slas to login. By default, public flow is enabled.

#### How private SLAS works
This section assumes you read and understand how [private SLAS](https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-private-client.html) flow works

To enable private slas flow, you need to pass your secret into the CommercerProvider via clientSecret prop.
**Note** You should only use private slas if you know you can secure your secret since commercer-sdk-react runs isomorphically.

```js
// app/components/_app-config/index.jsx

import {CommerceApiProvider} from '@salesforce/commerce-sdk-react'
import {withReactQuery} from '@salesforce/pwa-kit-react-sdk/ssr/universal/components/with-react-query'

const AppConfig = ({children}) => {
return (
<CommerceApiProvider
clientId="12345678-1234-1234-1234-123412341234"
organizationId="f_ecom_aaaa_001"
proxy="localhost:3000/mobify/proxy/api"
redirectURI="localhost:3000/callback"
siteId="RefArch"
shortCode="12345678"
locale="en-US"
currency="USD"
clientSecret="<your-slas-private-secret>"
>
{children}
</CommerceApiProvider>
)
}
```
#### Disable slas private warnings
By default, a warning as below will be displayed on client side to remind developers to always keep their secret safe and secured.
```js
'You are potentially exposing SLAS secret on browser. Make sure to keep it safe and secure!'
```
You can disable this warning by using CommerceProvider prop `silenceWarnings`

```js
const AppConfig = ({children}) => {
return (
<CommerceApiProvider
clientId="12345678-1234-1234-1234-123412341234"
organizationId="f_ecom_aaaa_001"
proxy="localhost:3000/mobify/proxy/api"
redirectURI="localhost:3000/callback"
siteId="RefArch"
shortCode="12345678"
locale="en-US"
currency="USD"
clientSecret="<your-slas-private-secret>"
silenceWarnings={true}
>
{children}
</CommerceApiProvider>
)
}
```

### Shopper Session Initialization

On `CommerceApiProvider` mount, the provider initializes shopper session by initiating the SLAS **Public Client** login flow. The tokens are stored/managed/refreshed by the library.
Expand All @@ -121,7 +182,6 @@ On `CommerceApiProvider` mount, the provider initializes shopper session by init
While the library is fetching/refreshing the access token, the network requests will queue up until there is a valid access token.

### Login helpers

To leverage the managed shopper authentication feature, use the `useAuthHelper` hook for shopper login.

Example:
Expand All @@ -144,6 +204,7 @@ const Example = () => {

You have the option of handling shopper authentication externally, by providing a SLAS access token. This is useful if you plan on using this library in an application where the authentication mechanism is different.


```jsx
const MyComponent = ({children}) => {
return <CommerceApiProvider fetchedToken="xxxxxxxxxxxx">{children}</CommerceApiProvider>
Expand Down
4 changes: 2 additions & 2 deletions packages/commerce-sdk-react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/commerce-sdk-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@salesforce/commerce-sdk-react",
"version": "1.4.0-dev",
"version": "1.4.0-dev.1",
"description": "A library that provides react hooks for fetching data from Commerce Cloud",
"homepage": "https://github.com/SalesforceCommerceCloud/pwa-kit/tree/develop/packages/ecom-react-hooks#readme",
"bugs": {
Expand Down Expand Up @@ -45,7 +45,7 @@
"jwt-decode": "^4.0.0"
},
"devDependencies": {
"@salesforce/pwa-kit-dev": "3.5.0-dev",
"@salesforce/pwa-kit-dev": "3.5.0-dev.1",
"@tanstack/react-query": "^4.28.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
Expand All @@ -59,7 +59,7 @@
"@types/react-helmet": "~6.1.6",
"@types/react-router-dom": "~5.3.3",
"cross-env": "^5.2.1",
"internal-lib-build": "3.5.0-dev",
"internal-lib-build": "3.5.0-dev.1",
"jsonwebtoken": "^9.0.0",
"nock": "^13.3.0",
"nodemon": "^2.0.22",
Expand Down
Loading

0 comments on commit 83a2db2

Please sign in to comment.