Cookies do not work on cross-domain requests over AJAX within the browser, immediately breaking the default cart management model using Drupal’s session storage. You can perform POST operations to add products to the cart, and it'll work. But you will get a new cart each time. In fact, you can then browse the backend site and even checkout with the latest order.
I am proposing the Cart API module provides an alternative cart session implementation that supports working with the Cart API in a fully decoupled situation. As it stands this module only works in progressively decoupled scenarios or when cross-domain cookies can work (ie: shared root domain, or something like that.)
Comment | File | Size | Author |
---|---|---|---|
#26 | 3036585-26.patch | 12.43 KB | mglaman |
#25 | 3036585-25.patch | 10.38 KB | mglaman |
#24 | 3036585-24.patch | 18.47 KB | mglaman |
#21 | 3036585-21.patch | 9.74 KB | mglaman |
#20 | 3036585-20.patch | 9.61 KB | mglaman |
Comments
Comment #2
mglamanThis needs some testing. To enable, alter your site's
sites/default/services.yml
. Enable the new service:Rebuild your cache.
I was able to use Postman to add an item to my cart and retrieve it without cookies or a session. Here I used a token called
fruitloops
. Normally it'd be a token, like the one from/session/token
Add to cart
Carts collection
Response (after two add to carts)
Comment #3
mglamanThe only problem is this: we do not have a checkout API, and you couldn't redirect to the checkout form and proceed with the order unless you setup a route to "claim" an order. That's also kind of hard because \Drupal\commerce_checkout\Controller\CheckoutController::checkAccess dips into the Cart Session class. The header will not always be present.
We could pass the token as a query parameter, but that would disappear after the first request. What would be nice is if there was a way to support the PHP session based storage and a token-based one. This way you could have a fully decoupled frontend that linked to your backend (directly, iframe, etc) for checkout.
The private.tempstore requires a session
\Drupal\commerce_cart_api\TokenCartSession could extend or decorate the default \Drupal\commerce_cart\CartSession. Each method could ensure the session is populated.
The session cookie is not transmitted during XHR requests, but it is still respected on the server. This might allow for fully decoupled cart management but still enable using the coupled checkout form.
Comment #4
mglamanThanks to gabesullice who pointed me to https://tools.ietf.org/html/rfc6648. The header should be renamed something like
Commerce-Cart-Token
.Comment #5
mglamanOkay, changed the service parameter to:
This now decorates the core service. Should allow working with fully decoupled and preserve progressively decoupled if enabled. Also the idea of cross-origin domain requests but a single checkout form.
Comment #7
mglamanThis still does not work, as the private tempstore relies on a session, which cannot be tracked. Add to cart is still working.. but fetching the carts via the token does not work.
When directly fetching the cart resource in browser I can get
But over AJAX it is empty.
Comment #8
mglamanUsing the shared tempstore did the trick! All endpoints were cached behind page_cache or internal_page_cache and busted appropriately. I was then able to click the link to enter checkout, such as
http://commerce2x-api.ddev.local/checkout/36
and purchase my products.Comment #10
mglamanThere is one problem: when a new cart is created, the cart collection cache does not bust. This is only noticeable when page_cache is active, which normally is not present when a session is activated.
When setting the following to disable the page_cache, everything works fine.
The cart collection's cache information is:
page_cache does not respect contexts. Whatever tag is invalidated when an order is updated needs to be attached to the response. However, that would make it impossible to cache that endpoint.
Entities by default have the following cache tag:
ENTITY_TYPE_ID:ENTITY_ID
Comment #11
mglamanThis adds a page_cache_response_policy service to deny the cart collection from being cache under page_cache.
Comment #12
mglamanWhen testing this using a decoupled site:
API_DOMAIN/checkout/{order_id}
: OK, can checkout.API_DOMAIN/checkout/{order_id}
: OK, can checkout.API_DOMAIN/checkout/{order_id}
: FAILURE, access denied, cannot checkout.API_DOMAIN/checkout/{order_id}
: FAILURE, access denied, cannot checkout.If that is the case, it is worth removing the cart session decoration and just full on replacing it, and providing details on how to create an "order claim" controller.
Comment #14
mglamanThis patch adds an event subscriber which allows passing
?cartToken
to convert the token cart data to the user's session.Comment #16
Wim LeersI'm not 100% certain what this is saying, but: look at
\Drupal\Core\Session\SessionConfigurationInterface::hasSession()
— and especially at the default implementation in\Drupal\Core\Session\SessionConfiguration::hasSession()
:You could decorate core's service and do:
It's
\Drupal\Core\PageCache\RequestPolicy\NoSessionOpen
that ensures Page Cache ignores responses to requests that have sessions, and it calls … you guessed it,\Drupal\Core\Session\SessionConfigurationInterface::hasSession()
, to determine whether a request has a session! So the above would fix this too.Comment #17
mglamanThis takes the advice from #16 and decorates
session_configuration
so that theCommerce-Cart-Token
header orcartToken
query parameter additional specify if a session is active.Comment #18
Wim LeersDoes it work as expected/hoped? :)
Comment #19
mglamanIt does! The session is definitely being activated and page_cache is ignored. I'm having some weird problems on my remote for the entire process, but that's something else I think.
Comment #20
mglamanBah, I had a horrible subscribed events definition in my event subscriber.
Comment #21
mglamanCartTokenClaimSubscriber needs to run before RouterListener which executes routing access checks.
Comment #22
mglamanHEAD fixed. Retesting
Comment #23
mglamanCommerce-Cart-Token needs to be added to the Vary header.
Comment #24
mglamanAdding a Vary that should bust reverse proxy caches if Drupal has a max page cache configured.
Comment #25
mglamanBad patch, re-do!
Comment #26
mglamanPatch with clean up for commit.
Comment #28
mglaman🙌working like a charm.
Comment #29
Wim LeersLovely! :D
final
👍private
👍Service decoration. 👍
A decorated session configuration service 👍
My thumb hurts now 😆
P.S.: issue credit would be cool :)
Comment #30
mglamanD'oh! I thought I checked the box.
Comment #31
ayalon CreditAttribution: ayalon commentedI tried this new feature:
- I had to add a service parameter to activate the feature, which is undocumented
- I have huge performance problems. Getting a cart takes multiple seconds on a plain new system
- I sometime have lock errors.
Any ideas what could be the problem?
Comment #32
ayalon CreditAttribution: ayalon commentedI'm pretty sure the current code only supports token in query strings. Header is documented but the code does not cover it:
Comment #33
ayalon CreditAttribution: ayalon commentedComment #34
mglamanThe code in #32 only exists when linking to the checkout via query parameter.
Cart token is fully supported and implemented on https://commerce-demo-decoupled.firebaseapp.com/. Reference here: https://github.com/mglaman/commerce_demo_decoupled
Can you open a new issue for your specific problem and we can track it there?
---
I thought I added that to https://www.drupal.org/docs/8/modules/commerce-cart-api/cart-token-sessions, but I did not. :) Sorry
That's.. odd. The backend serving the demo has minimal setup and no additional performance items.
Comment #35
ayalon CreditAttribution: ayalon commentedThanks. I was able to solve most of the problems. For an unknown reason, I had 15'000 copies of the same order ID in the tempstore.
Will create a new story for my questions.
Comment #36
mglaman😱That would do it!
This is definitely a more experimental feature, which is why it is off by default. So far it has been working for demos and initial "production" like usage. It would be worth investigating how the tempstore exploded.