JSS and Sitecore Commerce 10

Cet article en francais

In a very good serie of articles last year, Jonne Kats and Joost Meijles described and developped a small project to show how to create a JSS application on top of Sitecore Commerce 9.

Unfortunately, when I tried to install this project on top of Sitecore 10, I realised that a lot of changes were necessary in order to achieve the same result. So this article will be about modifying the code Jonne and Joost made public so it will also work on Sitecore 10.

First of all, I decided to install Sitecore 10 and Sitecore Commerce on my environment, without Docker, because I wanted to have total control of all the config files. My solution could be dockerized, but this is not the point of this article. I kept Jonne and Joosts design and code base, so all credit goes to them, I just took care of the adjustments for Sitecore 10.

The project is shared in two parts:

  • The headless part, under the folder “app”. This part results in a serie of Html, Javascript, CSS and media files, that you deploy to the folder app/dist (disconnected mode) or $sitecore_root/dist (connected mode). Usually, I open this part in Visual Code
  • The server part, under the folder “server”, which is a Visual Studio project and consists of
    • A proxy project, Gateway and Poortwachter, so that you don’t call Sitecore Commerce api directly from the client-side JS, as recommended. This projet will be run as a stand-alone executable, and will be accessible from the client with https://localhost:5555
    • A Sitecore project, JSS.Commerce, with some ContentResolvers for our project and apis. This code will be deployed to your Sitecore instance.
    • A Serialization project, containing all the Sitecore items necessary. Jonne and Joost used Unicorn to serialized, I changed this part in order to use the brand new Sitecore Content Serialization (SCS).

In Jonne and Joost’s serie of articles, they explained very well the whole architecture of the project and some good choice they made.

Here are the different changes I needed to make this project work with Sitecore Commerce 10

Sitecore 10 dependancies

Obviously, I had to update all the different dependancies, in order to target Sitecore 10 and Sitecore Commerce 10. This could be tricky to update dependancies after dependancies, so I started from scratch, with Sitecore Commerce 10 and JSS 10 projects, and then added their code to these new projects.

JSS

The new version of JSS had one main code-breaking change concerning the internatinalization

Old code:

import { t } from 'i18next';
(...)
{t('cart-line-remove')}

New code:

import i18n from 'i18next';
(...)
{i18n.t('cart-line-remove')}

This change has to be done every time the i18next package is used.

Proxy project

The biggest change was that Sitecore Commerce 10 doesn’t accept anymore the apis to use a client certificate for the authentication between Sitecore Commerce Connect and the Commerce engine. The original project used a request header X-CommerceEngineCert in the proxy to be able to talk to Sitecore Commerce 9.

Instead, we had to use a Sitecore Commerce Token to be able to authenticate. This token is saved in the memory cache during the time it is valid and is different for every user of the application. I took the user’s cartId (which is the authentication token between the app and the proxy) as a key to the memory cache. In my solution, I add this authorization header when the Request is being changed with all the other necessary modifications.

TokenBuilder tokenBuilder = TokenBuilder.Instance;
string XCToken = await tokenBuilder.GetXCAccessToken(route.TokenUserId());
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", XCToken);

I needed to have a new IdentityServer client in order to connect to Sitecore Commerce Connect via the IdentityServer. The change was done in $IdentityServer_root/config/production/Sitecore.Commerce.IdentityServer.Host.xml

