@fanfare-io/fanfare-sdk-shopify 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -17
- package/dist/claimer.d.ts +82 -0
- package/dist/claimer.js +1 -0
- package/dist/index.d.ts +12 -5
- package/dist/index.js +1 -1
- package/dist/metafield.d.ts +4 -4
- package/dist/resolver.d.ts +16 -19
- package/dist/verifier.d.ts +61 -8
- package/dist/verifier.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,29 +7,59 @@ It builds on the framework-free storefront-adapter contract published from
|
|
|
7
7
|
|
|
8
8
|
## Entry points
|
|
9
9
|
|
|
10
|
-
| Import | Runtime | Contents
|
|
11
|
-
| --------------------------------------- | ---------- |
|
|
12
|
-
| `@fanfare-io/fanfare-sdk-shopify` | isomorphic | `createShopifyExperienceResolver`, `shopifyCheckoutVerifier`, `FANFARE_PRODUCT_METAFIELD`, `parseExperienceIds`, `parseFirstExperienceId`, and a re-export of core's `./storefront` vocabulary |
|
|
13
|
-
| `@fanfare-io/fanfare-sdk-shopify/react` | client | `FanfareAdapterProvider`, `useFanfareAdapter`, `FanfareAdapterValue` (`"use client"`)
|
|
10
|
+
| Import | Runtime | Contents |
|
|
11
|
+
| --------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
12
|
+
| `@fanfare-io/fanfare-sdk-shopify` | isomorphic | `createShopifyExperienceResolver`, `createShopifyCheckoutVerifier`, `createShopifyCheckoutClaimer`, `shopifyCheckoutVerifier`, `FANFARE_PRODUCT_METAFIELD`, `parseExperienceIds`, `parseFirstExperienceId`, and a re-export of core's `./storefront` vocabulary |
|
|
13
|
+
| `@fanfare-io/fanfare-sdk-shopify/react` | client | `FanfareAdapterProvider`, `useFanfareAdapter`, `FanfareAdapterValue` (`"use client"`) |
|
|
14
14
|
|
|
15
15
|
The root entry imports zero React, so a server-only consumer (Hydrogen/Oxygen
|
|
16
16
|
loader, Next/Remix server component) installs and uses it without React. React
|
|
17
17
|
is an optional peer dependency, needed only for the `/react` subpath.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Claim vs verify (two separate factories, on purpose)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
`createShopifyCheckoutClaimer` and `createShopifyCheckoutVerifier` relay the same
|
|
22
|
+
`{ credential, distributionId, cartId }` body to the same proxy host, but they
|
|
23
|
+
are deliberately **separate factories** — not one factory with an endpoint flag
|
|
24
|
+
— because they sit on opposite sides of the admission authority's
|
|
25
|
+
validate / reserve / spend boundary:
|
|
26
|
+
|
|
27
|
+
- **Claimer** fires _on grant_ → the non-consuming `admissions/claim` route
|
|
28
|
+
(validate → mint gate token → write the cart metafield). It reserves and spends
|
|
29
|
+
nothing, and returns the minted token's `expDate`, so its result is
|
|
30
|
+
`{ ok:true; expDate } | { ok:false; reason }`.
|
|
31
|
+
- **Verifier** fires _at checkout entry_ → the `admissions/reserve` route
|
|
32
|
+
(validate **and** reserve), and returns a bare `{ ok; reason? }` decision
|
|
33
|
+
whose body it ignores.
|
|
34
|
+
|
|
35
|
+
The split is load-bearing in four ways: (1) different result shapes (the claimer
|
|
36
|
+
carries `expDate`, the verifier doesn't read the body); (2) different
|
|
37
|
+
context-readiness policies (the claimer fires automatically on every grant, so a
|
|
38
|
+
missing context is a benign `{ ok:false, reason:"no_claim_context" }` no-op — the
|
|
39
|
+
verifier _requires_ its context getter and throws at construction); (3) the
|
|
40
|
+
claimer is **not** a `CheckoutVerifier` and is invoked directly by the
|
|
41
|
+
storefront, never threaded through the React adapter's `recordGrant` seam; and
|
|
42
|
+
(4) it targets a different, non-consuming endpoint. They share exactly one
|
|
43
|
+
contract — the cart-bridge token correlation, the `$app:gate/token` metafield
|
|
44
|
+
key, and the `gate_grants` schema — which both must honor in lockstep.
|
|
45
|
+
|
|
46
|
+
## Default vs. proxy-backed seams
|
|
47
|
+
|
|
48
|
+
Each seam has a default that runs locally and network-free, and a proxy-backed
|
|
49
|
+
factory for server-side enforcement. Both share the same `resolve(product)` /
|
|
50
|
+
`verify(grant)` signature, so moving from one to the other is a config change,
|
|
51
|
+
not a surface change:
|
|
23
52
|
|
|
24
53
|
- **`createShopifyExperienceResolver(config)`** — `resolve(product)` resolves
|
|
25
54
|
from a config-supplied static `metafieldValues` map (handle → raw metafield
|
|
26
|
-
value) so
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- **`shopifyCheckoutVerifier`** — `verify(grant)`
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
value), so it runs SSR-safe and network-free. The live Storefront-API read of
|
|
56
|
+
the reified `app--<fanfareAppId>--product` / `experienceIds` metafield uses
|
|
57
|
+
the same `resolve(product)` seam.
|
|
58
|
+
- **`shopifyCheckoutVerifier`** — the zero-config default; `verify(grant)`
|
|
59
|
+
enforces only client-side grant expiry, so a storefront can render the gate
|
|
60
|
+
without any server wiring. For server-side enforcement, use
|
|
61
|
+
**`createShopifyCheckoutVerifier(config)`**, which drives the Fanfare Shopify
|
|
62
|
+
app proxy: a non-consuming admission-credential validation (validate, then
|
|
63
|
+
reserve + gate-token re-mint at checkout entry). Both share the same
|
|
64
|
+
`verify(grant)` signature. `verify` is **total**: it resolves for every grant
|
|
65
|
+
and never rejects, so a throwing transport is wrapped into a denied result.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shopify checkout CLAIMER — the on-grant, NON-CONSUMING sibling of
|
|
3
|
+
* `createShopifyCheckoutVerifier`.
|
|
4
|
+
*
|
|
5
|
+
* The claimer and the verifier relay the same `{ credential, distributionId,
|
|
6
|
+
* cartId }` body to the same proxy host, but they sit on opposite sides of the
|
|
7
|
+
* admission authority's validate / reserve / spend boundary and are deliberately
|
|
8
|
+
* SEPARATE factories — not one factory with an endpoint flag:
|
|
9
|
+
*
|
|
10
|
+
* - The CLAIMER fires *on grant*: it POSTs to the non-consuming `admissions/claim`
|
|
11
|
+
* route, which validates the credential, mints a short-lived gate token, and
|
|
12
|
+
* writes it to the cart metafield. It reserves and spends nothing. It returns
|
|
13
|
+
* the minted token's `expDate` so the storefront can surface a continuity
|
|
14
|
+
* signal — so its result carries a body the caller reads.
|
|
15
|
+
* - The VERIFIER fires *at checkout entry*: it POSTs to the `admissions/reserve`
|
|
16
|
+
* route which validates *and reserves*, and returns a bare gate decision whose
|
|
17
|
+
* body it ignores.
|
|
18
|
+
*
|
|
19
|
+
* The claimer is NOT a `CheckoutVerifier`: different result shape (carries
|
|
20
|
+
* `expDate`), different context-readiness policy (a missing context is a benign
|
|
21
|
+
* no-op, not a construction error — the claimer fires automatically on every
|
|
22
|
+
* grant), and it is invoked directly by the storefront, never threaded through
|
|
23
|
+
* the React adapter's `recordGrant` seam.
|
|
24
|
+
*
|
|
25
|
+
* TOTAL by construction: a missing/`null` context, a non-2xx proxy response, a
|
|
26
|
+
* 2xx body without a parseable `expDate`, and any transport throw all resolve to
|
|
27
|
+
* a denied `{ ok:false, reason }`. It NEVER rejects.
|
|
28
|
+
*/
|
|
29
|
+
/** The live context a claim needs. Separate from `ShopifyCheckoutContext` so the
|
|
30
|
+
* claim and verify seams can diverge without breaking each other. */
|
|
31
|
+
export interface ShopifyClaimContext {
|
|
32
|
+
/** The consumer's admission credential (the `admissionGrant` from the
|
|
33
|
+
* consumer-me snapshot). Relayed to the proxy; never persisted in Shopify. */
|
|
34
|
+
credential: string;
|
|
35
|
+
distributionId: string;
|
|
36
|
+
/** Ajax cart token (incl. `?key=`) for Liquid themes, or the Storefront cart
|
|
37
|
+
* GID for headless. The proxy/cart-bridge resolves it to a cart GID. */
|
|
38
|
+
cartId: string;
|
|
39
|
+
}
|
|
40
|
+
export type ShopifyClaimResult = {
|
|
41
|
+
ok: true;
|
|
42
|
+
expDate: string;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
reason: string;
|
|
46
|
+
};
|
|
47
|
+
export interface ShopifyClaimConfig {
|
|
48
|
+
/** App-proxy base, e.g. "/apps/fanfare/api". `claim` POSTs to
|
|
49
|
+
* `${proxyBaseUrl}/${endpoint}`. */
|
|
50
|
+
proxyBaseUrl: string;
|
|
51
|
+
/**
|
|
52
|
+
* Supplies the live claim context. Returns `null` when the storefront is not
|
|
53
|
+
* ready to claim (no credential/cart yet), which `claim` surfaces as a benign
|
|
54
|
+
* denied result rather than a thrown error. Absent ⇒ always not-ready.
|
|
55
|
+
*/
|
|
56
|
+
getClaimContext?: () => ShopifyClaimContext | null | Promise<ShopifyClaimContext | null>;
|
|
57
|
+
/** Proxy path under `proxyBaseUrl`. Defaults to the non-consuming claim route. */
|
|
58
|
+
endpoint?: string;
|
|
59
|
+
/** Injectable for SSR/test. Defaults to `globalThis.fetch`. */
|
|
60
|
+
fetchImpl?: typeof fetch;
|
|
61
|
+
/**
|
|
62
|
+
* Abort the proxy request after this many ms so a stalled (connected but
|
|
63
|
+
* unresponsive) proxy can't leave `claim()` pending — TOTAL requires every
|
|
64
|
+
* outcome to resolve. The abort surfaces as a denied result. Defaults to 10s.
|
|
65
|
+
*/
|
|
66
|
+
timeoutMs?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface ShopifyCheckoutClaimer {
|
|
69
|
+
claim(): Promise<ShopifyClaimResult>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build a Shopify checkout claimer. The proxy — not this client — is the trust
|
|
73
|
+
* boundary; a `{ ok:true }` means the authority validated the admission and the
|
|
74
|
+
* gate token was minted+written for this cart, never a client assertion.
|
|
75
|
+
*
|
|
76
|
+
* A 409 `cart_not_materialized` is surfaced verbatim as the reason (not a generic
|
|
77
|
+
* `proxy_409`) so the storefront can materialize a real cart and re-claim — the
|
|
78
|
+
* claimer does NOT own materialization, which is context-specific (the Ajax cart
|
|
79
|
+
* bridge for themes vs the Storefront cart GID for headless) and lives in the
|
|
80
|
+
* caller that holds that knowledge.
|
|
81
|
+
*/
|
|
82
|
+
export declare function createShopifyCheckoutClaimer(config: ShopifyClaimConfig): ShopifyCheckoutClaimer;
|
package/dist/claimer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function t(t){if(!t.proxyBaseUrl||""===t.proxyBaseUrl.trim())throw new Error("createShopifyCheckoutClaimer: proxyBaseUrl is required");const e=`${t.proxyBaseUrl.replace(/\/+$/,"")}/${(t.endpoint??"admissions/claim").replace(/^\/+/,"")}`,r=t.fetchImpl??globalThis.fetch;return{async claim(){try{const a=t.getClaimContext?await t.getClaimContext():null;if(null==a)return{ok:!1,reason:"no_claim_context"};const n=await r(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:a.credential,distributionId:a.distributionId,cartId:a.cartId}),signal:AbortSignal.timeout(t.timeoutMs??1e4)});if(!n.ok){const t=await async function(t){if(409===t.status)try{const e=await t.json();if("cart_not_materialized"===e?.error)return"cart_not_materialized"}catch{}return`proxy_${t.status}`}(n);return{ok:!1,reason:t}}const o=await async function(t){try{const e=await t.json();return"string"==typeof e?.expDate&&e.expDate.length>0?e.expDate:null}catch{return null}}(n);return null===o?{ok:!1,reason:"claim_malformed"}:{ok:!0,expDate:o}}catch{return{ok:!1,reason:"claim_unreachable"}}}}}export{t as createShopifyCheckoutClaimer};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,14 +7,21 @@
|
|
|
7
7
|
* component) installs and uses this package without React. The React provider /
|
|
8
8
|
* hooks live in `@fanfare-io/fanfare-sdk-shopify/react`.
|
|
9
9
|
*
|
|
10
|
-
* The resolver
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* The default resolver and verifier run locally and network-free:
|
|
11
|
+
* `createShopifyExperienceResolver` resolves from a config-supplied metafield
|
|
12
|
+
* map, and `shopifyCheckoutVerifier` enforces client-side grant expiry. For
|
|
13
|
+
* server-backed behavior — the live Storefront-API metafield read and
|
|
14
|
+
* admission-credential validation through the Fanfare Shopify app proxy — use
|
|
15
|
+
* the proxy-backed factories `createShopifyCheckoutVerifier` and
|
|
16
|
+
* `createShopifyCheckoutClaimer` (see their JSDoc). Both default and
|
|
17
|
+
* proxy-backed paths share the same `resolve(product)` / `verify(grant)` seams.
|
|
14
18
|
*/
|
|
19
|
+
export { createShopifyCheckoutClaimer } from './claimer';
|
|
20
|
+
export type { ShopifyCheckoutClaimer, ShopifyClaimConfig, ShopifyClaimContext, ShopifyClaimResult } from './claimer';
|
|
15
21
|
export { FANFARE_PRODUCT_METAFIELD, parseExperienceIds, parseFirstExperienceId } from './metafield';
|
|
16
22
|
export { createShopifyExperienceResolver } from './resolver';
|
|
17
23
|
export type { ShopifyExperienceResolverConfig } from './resolver';
|
|
18
|
-
export { shopifyCheckoutVerifier } from './verifier';
|
|
24
|
+
export { createShopifyCheckoutVerifier, shopifyCheckoutVerifier } from './verifier';
|
|
25
|
+
export type { ShopifyCheckoutContext, ShopifyCheckoutVerifierConfig } from './verifier';
|
|
19
26
|
export { ACCESS_STATUSES, snapshotToAccessStatus } from '@fanfare-io/fanfare-sdk-core/storefront';
|
|
20
27
|
export type { AccessStatus, CheckoutAccess, CheckoutVerifier, ExperienceResolver, GrantRecord, VerificationResult, } from '@fanfare-io/fanfare-sdk-core/storefront';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{FANFARE_PRODUCT_METAFIELD as
|
|
1
|
+
import{createShopifyCheckoutClaimer as r}from"./claimer.js";import{FANFARE_PRODUCT_METAFIELD as o,parseExperienceIds as e,parseFirstExperienceId as f}from"./metafield.js";import{createShopifyExperienceResolver as m}from"./resolver.js";import{createShopifyCheckoutVerifier as i,shopifyCheckoutVerifier as t}from"./verifier.js";import{ACCESS_STATUSES as s,snapshotToAccessStatus as a}from"@fanfare-io/fanfare-sdk-core/storefront";export{s as ACCESS_STATUSES,o as FANFARE_PRODUCT_METAFIELD,r as createShopifyCheckoutClaimer,i as createShopifyCheckoutVerifier,m as createShopifyExperienceResolver,e as parseExperienceIds,f as parseFirstExperienceId,t as shopifyCheckoutVerifier,a as snapshotToAccessStatus};
|
package/dist/metafield.d.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* reified namespace — the literal form returns null. The numeric app id is not
|
|
6
6
|
* storefront-readable and is provisioned out-of-band.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The reified-namespace read against the live Storefront API is performed by
|
|
9
|
+
* createShopifyExperienceResolver; the constant and parser below are the stable
|
|
10
|
+
* primitives that read builds on.
|
|
11
11
|
*/
|
|
12
12
|
export declare const FANFARE_PRODUCT_METAFIELD: {
|
|
13
13
|
readonly key: "experienceIds";
|
|
@@ -20,7 +20,7 @@ export declare const FANFARE_PRODUCT_METAFIELD: {
|
|
|
20
20
|
* - JSON array of strings → that array, order preserved
|
|
21
21
|
* - array containing any non-string entry → [] (the platform contract is a
|
|
22
22
|
* list of single-line text fields; a structurally mixed array is unusable)
|
|
23
|
-
* - non-array /
|
|
23
|
+
* - non-array / bare scalar / malformed JSON → []
|
|
24
24
|
* - absent / empty → []
|
|
25
25
|
*/
|
|
26
26
|
export declare function parseExperienceIds(metafieldValue: string | null | undefined): string[];
|
package/dist/resolver.d.ts
CHANGED
|
@@ -9,21 +9,19 @@ export interface ShopifyExperienceResolverConfig {
|
|
|
9
9
|
/** Numeric Shopify app id, provisioned out-of-band; reifies the namespace. */
|
|
10
10
|
fanfareAppId: string;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* (the only body change that lands later) — `resolve(product)`'s signature does
|
|
18
|
-
* NOT change. Omit it once the live read is wired.
|
|
12
|
+
* Static map of product `handle` → raw `experienceIds` metafield value. When
|
|
13
|
+
* supplied, `resolve(product)` reads from this map via `parseFirstExperienceId`
|
|
14
|
+
* and performs no network call, so the resolver runs SSR-safe and network-free.
|
|
15
|
+
* Provide it to resolve from values you have already loaded; omit it to fall
|
|
16
|
+
* back to an empty map (every product resolves to `null`).
|
|
19
17
|
*/
|
|
20
18
|
metafieldValues?: Record<string, string>;
|
|
21
19
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* Injectable fetch (SSR/test seam) for the live Storefront-API read of the
|
|
21
|
+
* reified `app--<fanfareAppId>--product` / `experienceIds` metafield. Defaults
|
|
22
|
+
* to `globalThis.fetch`. The default resolver path reads from `metafieldValues`
|
|
23
|
+
* and never invokes this; it is only exercised when the resolver performs the
|
|
24
|
+
* live read.
|
|
27
25
|
*/
|
|
28
26
|
fetchImpl?: typeof fetch;
|
|
29
27
|
}
|
|
@@ -32,12 +30,11 @@ export interface ShopifyExperienceResolverConfig {
|
|
|
32
30
|
* id carried by the product's `experienceIds` metafield (first id wins), or null
|
|
33
31
|
* when the product is not covered.
|
|
34
32
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* does NOT change. See ENG-931 metafield-wiring TODO.
|
|
33
|
+
* `resolve(product)` reads from the in-config static `metafieldValues` map
|
|
34
|
+
* (handle → raw metafield value) via `parseFirstExperienceId`, so it runs
|
|
35
|
+
* SSR-safe and network-free. The live Storefront-API read of the reified
|
|
36
|
+
* `app--<fanfareAppId>--product` / `experienceIds` metafield uses the same
|
|
37
|
+
* `resolve(product)` seam, so swapping in server-backed resolution is a
|
|
38
|
+
* config concern, not a signature change.
|
|
42
39
|
*/
|
|
43
40
|
export declare function createShopifyExperienceResolver(config: ShopifyExperienceResolverConfig): ExperienceResolver;
|
package/dist/verifier.d.ts
CHANGED
|
@@ -9,13 +9,66 @@ import { CheckoutVerifier } from '@fanfare-io/fanfare-sdk-core/storefront';
|
|
|
9
9
|
* { ok:false, reason:"grant_expired" }; any unexpected throw is caught and
|
|
10
10
|
* resolves { ok:false, reason:"verify_failed" }.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* change. The ENG-913 body redeems `grant.token` (the minted handoff token,
|
|
17
|
-
* marked by `grant.handoffToken === true`) via core's `processHandoffToken`, which
|
|
18
|
-
* THROWS on invalid/expired/replayed — the verifier MUST catch that and return
|
|
19
|
-
* { ok:false, reason } so the contract stays total. See ENG-913.
|
|
12
|
+
* This zero-config default enforces only client-side grant expiry, so a
|
|
13
|
+
* storefront can render the gate without any server wiring. For server-side
|
|
14
|
+
* enforcement, use `createShopifyCheckoutVerifier(config)` below, which drives
|
|
15
|
+
* the Fanfare Shopify app proxy.
|
|
20
16
|
*/
|
|
21
17
|
export declare const shopifyCheckoutVerifier: CheckoutVerifier;
|
|
18
|
+
/**
|
|
19
|
+
* The live checkout context the `verify(grant)` seam does not itself carry. The
|
|
20
|
+
* `CheckoutVerifier.verify` contract receives only a `GrantRecord` (UX/continuity
|
|
21
|
+
* state), but driving the proxy needs the consumer's admission credential, the
|
|
22
|
+
* distribution being checked out, and the cart to gate. The integration supplies
|
|
23
|
+
* these from its own journey state via `config.getCheckoutContext` — keeping the
|
|
24
|
+
* credential out of `GrantRecord` and the published surface minimal.
|
|
25
|
+
*/
|
|
26
|
+
export interface ShopifyCheckoutContext {
|
|
27
|
+
/** The consumer's admission credential (the `admissionGrant` from the
|
|
28
|
+
* consumer-me snapshot). Proof of admission — relayed to the proxy, never
|
|
29
|
+
* persisted in Shopify. */
|
|
30
|
+
credential: string;
|
|
31
|
+
distributionId: string;
|
|
32
|
+
/** Ajax cart token (including `?key=`) for Liquid themes, or the Storefront
|
|
33
|
+
* cart GID for headless. The proxy/cart-bridge resolves it to a cart GID. */
|
|
34
|
+
cartId: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ShopifyCheckoutVerifierConfig {
|
|
37
|
+
/** App-proxy base, e.g. "/apps/fanfare/api". `verify` POSTs to
|
|
38
|
+
* `${proxyBaseUrl}/${endpoint}`. */
|
|
39
|
+
proxyBaseUrl: string;
|
|
40
|
+
/**
|
|
41
|
+
* Supplies the live checkout context the grant seam omits. Returns `null` when
|
|
42
|
+
* the storefront is not ready to check out (no credential/cart yet), which
|
|
43
|
+
* `verify` surfaces as a denied result rather than a thrown error.
|
|
44
|
+
*/
|
|
45
|
+
getCheckoutContext: () => ShopifyCheckoutContext | null | Promise<ShopifyCheckoutContext | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Proxy path under `proxyBaseUrl`. Defaults to the checkout-entry reserve
|
|
48
|
+
* endpoint (validate + reserve + gate-token re-mint). A pre-reservation
|
|
49
|
+
* eligibility check can point this at the non-consuming claim/validate path.
|
|
50
|
+
*/
|
|
51
|
+
endpoint?: string;
|
|
52
|
+
/** Injectable for SSR/test. Defaults to `globalThis.fetch`. */
|
|
53
|
+
fetchImpl?: typeof fetch;
|
|
54
|
+
/**
|
|
55
|
+
* Abort the proxy request after this many ms. Without it, a proxy that accepts
|
|
56
|
+
* the connection but stalls (never responds) leaves `verify()` pending forever
|
|
57
|
+
* — breaking the TOTAL contract (the consumer is stuck in `checking`). The
|
|
58
|
+
* abort surfaces as a denied result. Defaults to 10s.
|
|
59
|
+
*/
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build a Shopify `CheckoutVerifier` that drives the Fanfare app proxy at
|
|
64
|
+
* checkout entry: it relays the admission credential + cart to the proxy, which
|
|
65
|
+
* validates against the admission authority, reserves the admission, and
|
|
66
|
+
* re-mints the platform gate token into the cart metafield. The proxy — not this
|
|
67
|
+
* client — is the trust boundary; a positive result means "the authority
|
|
68
|
+
* currently accepts this admission for this cart", never a client assertion.
|
|
69
|
+
*
|
|
70
|
+
* TOTAL by construction: client-side expiry is a cheap pre-check; a missing
|
|
71
|
+
* context, a non-2xx proxy response, and any transport throw all resolve to a
|
|
72
|
+
* denied `{ ok:false, reason }`. It never rejects.
|
|
73
|
+
*/
|
|
74
|
+
export declare function createShopifyCheckoutVerifier(config: ShopifyCheckoutVerifierConfig): CheckoutVerifier;
|
package/dist/verifier.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e={async verify(e){try{return void 0!==e.expiresAt&&Date.now()>e.expiresAt?{ok:!1,reason:"grant_expired"}:{ok:!0}}catch{return{ok:!1,reason:"verify_failed"}}}};export{e as shopifyCheckoutVerifier};
|
|
1
|
+
const e={async verify(e){try{return void 0!==e.expiresAt&&Date.now()>e.expiresAt?{ok:!1,reason:"grant_expired"}:{ok:!0}}catch{return{ok:!1,reason:"verify_failed"}}}};function r(e){if(!e.proxyBaseUrl||""===e.proxyBaseUrl.trim())throw new Error("createShopifyCheckoutVerifier: proxyBaseUrl is required");if("function"!=typeof e.getCheckoutContext)throw new Error("createShopifyCheckoutVerifier: getCheckoutContext is required");const r=`${e.proxyBaseUrl.replace(/\/+$/,"")}/${(e.endpoint??"admissions/reserve").replace(/^\/+/,"")}`,t=e.fetchImpl??globalThis.fetch;return{async verify(o){try{if(void 0!==o.expiresAt&&Date.now()>o.expiresAt)return{ok:!1,reason:"grant_expired"};const i=await e.getCheckoutContext();if(null==i)return{ok:!1,reason:"no_checkout_context"};const n=await t(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:i.credential,distributionId:i.distributionId,cartId:i.cartId}),signal:AbortSignal.timeout(e.timeoutMs??1e4)});return n.ok?{ok:!0}:{ok:!1,reason:`proxy_${n.status}`}}catch{return{ok:!1,reason:"verify_unreachable"}}}}}export{r as createShopifyCheckoutVerifier,e as shopifyCheckoutVerifier};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fanfare-io/fanfare-sdk-shopify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shopify storefront adapter for Fanfare SDK: product→experience resolver, checkout verifier, and experienceIds metafield primitives",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^18.0.0 || ^19.1.1",
|
|
41
41
|
"react-dom": "^18.0.0 || ^19.1.1",
|
|
42
|
-
"@fanfare-io/fanfare-sdk-core": "0.
|
|
42
|
+
"@fanfare-io/fanfare-sdk-core": "0.3.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependenciesMeta": {
|
|
45
45
|
"react": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"vite-plugin-dts": "^4.5.4",
|
|
65
65
|
"vite-tsconfig-paths": "^5.1.4",
|
|
66
66
|
"vitest": "^3.2.4",
|
|
67
|
-
"@fanfare-io/fanfare-sdk-core": "0.
|
|
67
|
+
"@fanfare-io/fanfare-sdk-core": "0.3.0"
|
|
68
68
|
},
|
|
69
69
|
"sideEffects": false,
|
|
70
70
|
"keywords": [
|