Skip to content

PoC HWB #2298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: canary
Choose a base branch
from
Draft

PoC HWB #2298

wants to merge 1 commit into from

Conversation

bc-nick
Copy link

@bc-nick bc-nick commented May 9, 2025

What/Why?

PoC of Headless Wallet Buttons feature

PoC related to checkout-sdk part: bigcommerce/checkout-sdk-js#2855

Copy link

vercel bot commented May 9, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
catalyst-canary ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 3, 2025 0:11am
4 Skipped Deployments
Name Status Preview Comments Updated (UTC)
catalyst ⬜️ Ignored (Inspect) Jun 3, 2025 0:11am
catalyst-au ⬜️ Ignored (Inspect) Visit Preview Jun 3, 2025 0:11am
catalyst-soul ⬜️ Ignored (Inspect) Visit Preview Jun 3, 2025 0:11am
catalyst-uk ⬜️ Ignored (Inspect) Visit Preview Jun 3, 2025 0:11am

Copy link

changeset-bot bot commented May 9, 2025

⚠️ No Changeset found

Latest commit: 7a5e48e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this PoC, this is fine, just wanted to make sure we make this a route handler and whitelist the queries and mutations via a route handler instead. This should all be working within canary...spike/gql-enablelist#diff-1e657e3f6573e741ec9de194cf6f83b13cda623925400917c480315d2cc4d905 for both queries and mutations cc. @davidchin

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the whitelist POC, I believe it's sufficient to whitelist based on the name of the query or mutation. Otherwise, the filtering logic seems overly complicated as we need to traverse the query.

I recall we previously discussed delegating the filtering logic to the server by passing a header. The distinction between browser and server client already exists on the API side. We already limit what a browser client can do, so it would be ideal if we can leverage that existing logic instead of introducing a separate filtering layer on the Catalyst side. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to prevent other scripts/integrations from utilizing this endpoint by passing different query information. For example, you can send this query with the enabled query name:

    const query = graphql(`
      query PaymentWalletWithInitializationDataQuery {
        site {
          settings {
            storeName
          }
        }
      }
    `);

Passing the header will only prevent sensitive data from coming through, which we do want, but we also don't want this API handler to be open to all queries, just payment wallet specific queries.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I suppose my point of view is that, from a security standpoint, enforcement should really happen at the SF API level. The SF API already distinguishes between browser and server-side clients and limits the fields that browser clients can access. What’s currently missing is the ability to recognise another type of client - those that use a server-side proxy to access the API via the browser.

If that distinction is made, then the filter we apply at the proxy layer isn’t really for security purposes, but rather to further narrow what queries/mutations are exposed through Catalyst. Therefore, it doesn’t need to be very granular. It’s sufficient to specify just the top-level nodes like cart, checkout, currency, and payment etc…

That said, you’re right in saying that if there are no restrictions at the SF API level for proxy clients, then we do need to be more granular to avoid accidental leakage of sensitive data. We probably don’t need to group the enabled list by operation names to avoid repetition, since what really matters are the query and mutation field names.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only reason why I would leave the operation name is so we could have a consistent set of names we can use for identifying queries (e.g. through otel and our other logging systems) since this is a proxy. If we don't enforce an operation name, you can provide an anonymous query and we won't have any sort of insight what the query is. For example:

query {
  site {
    settings {
      storeName
    }
  }
}

I don't mind if we remove it, but I do see it as a nice-to-have to enforce consistency + a narrower enablement of what is proxied.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, it's mainly to enforce best practices. For that, we just need to ensure that clients provide an operation name, not specific operation names, as we're not aiming to enforce a particular naming convention.

Ideally, we would enforce this validation at the SF GQL API layer, but I suppose we can’t do that anymore since it would be a backward incompatible change.

That said, I still believe the more restrictions we impose at the proxy layer, the harder it will be to roll out changes in the future. For example, if the client code (Checkout SDK) requires a new field in a few months, merchants would need to manually update the Catalyst codebase themselves to modify the whitelist.

We really have two options here to get around this problem:

  1. Option 1: Use the header approach and delegate the filtering on the server side, minimising the possible changes we have to do on the proxy side in the future.
  2. Option 2: Go with the library approach, where we ask the merchants to periodically pull updates for the client code and the server code.

Comment on lines 42 to 52
private async initCheckoutButtonInitializer() {
if (!window.checkoutKitLoader) {
throw new InitializationError();
}

const checkoutButtonModule = await window.checkoutKitLoader.load('headless-checkout-wallet');

return checkoutButtonModule.createHeadlessCheckoutWalletInitializer();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that @davidchin and I chatted about was being able to pass in the endpoint location into checkout-sdk in case other headless solutions need to change the path, but keep the logic consistent. We should add that as part of the final solution. For example:

window.checkoutKitLoader.setWalletGqlEndpoint("/wallets/graphql")

Comment on lines 96 to 102
const walletButtons = await getPaymentWallets({
filters: {
cartEntityId: cartId,
},
});

const walletButtonsInitOptions = await createWalletButtonsInitOptions(walletButtons, cart);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of these being waterfall requests, can you see about using our Streamable pattern here?

Copy link
Contributor

github-actions bot commented Jun 3, 2025

⚡️🏠 Lighthouse report

Lighthouse ran against https://catalyst-canary-884y6zdky-bigcommerce-platform.vercel.app

🖥️ Desktop

We ran Lighthouse against the changes on a desktop and produced this report. Here's the summary:

Category Score
🟢 Performance 97
🟢 Accessibility 92
🟠 Best practices 78
🟢 SEO 91

📱 Mobile

We ran Lighthouse against the changes on a mobile and produced this report. Here's the summary:

Category Score
🟠 Performance 70
🟢 Accessibility 92
🟠 Best practices 78
🟢 SEO 92

currencyCode: string;
},
) => {
const currencyData = await getCurrencyData(cart.currencyCode);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this request can be made concurrently, as the requests below don't depend on the result of this request.


const ENABLE_LIST = [
{
name: 'PaymentWalletWithInitializationDataQuery',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned earlier (#2298 (comment)), I think we should not enforce specific operation names. Otherwise, the exact operation names become a part of the API contract and we're not free to change in the future. Also, ideally I want to move the filtering logic out of Catalyst to the API layer so the clients won't be coupled to a specific version of Catalyst.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants