Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions examples/ep-commerce-app-router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# EP Commerce storefront (App Router)

Reference storefront for `@elasticpath/plasmic-ep-commerce-elastic-path`. It
mirrors a real consumer install: the Plasmic and Elastic Path packages resolve
from the registry, not from this monorepo.

> **Requires an unreleased package version.** This app uses
> `createEpAuthRoutes`, added in 0.3.0. Until 0.3.0 publishes, `yarn install`
> cannot resolve the dependency — use the local-source workflow below.

## Running it

```bash
cp .env.local.example .env.local # fill in EP_CLIENT_ID, CHECKOUT_SESSION_SECRET, …
yarn install
yarn dev # http://localhost:3456
```

`yarn dev` serves 3456 deliberately. `lib/ep-auth.ts` defaults `baseURL` to
`http://localhost:3456`, and the origin gate rejects cart mutations from an
origin that isn't trusted — so a different port means silently failing writes.
To serve elsewhere, set `NEXT_PUBLIC_BASE_URL` to match.

`plasmic-init.ts` points `host` at the Plasmic instance serving the project, so
that instance has to be reachable — a local `platform/wab` on `:3003` for local
development.

## Testing an unreleased package change

Registry versions can only ever exercise what has shipped. To run this app
against working-tree changes to the package, point the dependency at the local
source and rebuild it first:

```bash
# in plasmicpkgs/commerce-providers/elastic-path
yarn start # tsdx watch — a full `yarn build` takes ~28 minutes
node build-server.mjs # only needed for the /server entry (esbuild, seconds)

# here
yarn add file:../../plasmicpkgs/commerce-providers/elastic-path
rm -rf .next # Next caches across node_modules changes
```

Revert the `package.json` and `yarn.lock` changes before committing — this app
is meant to reflect what a customer installs.

> Under `next dev`, Next's RSC debug instrumentation serializes server-component
> locals — including the EP session and its access token — into the page source.
> That does not happen in `next build` output. Don't run a dev server on a shared
> host or against production Elastic Path credentials.
9 changes: 0 additions & 9 deletions examples/ep-commerce-app-router/app/[[...catchall]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,10 @@ export default async function PlasmicLoaderPage({
PLASMIC.unstable__getServerQueriesData(prefetchedData, queryCtx)
);

// The `$dev` suffix matches `registerWithDevMeta`'s convention in
// plasmic-register-dev-meta.ts. When registering for production
// (without dev-meta), drop the suffix here too so the component name
// matches what's actually registered.
const globalContextsProps = {
"plasmic-commerce-elastic-path-provider$dev": session.providerProps(),
};

return (
<PlasmicClientRootProvider
prefetchedData={prefetchedData}
prefetchedQueryData={prefetchedQueryData}
globalContextsProps={globalContextsProps}
pageParams={pageMeta.params}
pageQuery={queryCtx.query}
>
Expand Down
7 changes: 4 additions & 3 deletions examples/ep-commerce-app-router/app/api/ep/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
* POST /api/ep/ep/cart → set epCartId on session
* POST /api/ep/ep/account/login → persist account fields
* POST /api/ep/ep/account/logout → strip account fields
* GET /api/ep/get-session → read current session
* GET /api/ep/get-session → read current session, minus the
* shopper's EP credentials
*
* The legacy `withEpProviderHeaders` wrapper is no longer needed — the
* `resolveConfig` callback inside `lib/ep-auth.ts` pulls clientId/host
* from the Plasmic loader bundle on every request.
*/
import { toNextJsHandler } from "better-auth/next-js";
import { createEpAuthRoutes } from "@elasticpath/plasmic-ep-commerce-elastic-path/server";
import { epAuth } from "@/lib/ep-auth";

export const { GET, POST } = toNextJsHandler(epAuth.handler);
export const { GET, POST } = createEpAuthRoutes(epAuth);
7 changes: 3 additions & 4 deletions examples/ep-commerce-app-router/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
* middleware runs BEFORE the page, mints an anonymous EP session via
* the better-auth handler, and persists the resulting cookies. After
* this fires once, every subsequent request carries
* `better-auth.session_token` + `better-auth.session_data`, the
* catchall page reads them via `epAuth.api.getSession({cookies})`,
* and the client-side EP SDK receives the same `serverToken` via
* globalContextsProps — no localStorage fallback.
* `better-auth.session_token` + `better-auth.session_data`, and the
* catchall page reads them via `epAuth.api.getSession({cookies})`. The
* token stays server-side from there.
*/
import { epAuthMiddleware } from "@elasticpath/plasmic-ep-commerce-elastic-path/server";
import { epAuth } from "@/lib/ep-auth";
Expand Down
14 changes: 7 additions & 7 deletions examples/ep-commerce-app-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev -p 3456",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@elasticpath/plasmic-ep-commerce-elastic-path": "*",
"@elasticpath/plasmic-mcp-registry": "*",
"@elasticpath/plasmic-ep-commerce-elastic-path": "^0.3.0",
"@elasticpath/plasmic-mcp-registry": "^0.2.1",
"@epcc-sdk/sdks-shopper": "^0.1.0",
"@plasmicapp/loader-nextjs": "*",
"@plasmicapp/nextjs-app-router": "*",
"@plasmicapp/host": "*",
"@plasmicpkgs/commerce": "*",
"@plasmicapp/loader-nextjs": "^2.0.22",
"@plasmicapp/nextjs-app-router": "^1.0.30",
"@plasmicapp/host": "^2.0.16",
"@plasmicpkgs/commerce": "^0.0.257",
"next": "^15.5.9",
"react": "^19",
"react-dom": "^19"
Expand Down
67 changes: 67 additions & 0 deletions plasmicpkgs/commerce-providers/elastic-path/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Changelog

## Unreleased

### Breaking

The shopper's Elastic Path access token no longer reaches the browser. It was
previously serialized into page HTML as `globalContextsProps.serverToken`,
readable by any script on the page.

Cart and checkout now always run through the server routes. The browser-direct
Elastic Path SDK cart path, and the `serverCartMode` toggle that selected
between them, are gone.

**Removed hooks** — all four called the Elastic Path SDK directly from the
browser using the leaked token:

| Removed | Replacement |
| --- | --- |
| `cart/use-cart` | `useCart` from `shopper-context` |
| `cart/use-add-item` | `useAddItem` from `shopper-context`, or `EPAddToCartButton` |
| `cart/use-update-item` | `useUpdateItem` from `shopper-context` |
| `cart/use-remove-item` | `useRemoveItem` from `shopper-context` |

**Removed provider surface** — `getElasticPathProvider` and
`getCommerceProvider` no longer take a `serverToken` argument, and the returned
provider no longer carries `cart`. `initElasticPathClient` takes credentials
only; its client serves catalog reads and mints its own anonymous token from
the public `clientId`.

**Removed from the storefront** — drop the `globalContextsProps` entry that fed
`session.providerProps()` into `PlasmicClientRootProvider`. `providerProps()`
now returns `{}`.

**Mount the auth handler with `createEpAuthRoutes`**, not better-auth's
`toNextJsHandler`:

```ts
// app/api/ep/[...path]/route.ts
export const { GET, POST } = createEpAuthRoutes(epAuth);
```

Every endpoint on the auth handler returns the session record, and the session
carries the shopper's EP credentials — the anonymous access token on all of
them, plus the account-management token once a shopper logs in. The raw handler
hands those to any same-origin script for the cost of one fetch; `/ep/refresh`
returns a freshly rotated token and `/ep/anonymous` needs no cookie at all.

`createEpAuthRoutes` filters the session on every response down to an allowlist:
`id`, `userId`, `expiresAt`, `createdAt`, `updatedAt`, `epCartId`, `epExpires`.
A field added to the session later is withheld by default. `epCartId` is kept
because the checkout components read it and it is not a credential; the
better-auth session id is not, because it lives in an HttpOnly cookie
specifically so scripts cannot read it.

`serverToken` and `serverCartMode` remain in the registered prop schema, hidden
and ignored. Registered props on a hostless package are append-only; removing
one breaks hostless publishing for every package. Everywhere else the flag is
gone: `EpCtx` and `EpProviderBundleConfig` no longer carry `serverCartMode`,
and `extractEpProviderConfig` no longer scrapes it from the loader bundle.

### Removed

The `getServerInfo` bridge on the provider global context, along with the
`auth/ep-*-server-info.ts` modules it fed. They targeted an upstream API that
was reverted and were never wired to a component. Server-side data flows
through Studio Server Queries and `withEpSession` instead.
Loading
Loading