<JSSClient>
          <ClientId>JSSClient</ClientId>
          <ClientName>JSS client</ClientName>
          <AccessTokenType>0</AccessTokenType>
          <AllowOfflineAccess>true</AllowOfflineAccess>
          <AlwaysIncludeUserClaimsInIdToken>false</AlwaysIncludeUserClaimsInIdToken>
          <AccessTokenLifetimeInSeconds>120</AccessTokenLifetimeInSeconds>
          <IdentityTokenLifetimeInSeconds>120</IdentityTokenLifetimeInSeconds>
          <AllowAccessTokensViaBrowser>true</AllowAccessTokensViaBrowser>
          <RequireConsent>false</RequireConsent>
          <RequireClientSecret>false</RequireClientSecret>
          <AllowedGrantTypes>
            <AllowedGrantType1>client_credentials</AllowedGrantType1>
            <AllowedGrantType2>hybrid</AllowedGrantType2>
			<AllowedGrantType3>password</AllowedGrantType3>
          </AllowedGrantTypes>
          <RedirectUris>
            <RedirectUri1>{AllowedCorsOrigin}/signin-oidc</RedirectUri1>
			<RedirectUri2>{AllowedCorsOrigin}/oauth2/callback</RedirectUri2>
          </RedirectUris>
          <PostLogoutRedirectUris>
            <PostLogoutRedirectUri1>{AllowedCorsOrigin}/signout-callback-oidc</PostLogoutRedirectUri1>
			<PostLogoutRedirectUri2>{AllowedCorsOrigin}</PostLogoutRedirectUri2>
          </PostLogoutRedirectUris>
          <AllowedCorsOrigins>
            <AllowedCorsOriginsGroup1>https://localhost:5555|https://sc10sc.dev.local|http://sc10sc.dev.local</AllowedCorsOriginsGroup1>
          </AllowedCorsOrigins>
          <AllowedScopes>
            <AllowedScope1>openid</AllowedScope1>
            <AllowedScope2>sitecore.profile</AllowedScope2>
            <AllowedScope3>sitecore.profile.api</AllowedScope3>
			<AllowedScope4>EngineAPI</AllowedScope4>
			<AllowedScope5>postman_api</AllowedScope5>
          </AllowedScopes>
          <UpdateAccessTokenClaimsOnRefresh>true</UpdateAccessTokenClaimsOnRefresh>
		  <ClientSecrets>
            <ClientSecret1>U0hZx8H1Gxe4KB0x3LOlACA2aShpIDxfvlQOi10lnX4=</ClientSecret1>
          </ClientSecrets>
        </JSSClient>

Be sure to have the correct value for your own environment in AllowedCorsOriginsGroup1 !

I also changed the code in startup.cs to use the configuration variables instead of hard coding the Commerce hostname.

Also in Startup.cs, most of the api calls to the Sitecore Commerce were using the Http method PUT, which is wrong in the version 10. Instead, we need to use the Http method POST (row 5)

c.ReRoute("/carts/me/addline")
                      .Method(HttpMethod.Post)
                      .To($"{hostRoot}/AddCartLine()")
                      .TransformBody((_, httpContext, bytes) => SetCartIdInBody(httpContext, bytes))
                      .Method(HttpMethod.Post)
                      .AuthenticateWith("test");

As a consequence, in the JSS app project, I also needed to create a new post function and call it instead of the old put function.

In src/lib/commerce/CartProvider.js

async function addCartLine(token, line) {
    await commerceRequest.post('api/carts/me/addline', token, JSON.stringify(line));
    trackCartlineAdded(line);
}

In src/lib/commerce/request.js

export async function post(uri, token, body) {
    let res = await fetch(`${gatewayUrl}/${uri}`, {
        method: 'post', 
        headers: {
            'Authorization' : `Bearer ${token}`, 
            'Content-Type' : 'application/json'
        }, 
        body: body
    });

    return handleErrors(res);
}

Sitecore Content Serialization (SCS)

As SCS is now the norm for Sitecore item serialization, it was natural to replace Unicorn and use SCS instead.

In order to deserialize the items you’ll need for this project in your Sitecore Commerce environment, open a Powershell console, install the necessary tools and connect to Sitecore CM. Then you can push the items to your sitecore instance.

cd server\SCSerialization
dotnet new tool-manifest
dotnet tool install Sitecore.CLI --add-source https://sitecore.myget.org/F/sc-packages/api/v3/index.json
dotnet sitecore login --authority https://sc10identityserver.dev.local --cm https://sc10sc.dev.local --allow-write true
dotnet sitecore ser push

When I first tried this, I had an environment with the Habitat storefront. This project uses the same catalog, and the serialization failed because one catalog can only point to one sitecore site only. So I needed to unselect the Habitat catalog in Storefront in order to make the serialization of the Sitecore content items work.

Install, build and deploy the JSS app

cd app
npm install
npm run build-css
npm run build
remove-Item .\dist\app\* -Recurse -Force
copy-item -Path .\build\* -Destination .\dist\app -Recurse
remove-Item C:\inetpub\wwwroot\sc10sc.dev.local\dist\app\* -Recurse -Force
copy-item -Path .\build\* -Destination C:\inetpub\wwwroot\sc10sc.dev.local\dist\app -Recurse

Start the proxy

cd server/Gateway
dotnet watch run

Now you can open a browser to https://app.dev.local (or whatever you have changed your local URL to) and you can surf this commerce app.

Where is the code ?

The code is here. Remember that 80% of this code comes from Jonne Kats and Joost Meijles‘s project, I just made some adjustments in order to create a proof of concept for Sitecore Commerce 10.

Some configuration to change to match your local environment are in those files

server/Gateway/app.cong
server/Gateway/ocelot.json
app/scjssconfig.json
app/package.json
app/sitecore/config/app.config

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: