@cmssy/next 0.1.9 → 0.2.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/dist/index.cjs +346 -19
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +346 -21
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -181,14 +181,14 @@ function createDraftRoute(config) {
|
|
|
181
181
|
"cmssy: defaultRedirect must be a same-origin path starting with '/'"
|
|
182
182
|
);
|
|
183
183
|
}
|
|
184
|
-
return async function GET(
|
|
184
|
+
return async function GET(request2) {
|
|
185
185
|
if (config.draftSecret.length < MIN_SECRET_LENGTH) {
|
|
186
186
|
return new Response(
|
|
187
187
|
`cmssy: draftSecret must be at least ${MIN_SECRET_LENGTH} characters`,
|
|
188
188
|
{ status: 500 }
|
|
189
189
|
);
|
|
190
190
|
}
|
|
191
|
-
const url = new URL(
|
|
191
|
+
const url = new URL(request2.url);
|
|
192
192
|
const secret = url.searchParams.get("secret");
|
|
193
193
|
if (!secret || !secretsMatch(secret, config.draftSecret)) {
|
|
194
194
|
return new Response("Invalid draft secret", { status: 401 });
|
|
@@ -203,8 +203,8 @@ function createDraftRoute(config) {
|
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
206
|
-
function isCmssyEditRequest(
|
|
207
|
-
return
|
|
206
|
+
function isCmssyEditRequest(request2) {
|
|
207
|
+
return request2.cookies.has("__prerender_bypass") || request2.nextUrl.searchParams.getAll("cmssyEdit").includes("1");
|
|
208
208
|
}
|
|
209
209
|
async function isCmssyEditMode() {
|
|
210
210
|
const h = await headers.headers();
|
|
@@ -362,11 +362,11 @@ function decodeAccessClaims(accessToken) {
|
|
|
362
362
|
try {
|
|
363
363
|
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
364
364
|
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
365
|
-
const
|
|
366
|
-
if (typeof
|
|
365
|
+
const json3 = JSON.parse(new TextDecoder().decode(bytes));
|
|
366
|
+
if (typeof json3.recordId !== "string" || typeof json3.email !== "string" || json3.type !== "site_member") {
|
|
367
367
|
return null;
|
|
368
368
|
}
|
|
369
|
-
return { recordId:
|
|
369
|
+
return { recordId: json3.recordId, email: json3.email };
|
|
370
370
|
} catch {
|
|
371
371
|
return null;
|
|
372
372
|
}
|
|
@@ -479,12 +479,12 @@ function json(body, status = 200) {
|
|
|
479
479
|
}
|
|
480
480
|
});
|
|
481
481
|
}
|
|
482
|
-
async function readBody(
|
|
483
|
-
const contentType =
|
|
482
|
+
async function readBody(request2) {
|
|
483
|
+
const contentType = request2.headers.get("content-type") ?? "";
|
|
484
484
|
if (!contentType.toLowerCase().includes("application/json")) {
|
|
485
485
|
throw new Error("content-type must be application/json");
|
|
486
486
|
}
|
|
487
|
-
const text = await
|
|
487
|
+
const text = await request2.text();
|
|
488
488
|
if (text.length > MAX_BODY_CHARS) {
|
|
489
489
|
throw new Error("body too large");
|
|
490
490
|
}
|
|
@@ -637,11 +637,11 @@ function createCmssyAuthRoute(config) {
|
|
|
637
637
|
return json({ ok: result.success, message: result.message });
|
|
638
638
|
}
|
|
639
639
|
return {
|
|
640
|
-
async POST(
|
|
640
|
+
async POST(request2, context) {
|
|
641
641
|
const { action } = await context.params;
|
|
642
642
|
let body;
|
|
643
643
|
try {
|
|
644
|
-
body = await readBody(
|
|
644
|
+
body = await readBody(request2);
|
|
645
645
|
} catch {
|
|
646
646
|
return json({ ok: false, message: "Invalid request body." }, 400);
|
|
647
647
|
}
|
|
@@ -683,6 +683,331 @@ function createCmssyAuthRoute(config) {
|
|
|
683
683
|
}
|
|
684
684
|
};
|
|
685
685
|
}
|
|
686
|
+
var CART_FIELDS = `
|
|
687
|
+
id
|
|
688
|
+
status
|
|
689
|
+
itemCount
|
|
690
|
+
subtotal
|
|
691
|
+
currency
|
|
692
|
+
discountedTotal
|
|
693
|
+
appliedDiscount { code type value computedAmount }
|
|
694
|
+
items {
|
|
695
|
+
id
|
|
696
|
+
recordId
|
|
697
|
+
quantity
|
|
698
|
+
variantSelections
|
|
699
|
+
currentPrice
|
|
700
|
+
priceMismatch
|
|
701
|
+
snapshot { name price currency imageUrl sku }
|
|
702
|
+
}
|
|
703
|
+
`;
|
|
704
|
+
var CART_QUERY = `query Cart($workspaceId: ID!) { cart(workspaceId: $workspaceId) { ${CART_FIELDS} } }`;
|
|
705
|
+
var ADD_TO_CART = `mutation AddToCart($input: AddToCartInput!) { addToCart(input: $input) { ${CART_FIELDS} } }`;
|
|
706
|
+
var UPDATE_ITEM = `mutation UpdateCartItem($input: UpdateCartItemInput!) { updateCartItem(input: $input) { ${CART_FIELDS} } }`;
|
|
707
|
+
var REMOVE_ITEM = `mutation RemoveCartItem($workspaceId: ID!, $itemId: ID!) { removeCartItem(workspaceId: $workspaceId, itemId: $itemId) { ${CART_FIELDS} } }`;
|
|
708
|
+
var CLEAR_CART = `mutation ClearCart($workspaceId: ID!) { clearCart(workspaceId: $workspaceId) { ${CART_FIELDS} } }`;
|
|
709
|
+
var APPLY_DISCOUNT = `mutation ApplyDiscount($workspaceId: ID!, $code: String!) { applyDiscount(workspaceId: $workspaceId, code: $code) { ${CART_FIELDS} } }`;
|
|
710
|
+
var REMOVE_DISCOUNT = `mutation RemoveDiscount($workspaceId: ID!) { removeDiscount(workspaceId: $workspaceId) { ${CART_FIELDS} } }`;
|
|
711
|
+
var CHECKOUT = `mutation Checkout($input: CheckoutInput!) {
|
|
712
|
+
checkout(input: $input) { id status subtotal total currency customerEmail }
|
|
713
|
+
}`;
|
|
714
|
+
var PRODUCT = `query Product($workspaceId: String!, $modelSlug: String!, $filter: JSON) {
|
|
715
|
+
publicModelRecords(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, limit: 1) {
|
|
716
|
+
items { id data variants { id sku price inventory selectedOptions { name value } } }
|
|
717
|
+
}
|
|
718
|
+
}`;
|
|
719
|
+
var workspaceIdCache2 = /* @__PURE__ */ new Map();
|
|
720
|
+
function workspaceIdFor2(config) {
|
|
721
|
+
const key = `${config.apiUrl}::${config.workspaceSlug}`;
|
|
722
|
+
const existing = workspaceIdCache2.get(key);
|
|
723
|
+
if (existing) return existing;
|
|
724
|
+
const fresh = react.resolveWorkspaceId(config).catch((err) => {
|
|
725
|
+
workspaceIdCache2.delete(key);
|
|
726
|
+
throw err;
|
|
727
|
+
});
|
|
728
|
+
workspaceIdCache2.set(key, fresh);
|
|
729
|
+
return fresh;
|
|
730
|
+
}
|
|
731
|
+
async function request(config, ctx, workspaceId, query, variables, label) {
|
|
732
|
+
return react.graphqlRequest(
|
|
733
|
+
config,
|
|
734
|
+
query,
|
|
735
|
+
variables,
|
|
736
|
+
{
|
|
737
|
+
headers: {
|
|
738
|
+
"x-workspace-id": workspaceId,
|
|
739
|
+
"x-cart-session": ctx.cartToken,
|
|
740
|
+
...ctx.accessToken ? { authorization: `Bearer ${ctx.accessToken}` } : {}
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
label
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
async function backendGetCart(config, ctx) {
|
|
747
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
748
|
+
const data = await request(
|
|
749
|
+
config,
|
|
750
|
+
ctx,
|
|
751
|
+
workspaceId,
|
|
752
|
+
CART_QUERY,
|
|
753
|
+
{ workspaceId },
|
|
754
|
+
"cart query"
|
|
755
|
+
);
|
|
756
|
+
return data.cart;
|
|
757
|
+
}
|
|
758
|
+
async function backendAddToCart(config, ctx, input) {
|
|
759
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
760
|
+
const data = await request(
|
|
761
|
+
config,
|
|
762
|
+
ctx,
|
|
763
|
+
workspaceId,
|
|
764
|
+
ADD_TO_CART,
|
|
765
|
+
{ input: { workspaceId, ...input } },
|
|
766
|
+
"add to cart"
|
|
767
|
+
);
|
|
768
|
+
return data.addToCart;
|
|
769
|
+
}
|
|
770
|
+
async function backendUpdateItem(config, ctx, input) {
|
|
771
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
772
|
+
const data = await request(
|
|
773
|
+
config,
|
|
774
|
+
ctx,
|
|
775
|
+
workspaceId,
|
|
776
|
+
UPDATE_ITEM,
|
|
777
|
+
{ input: { workspaceId, ...input } },
|
|
778
|
+
"update cart item"
|
|
779
|
+
);
|
|
780
|
+
return data.updateCartItem;
|
|
781
|
+
}
|
|
782
|
+
async function backendRemoveItem(config, ctx, itemId) {
|
|
783
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
784
|
+
const data = await request(
|
|
785
|
+
config,
|
|
786
|
+
ctx,
|
|
787
|
+
workspaceId,
|
|
788
|
+
REMOVE_ITEM,
|
|
789
|
+
{ workspaceId, itemId },
|
|
790
|
+
"remove cart item"
|
|
791
|
+
);
|
|
792
|
+
return data.removeCartItem;
|
|
793
|
+
}
|
|
794
|
+
async function backendClearCart(config, ctx) {
|
|
795
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
796
|
+
const data = await request(
|
|
797
|
+
config,
|
|
798
|
+
ctx,
|
|
799
|
+
workspaceId,
|
|
800
|
+
CLEAR_CART,
|
|
801
|
+
{ workspaceId },
|
|
802
|
+
"clear cart"
|
|
803
|
+
);
|
|
804
|
+
return data.clearCart;
|
|
805
|
+
}
|
|
806
|
+
async function backendApplyDiscount(config, ctx, code) {
|
|
807
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
808
|
+
const data = await request(
|
|
809
|
+
config,
|
|
810
|
+
ctx,
|
|
811
|
+
workspaceId,
|
|
812
|
+
APPLY_DISCOUNT,
|
|
813
|
+
{ workspaceId, code },
|
|
814
|
+
"apply discount"
|
|
815
|
+
);
|
|
816
|
+
return data.applyDiscount;
|
|
817
|
+
}
|
|
818
|
+
async function backendRemoveDiscount(config, ctx) {
|
|
819
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
820
|
+
const data = await request(
|
|
821
|
+
config,
|
|
822
|
+
ctx,
|
|
823
|
+
workspaceId,
|
|
824
|
+
REMOVE_DISCOUNT,
|
|
825
|
+
{ workspaceId },
|
|
826
|
+
"remove discount"
|
|
827
|
+
);
|
|
828
|
+
return data.removeDiscount;
|
|
829
|
+
}
|
|
830
|
+
async function backendCheckout(config, ctx, customerEmail) {
|
|
831
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
832
|
+
const data = await request(
|
|
833
|
+
config,
|
|
834
|
+
ctx,
|
|
835
|
+
workspaceId,
|
|
836
|
+
CHECKOUT,
|
|
837
|
+
{ input: { workspaceId, customerEmail } },
|
|
838
|
+
"checkout"
|
|
839
|
+
);
|
|
840
|
+
return data.checkout;
|
|
841
|
+
}
|
|
842
|
+
async function backendProduct(config, ctx, modelSlug, filter) {
|
|
843
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
844
|
+
const data = await request(
|
|
845
|
+
config,
|
|
846
|
+
ctx,
|
|
847
|
+
workspaceId,
|
|
848
|
+
PRODUCT,
|
|
849
|
+
{ workspaceId, modelSlug, filter },
|
|
850
|
+
"product query"
|
|
851
|
+
);
|
|
852
|
+
return data.publicModelRecords.items[0] ?? null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/create-cart-route.ts
|
|
856
|
+
var CMSSY_CART_COOKIE = "cmssy_cart";
|
|
857
|
+
var CART_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
858
|
+
var CART_TOKEN_BYTES = 32;
|
|
859
|
+
var MAX_BODY_CHARS2 = 16 * 1024;
|
|
860
|
+
function json2(body, status = 200) {
|
|
861
|
+
return new Response(JSON.stringify(body), {
|
|
862
|
+
status,
|
|
863
|
+
headers: {
|
|
864
|
+
"content-type": "application/json",
|
|
865
|
+
"cache-control": "no-store"
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
function cartCookieOptions() {
|
|
870
|
+
return {
|
|
871
|
+
httpOnly: true,
|
|
872
|
+
secure: process.env.NODE_ENV !== "development",
|
|
873
|
+
sameSite: "lax",
|
|
874
|
+
path: "/",
|
|
875
|
+
maxAge: CART_MAX_AGE_SECONDS
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function mintToken() {
|
|
879
|
+
const bytes = new Uint8Array(CART_TOKEN_BYTES);
|
|
880
|
+
crypto.getRandomValues(bytes);
|
|
881
|
+
let binary = "";
|
|
882
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
883
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
884
|
+
}
|
|
885
|
+
async function readBody2(request2) {
|
|
886
|
+
const contentType = request2.headers.get("content-type") ?? "";
|
|
887
|
+
if (!contentType.toLowerCase().includes("application/json")) {
|
|
888
|
+
throw new Error("content-type must be application/json");
|
|
889
|
+
}
|
|
890
|
+
const text = await request2.text();
|
|
891
|
+
if (text.length > MAX_BODY_CHARS2) throw new Error("body too large");
|
|
892
|
+
if (!text) return {};
|
|
893
|
+
const parsed = JSON.parse(text);
|
|
894
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
895
|
+
throw new Error("body must be a JSON object");
|
|
896
|
+
}
|
|
897
|
+
return parsed;
|
|
898
|
+
}
|
|
899
|
+
function str2(value) {
|
|
900
|
+
return typeof value === "string" ? value : "";
|
|
901
|
+
}
|
|
902
|
+
function plainObject2(value) {
|
|
903
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
904
|
+
return value;
|
|
905
|
+
}
|
|
906
|
+
function createCmssyCartRoute(config) {
|
|
907
|
+
async function ensureCartToken() {
|
|
908
|
+
const jar = await headers.cookies();
|
|
909
|
+
const existing = jar.get(CMSSY_CART_COOKIE)?.value;
|
|
910
|
+
if (existing) return existing;
|
|
911
|
+
const token = mintToken();
|
|
912
|
+
jar.set(CMSSY_CART_COOKIE, token, cartCookieOptions());
|
|
913
|
+
return token;
|
|
914
|
+
}
|
|
915
|
+
async function clearCartToken() {
|
|
916
|
+
const jar = await headers.cookies();
|
|
917
|
+
jar.set(CMSSY_CART_COOKIE, "", { ...cartCookieOptions(), maxAge: 0 });
|
|
918
|
+
}
|
|
919
|
+
async function memberAccessToken() {
|
|
920
|
+
if (!config.auth) return void 0;
|
|
921
|
+
const jar = await headers.cookies();
|
|
922
|
+
const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
|
|
923
|
+
if (!raw) return void 0;
|
|
924
|
+
const session = await openSession(
|
|
925
|
+
raw,
|
|
926
|
+
config.auth.sessionSecret,
|
|
927
|
+
config.workspaceSlug
|
|
928
|
+
);
|
|
929
|
+
if (!session || isAccessExpired(session)) return void 0;
|
|
930
|
+
return session.accessToken;
|
|
931
|
+
}
|
|
932
|
+
async function buildContext() {
|
|
933
|
+
const cartToken = await ensureCartToken();
|
|
934
|
+
const accessToken = await memberAccessToken();
|
|
935
|
+
return accessToken ? { cartToken, accessToken } : { cartToken };
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
async POST(request2, context) {
|
|
939
|
+
const { action } = await context.params;
|
|
940
|
+
let body;
|
|
941
|
+
try {
|
|
942
|
+
body = await readBody2(request2);
|
|
943
|
+
} catch {
|
|
944
|
+
return json2({ message: "Invalid request body." }, 400);
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
const ctx = await buildContext();
|
|
948
|
+
switch (action) {
|
|
949
|
+
case "cart":
|
|
950
|
+
return json2({ cart: await backendGetCart(config, ctx) });
|
|
951
|
+
case "add":
|
|
952
|
+
return json2({
|
|
953
|
+
cart: await backendAddToCart(config, ctx, {
|
|
954
|
+
recordId: str2(body.recordId),
|
|
955
|
+
quantity: typeof body.quantity === "number" ? body.quantity : 1,
|
|
956
|
+
variantSelections: body.variantSelections,
|
|
957
|
+
notes: typeof body.notes === "string" ? body.notes : void 0
|
|
958
|
+
})
|
|
959
|
+
});
|
|
960
|
+
case "update":
|
|
961
|
+
return json2({
|
|
962
|
+
cart: await backendUpdateItem(config, ctx, {
|
|
963
|
+
itemId: str2(body.itemId),
|
|
964
|
+
quantity: typeof body.quantity === "number" ? body.quantity : 0
|
|
965
|
+
})
|
|
966
|
+
});
|
|
967
|
+
case "remove":
|
|
968
|
+
return json2({
|
|
969
|
+
cart: await backendRemoveItem(config, ctx, str2(body.itemId))
|
|
970
|
+
});
|
|
971
|
+
case "clear":
|
|
972
|
+
return json2({ cart: await backendClearCart(config, ctx) });
|
|
973
|
+
case "apply-discount":
|
|
974
|
+
return json2({
|
|
975
|
+
cart: await backendApplyDiscount(config, ctx, str2(body.code))
|
|
976
|
+
});
|
|
977
|
+
case "remove-discount":
|
|
978
|
+
return json2({ cart: await backendRemoveDiscount(config, ctx) });
|
|
979
|
+
case "checkout": {
|
|
980
|
+
const order = await backendCheckout(
|
|
981
|
+
config,
|
|
982
|
+
ctx,
|
|
983
|
+
str2(body.customerEmail)
|
|
984
|
+
);
|
|
985
|
+
await clearCartToken();
|
|
986
|
+
return json2({ order });
|
|
987
|
+
}
|
|
988
|
+
case "product":
|
|
989
|
+
return json2({
|
|
990
|
+
product: await backendProduct(
|
|
991
|
+
config,
|
|
992
|
+
ctx,
|
|
993
|
+
str2(body.modelSlug),
|
|
994
|
+
plainObject2(body.filter)
|
|
995
|
+
)
|
|
996
|
+
});
|
|
997
|
+
default:
|
|
998
|
+
return json2({ message: "Not found." }, 404);
|
|
999
|
+
}
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
return json2(
|
|
1002
|
+
{
|
|
1003
|
+
message: err instanceof Error ? err.message : "Commerce request failed"
|
|
1004
|
+
},
|
|
1005
|
+
502
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
686
1011
|
async function readValidSession(config) {
|
|
687
1012
|
const auth = assertAuthConfig(config);
|
|
688
1013
|
const jar = await headers.cookies();
|
|
@@ -704,13 +1029,13 @@ async function getCmssyAccessToken(config) {
|
|
|
704
1029
|
const session = await readValidSession(config);
|
|
705
1030
|
return session?.accessToken ?? null;
|
|
706
1031
|
}
|
|
707
|
-
function isPrefetch(
|
|
708
|
-
return
|
|
1032
|
+
function isPrefetch(request2) {
|
|
1033
|
+
return request2.headers.get("next-router-prefetch") !== null || request2.headers.get("purpose") === "prefetch" || (request2.headers.get("sec-purpose") ?? "").includes("prefetch");
|
|
709
1034
|
}
|
|
710
1035
|
function createCmssyAuthMiddleware(config) {
|
|
711
1036
|
const auth = assertAuthConfig(config);
|
|
712
|
-
return async function cmssyAuthMiddleware(
|
|
713
|
-
const raw =
|
|
1037
|
+
return async function cmssyAuthMiddleware(request2) {
|
|
1038
|
+
const raw = request2.cookies.get(CMSSY_SESSION_COOKIE)?.value;
|
|
714
1039
|
if (!raw) return server.NextResponse.next();
|
|
715
1040
|
const session = await openSession(
|
|
716
1041
|
raw,
|
|
@@ -726,7 +1051,7 @@ function createCmssyAuthMiddleware(config) {
|
|
|
726
1051
|
return response;
|
|
727
1052
|
}
|
|
728
1053
|
if (!isAccessExpired(session)) return server.NextResponse.next();
|
|
729
|
-
if (isPrefetch(
|
|
1054
|
+
if (isPrefetch(request2)) return server.NextResponse.next();
|
|
730
1055
|
let payload = null;
|
|
731
1056
|
try {
|
|
732
1057
|
const result = await backendRefresh(config, session.refreshToken);
|
|
@@ -747,13 +1072,14 @@ function createCmssyAuthMiddleware(config) {
|
|
|
747
1072
|
auth.sessionSecret,
|
|
748
1073
|
config.workspaceSlug
|
|
749
1074
|
);
|
|
750
|
-
|
|
751
|
-
const refreshed = server.NextResponse.next({ request });
|
|
1075
|
+
request2.cookies.set(CMSSY_SESSION_COOKIE, sealed);
|
|
1076
|
+
const refreshed = server.NextResponse.next({ request: request2 });
|
|
752
1077
|
refreshed.cookies.set(CMSSY_SESSION_COOKIE, sealed, sessionCookieOptions());
|
|
753
1078
|
return refreshed;
|
|
754
1079
|
};
|
|
755
1080
|
}
|
|
756
1081
|
|
|
1082
|
+
exports.CMSSY_CART_COOKIE = CMSSY_CART_COOKIE;
|
|
757
1083
|
exports.CMSSY_EDIT_HEADER = CMSSY_EDIT_HEADER;
|
|
758
1084
|
exports.CMSSY_LOCALE_HEADER = CMSSY_LOCALE_HEADER;
|
|
759
1085
|
exports.CMSSY_SESSION_COOKIE = CMSSY_SESSION_COOKIE;
|
|
@@ -763,6 +1089,7 @@ exports.assertAuthConfig = assertAuthConfig;
|
|
|
763
1089
|
exports.cmssyCspHeaders = cmssyCspHeaders;
|
|
764
1090
|
exports.createCmssyAuthMiddleware = createCmssyAuthMiddleware;
|
|
765
1091
|
exports.createCmssyAuthRoute = createCmssyAuthRoute;
|
|
1092
|
+
exports.createCmssyCartRoute = createCmssyCartRoute;
|
|
766
1093
|
exports.createCmssyPage = createCmssyPage;
|
|
767
1094
|
exports.createDraftRoute = createDraftRoute;
|
|
768
1095
|
exports.getCmssyAccessToken = getCmssyAccessToken;
|
package/dist/index.d.cts
CHANGED
|
@@ -120,10 +120,20 @@ interface CmssyAuthRouteHandlers {
|
|
|
120
120
|
}
|
|
121
121
|
declare function createCmssyAuthRoute(config: CmssyNextConfig): CmssyAuthRouteHandlers;
|
|
122
122
|
|
|
123
|
+
declare const CMSSY_CART_COOKIE = "cmssy_cart";
|
|
124
|
+
interface CmssyCartRouteHandlers {
|
|
125
|
+
POST(request: Request, context: {
|
|
126
|
+
params: Promise<{
|
|
127
|
+
action: string;
|
|
128
|
+
}>;
|
|
129
|
+
}): Promise<Response>;
|
|
130
|
+
}
|
|
131
|
+
declare function createCmssyCartRoute(config: CmssyNextConfig): CmssyCartRouteHandlers;
|
|
132
|
+
|
|
123
133
|
declare function getCmssyUser(config: CmssyNextConfig): Promise<CmssySessionUser | null>;
|
|
124
134
|
declare function getCmssyAccessToken(config: CmssyNextConfig): Promise<string | null>;
|
|
125
135
|
|
|
126
136
|
type CmssyAuthMiddleware = (request: NextRequest) => Promise<NextResponse>;
|
|
127
137
|
declare function createCmssyAuthMiddleware(config: CmssyNextConfig): CmssyAuthMiddleware;
|
|
128
138
|
|
|
129
|
-
export { CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssySessionPayload, type CmssySessionUser, type CreateCmssyPageOptions, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyPage, createDraftRoute, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, sealSession, sessionCookieOptions, splitCmssyLocale };
|
|
139
|
+
export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssySessionPayload, type CmssySessionUser, type CreateCmssyPageOptions, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyPage, createDraftRoute, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, sealSession, sessionCookieOptions, splitCmssyLocale };
|
package/dist/index.d.ts
CHANGED
|
@@ -120,10 +120,20 @@ interface CmssyAuthRouteHandlers {
|
|
|
120
120
|
}
|
|
121
121
|
declare function createCmssyAuthRoute(config: CmssyNextConfig): CmssyAuthRouteHandlers;
|
|
122
122
|
|
|
123
|
+
declare const CMSSY_CART_COOKIE = "cmssy_cart";
|
|
124
|
+
interface CmssyCartRouteHandlers {
|
|
125
|
+
POST(request: Request, context: {
|
|
126
|
+
params: Promise<{
|
|
127
|
+
action: string;
|
|
128
|
+
}>;
|
|
129
|
+
}): Promise<Response>;
|
|
130
|
+
}
|
|
131
|
+
declare function createCmssyCartRoute(config: CmssyNextConfig): CmssyCartRouteHandlers;
|
|
132
|
+
|
|
123
133
|
declare function getCmssyUser(config: CmssyNextConfig): Promise<CmssySessionUser | null>;
|
|
124
134
|
declare function getCmssyAccessToken(config: CmssyNextConfig): Promise<string | null>;
|
|
125
135
|
|
|
126
136
|
type CmssyAuthMiddleware = (request: NextRequest) => Promise<NextResponse>;
|
|
127
137
|
declare function createCmssyAuthMiddleware(config: CmssyNextConfig): CmssyAuthMiddleware;
|
|
128
138
|
|
|
129
|
-
export { CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssySessionPayload, type CmssySessionUser, type CreateCmssyPageOptions, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyPage, createDraftRoute, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, sealSession, sessionCookieOptions, splitCmssyLocale };
|
|
139
|
+
export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssySessionPayload, type CmssySessionUser, type CreateCmssyPageOptions, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyPage, createDraftRoute, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, sealSession, sessionCookieOptions, splitCmssyLocale };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { draftMode, headers, cookies } from 'next/headers';
|
|
2
2
|
import { notFound, redirect } from 'next/navigation';
|
|
3
|
-
import { resolveSiteLocales, splitLocaleFromPath, fetchPage, resolveForms, CmssyServerPage,
|
|
3
|
+
import { resolveSiteLocales, splitLocaleFromPath, fetchPage, resolveForms, CmssyServerPage, resolveWorkspaceId, graphqlRequest } from '@cmssy/react';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
5
5
|
import { createHash, timingSafeEqual } from 'crypto';
|
|
6
6
|
import { EncryptJWT, jwtDecrypt } from 'jose';
|
|
@@ -179,14 +179,14 @@ function createDraftRoute(config) {
|
|
|
179
179
|
"cmssy: defaultRedirect must be a same-origin path starting with '/'"
|
|
180
180
|
);
|
|
181
181
|
}
|
|
182
|
-
return async function GET(
|
|
182
|
+
return async function GET(request2) {
|
|
183
183
|
if (config.draftSecret.length < MIN_SECRET_LENGTH) {
|
|
184
184
|
return new Response(
|
|
185
185
|
`cmssy: draftSecret must be at least ${MIN_SECRET_LENGTH} characters`,
|
|
186
186
|
{ status: 500 }
|
|
187
187
|
);
|
|
188
188
|
}
|
|
189
|
-
const url = new URL(
|
|
189
|
+
const url = new URL(request2.url);
|
|
190
190
|
const secret = url.searchParams.get("secret");
|
|
191
191
|
if (!secret || !secretsMatch(secret, config.draftSecret)) {
|
|
192
192
|
return new Response("Invalid draft secret", { status: 401 });
|
|
@@ -201,8 +201,8 @@ function createDraftRoute(config) {
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
204
|
-
function isCmssyEditRequest(
|
|
205
|
-
return
|
|
204
|
+
function isCmssyEditRequest(request2) {
|
|
205
|
+
return request2.cookies.has("__prerender_bypass") || request2.nextUrl.searchParams.getAll("cmssyEdit").includes("1");
|
|
206
206
|
}
|
|
207
207
|
async function isCmssyEditMode() {
|
|
208
208
|
const h = await headers();
|
|
@@ -360,11 +360,11 @@ function decodeAccessClaims(accessToken) {
|
|
|
360
360
|
try {
|
|
361
361
|
const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
362
362
|
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
363
|
-
const
|
|
364
|
-
if (typeof
|
|
363
|
+
const json3 = JSON.parse(new TextDecoder().decode(bytes));
|
|
364
|
+
if (typeof json3.recordId !== "string" || typeof json3.email !== "string" || json3.type !== "site_member") {
|
|
365
365
|
return null;
|
|
366
366
|
}
|
|
367
|
-
return { recordId:
|
|
367
|
+
return { recordId: json3.recordId, email: json3.email };
|
|
368
368
|
} catch {
|
|
369
369
|
return null;
|
|
370
370
|
}
|
|
@@ -477,12 +477,12 @@ function json(body, status = 200) {
|
|
|
477
477
|
}
|
|
478
478
|
});
|
|
479
479
|
}
|
|
480
|
-
async function readBody(
|
|
481
|
-
const contentType =
|
|
480
|
+
async function readBody(request2) {
|
|
481
|
+
const contentType = request2.headers.get("content-type") ?? "";
|
|
482
482
|
if (!contentType.toLowerCase().includes("application/json")) {
|
|
483
483
|
throw new Error("content-type must be application/json");
|
|
484
484
|
}
|
|
485
|
-
const text = await
|
|
485
|
+
const text = await request2.text();
|
|
486
486
|
if (text.length > MAX_BODY_CHARS) {
|
|
487
487
|
throw new Error("body too large");
|
|
488
488
|
}
|
|
@@ -635,11 +635,11 @@ function createCmssyAuthRoute(config) {
|
|
|
635
635
|
return json({ ok: result.success, message: result.message });
|
|
636
636
|
}
|
|
637
637
|
return {
|
|
638
|
-
async POST(
|
|
638
|
+
async POST(request2, context) {
|
|
639
639
|
const { action } = await context.params;
|
|
640
640
|
let body;
|
|
641
641
|
try {
|
|
642
|
-
body = await readBody(
|
|
642
|
+
body = await readBody(request2);
|
|
643
643
|
} catch {
|
|
644
644
|
return json({ ok: false, message: "Invalid request body." }, 400);
|
|
645
645
|
}
|
|
@@ -681,6 +681,331 @@ function createCmssyAuthRoute(config) {
|
|
|
681
681
|
}
|
|
682
682
|
};
|
|
683
683
|
}
|
|
684
|
+
var CART_FIELDS = `
|
|
685
|
+
id
|
|
686
|
+
status
|
|
687
|
+
itemCount
|
|
688
|
+
subtotal
|
|
689
|
+
currency
|
|
690
|
+
discountedTotal
|
|
691
|
+
appliedDiscount { code type value computedAmount }
|
|
692
|
+
items {
|
|
693
|
+
id
|
|
694
|
+
recordId
|
|
695
|
+
quantity
|
|
696
|
+
variantSelections
|
|
697
|
+
currentPrice
|
|
698
|
+
priceMismatch
|
|
699
|
+
snapshot { name price currency imageUrl sku }
|
|
700
|
+
}
|
|
701
|
+
`;
|
|
702
|
+
var CART_QUERY = `query Cart($workspaceId: ID!) { cart(workspaceId: $workspaceId) { ${CART_FIELDS} } }`;
|
|
703
|
+
var ADD_TO_CART = `mutation AddToCart($input: AddToCartInput!) { addToCart(input: $input) { ${CART_FIELDS} } }`;
|
|
704
|
+
var UPDATE_ITEM = `mutation UpdateCartItem($input: UpdateCartItemInput!) { updateCartItem(input: $input) { ${CART_FIELDS} } }`;
|
|
705
|
+
var REMOVE_ITEM = `mutation RemoveCartItem($workspaceId: ID!, $itemId: ID!) { removeCartItem(workspaceId: $workspaceId, itemId: $itemId) { ${CART_FIELDS} } }`;
|
|
706
|
+
var CLEAR_CART = `mutation ClearCart($workspaceId: ID!) { clearCart(workspaceId: $workspaceId) { ${CART_FIELDS} } }`;
|
|
707
|
+
var APPLY_DISCOUNT = `mutation ApplyDiscount($workspaceId: ID!, $code: String!) { applyDiscount(workspaceId: $workspaceId, code: $code) { ${CART_FIELDS} } }`;
|
|
708
|
+
var REMOVE_DISCOUNT = `mutation RemoveDiscount($workspaceId: ID!) { removeDiscount(workspaceId: $workspaceId) { ${CART_FIELDS} } }`;
|
|
709
|
+
var CHECKOUT = `mutation Checkout($input: CheckoutInput!) {
|
|
710
|
+
checkout(input: $input) { id status subtotal total currency customerEmail }
|
|
711
|
+
}`;
|
|
712
|
+
var PRODUCT = `query Product($workspaceId: String!, $modelSlug: String!, $filter: JSON) {
|
|
713
|
+
publicModelRecords(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, limit: 1) {
|
|
714
|
+
items { id data variants { id sku price inventory selectedOptions { name value } } }
|
|
715
|
+
}
|
|
716
|
+
}`;
|
|
717
|
+
var workspaceIdCache2 = /* @__PURE__ */ new Map();
|
|
718
|
+
function workspaceIdFor2(config) {
|
|
719
|
+
const key = `${config.apiUrl}::${config.workspaceSlug}`;
|
|
720
|
+
const existing = workspaceIdCache2.get(key);
|
|
721
|
+
if (existing) return existing;
|
|
722
|
+
const fresh = resolveWorkspaceId(config).catch((err) => {
|
|
723
|
+
workspaceIdCache2.delete(key);
|
|
724
|
+
throw err;
|
|
725
|
+
});
|
|
726
|
+
workspaceIdCache2.set(key, fresh);
|
|
727
|
+
return fresh;
|
|
728
|
+
}
|
|
729
|
+
async function request(config, ctx, workspaceId, query, variables, label) {
|
|
730
|
+
return graphqlRequest(
|
|
731
|
+
config,
|
|
732
|
+
query,
|
|
733
|
+
variables,
|
|
734
|
+
{
|
|
735
|
+
headers: {
|
|
736
|
+
"x-workspace-id": workspaceId,
|
|
737
|
+
"x-cart-session": ctx.cartToken,
|
|
738
|
+
...ctx.accessToken ? { authorization: `Bearer ${ctx.accessToken}` } : {}
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
label
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
async function backendGetCart(config, ctx) {
|
|
745
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
746
|
+
const data = await request(
|
|
747
|
+
config,
|
|
748
|
+
ctx,
|
|
749
|
+
workspaceId,
|
|
750
|
+
CART_QUERY,
|
|
751
|
+
{ workspaceId },
|
|
752
|
+
"cart query"
|
|
753
|
+
);
|
|
754
|
+
return data.cart;
|
|
755
|
+
}
|
|
756
|
+
async function backendAddToCart(config, ctx, input) {
|
|
757
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
758
|
+
const data = await request(
|
|
759
|
+
config,
|
|
760
|
+
ctx,
|
|
761
|
+
workspaceId,
|
|
762
|
+
ADD_TO_CART,
|
|
763
|
+
{ input: { workspaceId, ...input } },
|
|
764
|
+
"add to cart"
|
|
765
|
+
);
|
|
766
|
+
return data.addToCart;
|
|
767
|
+
}
|
|
768
|
+
async function backendUpdateItem(config, ctx, input) {
|
|
769
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
770
|
+
const data = await request(
|
|
771
|
+
config,
|
|
772
|
+
ctx,
|
|
773
|
+
workspaceId,
|
|
774
|
+
UPDATE_ITEM,
|
|
775
|
+
{ input: { workspaceId, ...input } },
|
|
776
|
+
"update cart item"
|
|
777
|
+
);
|
|
778
|
+
return data.updateCartItem;
|
|
779
|
+
}
|
|
780
|
+
async function backendRemoveItem(config, ctx, itemId) {
|
|
781
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
782
|
+
const data = await request(
|
|
783
|
+
config,
|
|
784
|
+
ctx,
|
|
785
|
+
workspaceId,
|
|
786
|
+
REMOVE_ITEM,
|
|
787
|
+
{ workspaceId, itemId },
|
|
788
|
+
"remove cart item"
|
|
789
|
+
);
|
|
790
|
+
return data.removeCartItem;
|
|
791
|
+
}
|
|
792
|
+
async function backendClearCart(config, ctx) {
|
|
793
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
794
|
+
const data = await request(
|
|
795
|
+
config,
|
|
796
|
+
ctx,
|
|
797
|
+
workspaceId,
|
|
798
|
+
CLEAR_CART,
|
|
799
|
+
{ workspaceId },
|
|
800
|
+
"clear cart"
|
|
801
|
+
);
|
|
802
|
+
return data.clearCart;
|
|
803
|
+
}
|
|
804
|
+
async function backendApplyDiscount(config, ctx, code) {
|
|
805
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
806
|
+
const data = await request(
|
|
807
|
+
config,
|
|
808
|
+
ctx,
|
|
809
|
+
workspaceId,
|
|
810
|
+
APPLY_DISCOUNT,
|
|
811
|
+
{ workspaceId, code },
|
|
812
|
+
"apply discount"
|
|
813
|
+
);
|
|
814
|
+
return data.applyDiscount;
|
|
815
|
+
}
|
|
816
|
+
async function backendRemoveDiscount(config, ctx) {
|
|
817
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
818
|
+
const data = await request(
|
|
819
|
+
config,
|
|
820
|
+
ctx,
|
|
821
|
+
workspaceId,
|
|
822
|
+
REMOVE_DISCOUNT,
|
|
823
|
+
{ workspaceId },
|
|
824
|
+
"remove discount"
|
|
825
|
+
);
|
|
826
|
+
return data.removeDiscount;
|
|
827
|
+
}
|
|
828
|
+
async function backendCheckout(config, ctx, customerEmail) {
|
|
829
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
830
|
+
const data = await request(
|
|
831
|
+
config,
|
|
832
|
+
ctx,
|
|
833
|
+
workspaceId,
|
|
834
|
+
CHECKOUT,
|
|
835
|
+
{ input: { workspaceId, customerEmail } },
|
|
836
|
+
"checkout"
|
|
837
|
+
);
|
|
838
|
+
return data.checkout;
|
|
839
|
+
}
|
|
840
|
+
async function backendProduct(config, ctx, modelSlug, filter) {
|
|
841
|
+
const workspaceId = await workspaceIdFor2(config);
|
|
842
|
+
const data = await request(
|
|
843
|
+
config,
|
|
844
|
+
ctx,
|
|
845
|
+
workspaceId,
|
|
846
|
+
PRODUCT,
|
|
847
|
+
{ workspaceId, modelSlug, filter },
|
|
848
|
+
"product query"
|
|
849
|
+
);
|
|
850
|
+
return data.publicModelRecords.items[0] ?? null;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/create-cart-route.ts
|
|
854
|
+
var CMSSY_CART_COOKIE = "cmssy_cart";
|
|
855
|
+
var CART_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
|
|
856
|
+
var CART_TOKEN_BYTES = 32;
|
|
857
|
+
var MAX_BODY_CHARS2 = 16 * 1024;
|
|
858
|
+
function json2(body, status = 200) {
|
|
859
|
+
return new Response(JSON.stringify(body), {
|
|
860
|
+
status,
|
|
861
|
+
headers: {
|
|
862
|
+
"content-type": "application/json",
|
|
863
|
+
"cache-control": "no-store"
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
function cartCookieOptions() {
|
|
868
|
+
return {
|
|
869
|
+
httpOnly: true,
|
|
870
|
+
secure: process.env.NODE_ENV !== "development",
|
|
871
|
+
sameSite: "lax",
|
|
872
|
+
path: "/",
|
|
873
|
+
maxAge: CART_MAX_AGE_SECONDS
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function mintToken() {
|
|
877
|
+
const bytes = new Uint8Array(CART_TOKEN_BYTES);
|
|
878
|
+
crypto.getRandomValues(bytes);
|
|
879
|
+
let binary = "";
|
|
880
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
881
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
882
|
+
}
|
|
883
|
+
async function readBody2(request2) {
|
|
884
|
+
const contentType = request2.headers.get("content-type") ?? "";
|
|
885
|
+
if (!contentType.toLowerCase().includes("application/json")) {
|
|
886
|
+
throw new Error("content-type must be application/json");
|
|
887
|
+
}
|
|
888
|
+
const text = await request2.text();
|
|
889
|
+
if (text.length > MAX_BODY_CHARS2) throw new Error("body too large");
|
|
890
|
+
if (!text) return {};
|
|
891
|
+
const parsed = JSON.parse(text);
|
|
892
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
893
|
+
throw new Error("body must be a JSON object");
|
|
894
|
+
}
|
|
895
|
+
return parsed;
|
|
896
|
+
}
|
|
897
|
+
function str2(value) {
|
|
898
|
+
return typeof value === "string" ? value : "";
|
|
899
|
+
}
|
|
900
|
+
function plainObject2(value) {
|
|
901
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
902
|
+
return value;
|
|
903
|
+
}
|
|
904
|
+
function createCmssyCartRoute(config) {
|
|
905
|
+
async function ensureCartToken() {
|
|
906
|
+
const jar = await cookies();
|
|
907
|
+
const existing = jar.get(CMSSY_CART_COOKIE)?.value;
|
|
908
|
+
if (existing) return existing;
|
|
909
|
+
const token = mintToken();
|
|
910
|
+
jar.set(CMSSY_CART_COOKIE, token, cartCookieOptions());
|
|
911
|
+
return token;
|
|
912
|
+
}
|
|
913
|
+
async function clearCartToken() {
|
|
914
|
+
const jar = await cookies();
|
|
915
|
+
jar.set(CMSSY_CART_COOKIE, "", { ...cartCookieOptions(), maxAge: 0 });
|
|
916
|
+
}
|
|
917
|
+
async function memberAccessToken() {
|
|
918
|
+
if (!config.auth) return void 0;
|
|
919
|
+
const jar = await cookies();
|
|
920
|
+
const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
|
|
921
|
+
if (!raw) return void 0;
|
|
922
|
+
const session = await openSession(
|
|
923
|
+
raw,
|
|
924
|
+
config.auth.sessionSecret,
|
|
925
|
+
config.workspaceSlug
|
|
926
|
+
);
|
|
927
|
+
if (!session || isAccessExpired(session)) return void 0;
|
|
928
|
+
return session.accessToken;
|
|
929
|
+
}
|
|
930
|
+
async function buildContext() {
|
|
931
|
+
const cartToken = await ensureCartToken();
|
|
932
|
+
const accessToken = await memberAccessToken();
|
|
933
|
+
return accessToken ? { cartToken, accessToken } : { cartToken };
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
async POST(request2, context) {
|
|
937
|
+
const { action } = await context.params;
|
|
938
|
+
let body;
|
|
939
|
+
try {
|
|
940
|
+
body = await readBody2(request2);
|
|
941
|
+
} catch {
|
|
942
|
+
return json2({ message: "Invalid request body." }, 400);
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
const ctx = await buildContext();
|
|
946
|
+
switch (action) {
|
|
947
|
+
case "cart":
|
|
948
|
+
return json2({ cart: await backendGetCart(config, ctx) });
|
|
949
|
+
case "add":
|
|
950
|
+
return json2({
|
|
951
|
+
cart: await backendAddToCart(config, ctx, {
|
|
952
|
+
recordId: str2(body.recordId),
|
|
953
|
+
quantity: typeof body.quantity === "number" ? body.quantity : 1,
|
|
954
|
+
variantSelections: body.variantSelections,
|
|
955
|
+
notes: typeof body.notes === "string" ? body.notes : void 0
|
|
956
|
+
})
|
|
957
|
+
});
|
|
958
|
+
case "update":
|
|
959
|
+
return json2({
|
|
960
|
+
cart: await backendUpdateItem(config, ctx, {
|
|
961
|
+
itemId: str2(body.itemId),
|
|
962
|
+
quantity: typeof body.quantity === "number" ? body.quantity : 0
|
|
963
|
+
})
|
|
964
|
+
});
|
|
965
|
+
case "remove":
|
|
966
|
+
return json2({
|
|
967
|
+
cart: await backendRemoveItem(config, ctx, str2(body.itemId))
|
|
968
|
+
});
|
|
969
|
+
case "clear":
|
|
970
|
+
return json2({ cart: await backendClearCart(config, ctx) });
|
|
971
|
+
case "apply-discount":
|
|
972
|
+
return json2({
|
|
973
|
+
cart: await backendApplyDiscount(config, ctx, str2(body.code))
|
|
974
|
+
});
|
|
975
|
+
case "remove-discount":
|
|
976
|
+
return json2({ cart: await backendRemoveDiscount(config, ctx) });
|
|
977
|
+
case "checkout": {
|
|
978
|
+
const order = await backendCheckout(
|
|
979
|
+
config,
|
|
980
|
+
ctx,
|
|
981
|
+
str2(body.customerEmail)
|
|
982
|
+
);
|
|
983
|
+
await clearCartToken();
|
|
984
|
+
return json2({ order });
|
|
985
|
+
}
|
|
986
|
+
case "product":
|
|
987
|
+
return json2({
|
|
988
|
+
product: await backendProduct(
|
|
989
|
+
config,
|
|
990
|
+
ctx,
|
|
991
|
+
str2(body.modelSlug),
|
|
992
|
+
plainObject2(body.filter)
|
|
993
|
+
)
|
|
994
|
+
});
|
|
995
|
+
default:
|
|
996
|
+
return json2({ message: "Not found." }, 404);
|
|
997
|
+
}
|
|
998
|
+
} catch (err) {
|
|
999
|
+
return json2(
|
|
1000
|
+
{
|
|
1001
|
+
message: err instanceof Error ? err.message : "Commerce request failed"
|
|
1002
|
+
},
|
|
1003
|
+
502
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
684
1009
|
async function readValidSession(config) {
|
|
685
1010
|
const auth = assertAuthConfig(config);
|
|
686
1011
|
const jar = await cookies();
|
|
@@ -702,13 +1027,13 @@ async function getCmssyAccessToken(config) {
|
|
|
702
1027
|
const session = await readValidSession(config);
|
|
703
1028
|
return session?.accessToken ?? null;
|
|
704
1029
|
}
|
|
705
|
-
function isPrefetch(
|
|
706
|
-
return
|
|
1030
|
+
function isPrefetch(request2) {
|
|
1031
|
+
return request2.headers.get("next-router-prefetch") !== null || request2.headers.get("purpose") === "prefetch" || (request2.headers.get("sec-purpose") ?? "").includes("prefetch");
|
|
707
1032
|
}
|
|
708
1033
|
function createCmssyAuthMiddleware(config) {
|
|
709
1034
|
const auth = assertAuthConfig(config);
|
|
710
|
-
return async function cmssyAuthMiddleware(
|
|
711
|
-
const raw =
|
|
1035
|
+
return async function cmssyAuthMiddleware(request2) {
|
|
1036
|
+
const raw = request2.cookies.get(CMSSY_SESSION_COOKIE)?.value;
|
|
712
1037
|
if (!raw) return NextResponse.next();
|
|
713
1038
|
const session = await openSession(
|
|
714
1039
|
raw,
|
|
@@ -724,7 +1049,7 @@ function createCmssyAuthMiddleware(config) {
|
|
|
724
1049
|
return response;
|
|
725
1050
|
}
|
|
726
1051
|
if (!isAccessExpired(session)) return NextResponse.next();
|
|
727
|
-
if (isPrefetch(
|
|
1052
|
+
if (isPrefetch(request2)) return NextResponse.next();
|
|
728
1053
|
let payload = null;
|
|
729
1054
|
try {
|
|
730
1055
|
const result = await backendRefresh(config, session.refreshToken);
|
|
@@ -745,11 +1070,11 @@ function createCmssyAuthMiddleware(config) {
|
|
|
745
1070
|
auth.sessionSecret,
|
|
746
1071
|
config.workspaceSlug
|
|
747
1072
|
);
|
|
748
|
-
|
|
749
|
-
const refreshed = NextResponse.next({ request });
|
|
1073
|
+
request2.cookies.set(CMSSY_SESSION_COOKIE, sealed);
|
|
1074
|
+
const refreshed = NextResponse.next({ request: request2 });
|
|
750
1075
|
refreshed.cookies.set(CMSSY_SESSION_COOKIE, sealed, sessionCookieOptions());
|
|
751
1076
|
return refreshed;
|
|
752
1077
|
};
|
|
753
1078
|
}
|
|
754
1079
|
|
|
755
|
-
export { CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyPage, createDraftRoute, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, sealSession, sessionCookieOptions, splitCmssyLocale };
|
|
1080
|
+
export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyPage, createDraftRoute, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, sealSession, sessionCookieOptions, splitCmssyLocale };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Next.js App Router bindings for cmssy headless sites (createCmssyPage + draft preview)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@cmssy/react": "^0.
|
|
39
|
+
"@cmssy/react": "^0.2.0",
|
|
40
40
|
"next": ">=15",
|
|
41
41
|
"react": "^18.2.0 || ^19.0.0",
|
|
42
42
|
"react-dom": "^18.2.0 || ^19.0.0"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"tsup": "^8.3.0",
|
|
50
50
|
"typescript": "^5.6.0",
|
|
51
51
|
"vitest": "^2.1.0",
|
|
52
|
-
"@cmssy/react": "0.
|
|
52
|
+
"@cmssy/react": "0.2.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"jose": "^6.2.3"
|