Skip to content
Open
52 changes: 52 additions & 0 deletions vtex/actions/cart/updateCustomData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { AppContext } from "../../mod.ts";
import { proxySetCookie } from "../../utils/cookies.ts";
import { parseCookie } from "../../utils/orderForm.ts";
import type { OrderForm } from "../../utils/types.ts";

export interface Props {
appId: string;
// deno-lint-ignore no-explicit-any
body: any;
}

/**
* @title Update Custom Data
* @description Update the custom data in the cart
*/
const action = async (
props: Props,
req: Request,
ctx: AppContext,
): Promise<OrderForm> => {
const { vcsDeprecated } = ctx;
const {
appId,
body,
} = props;
const { orderFormId } = parseCookie(req.headers);

if (!orderFormId || orderFormId === "") {
throw new Error("Order form ID is required");
}

const cookie = req.headers.get("cookie") ?? "";

const response = await vcsDeprecated
["PUT /api/checkout/pub/orderForm/:orderFormId/customData/:appId"]({
orderFormId,
appId,
}, {
body,
headers: {
accept: "application/json",
"content-type": "application/json",
cookie,
},
});

proxySetCookie(response.headers, ctx.response.headers, req.url);

return response.json();
};

export default action;
61 changes: 61 additions & 0 deletions vtex/actions/events/productView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { AppContext } from "../../mod.ts";
import { getOrigin, parseCookie } from "../../utils/recommendations.ts";
import type { ProductViewSource } from "../../utils/types.ts";

interface Props {
/**
* @title User ID
* @description The ID of the user who viewed the recommendation.
*/
userId?: string;
/**
* @title Product ID
* @description The ID of the product that was clicked.
*/
Comment on lines +12 to +14

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix product-view prop description typo.

Line 13 says the product was “clicked”, but this loader emits a product-view event.

✏️ Suggested edit
-   * `@description` The ID of the product that was clicked.
+   * `@description` The ID of the product that was viewed.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* @title Product ID
* @description The ID of the product that was clicked.
*/
* `@title` Product ID
* `@description` The ID of the product that was viewed.
*/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vtex/actions/events/productView.ts` around lines 12 - 14, Update the JSDoc
for the "Product ID" prop in the product-view loader so the description reflects
a view event rather than a click: locate the comment block with `@title` "Product
ID" in productView.ts (the product-view loader) and change the phrase "The ID of
the product that was clicked." to something like "The ID of the product that was
viewed." to match the emitted product-view event.

productId: string;
/**
* @title Source
* @description The source of the product view event.
*/
source: ProductViewSource;
/**
* @description The origin of the recommendation request. E.g: apiexamples/storefront/vtex.recommendation-shelf@2.x
*/
"x-vtex-rec-origin"?: string;
}

export default async function loader(
props: Props,
req: Request,
ctx: AppContext,
) {
const { bff } = ctx;

const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]);
if (!origin) {
throw new Error("x-vtex-rec-origin header is required");
}

const cookies = parseCookie(req.headers);
const userId = props.userId ?? cookies?.userId;

if (!userId) {
throw new Error("userId is required");
}

await bff["POST /api/recommend-bff/v2/events/product-view"]({
an: ctx.account,
}, {
body: {
userId,
product: props.productId,
source: props.source,
},
headers: {
"x-vtex-rec-origin": origin,
"user-agent": req.headers.get("user-agent") || "",
},
});

return { ok: true };
}
59 changes: 59 additions & 0 deletions vtex/actions/events/recommendationClick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { AppContext } from "../../mod.ts";
import { getOrigin, parseCookie } from "../../utils/recommendations.ts";

interface Props {
/**
* @title User ID
* @description The ID of the user who viewed the recommendation.
*/
userId?: string;
/**
* @title Product ID
* @description The ID of the product that was clicked.
*/
productId: string;
/**
* @title Correlation ID
* @description The correlation ID of the recommendation request.
*/
correlationId: string;
/**
* @description The origin of the recommendation request. E.g: apiexamples/storefront/vtex.recommendation-shelf@2.x
*/
"x-vtex-rec-origin"?: string;
}

export default async function loader(
props: Props,
req: Request,
ctx: AppContext,
) {
const { bff } = ctx;

const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]);
if (!origin) {
throw new Error("x-vtex-rec-origin header is required");
}

const cookies = parseCookie(req.headers);
const userId = props.userId ?? cookies?.userId;

if (!userId) {
throw new Error("userId is required");
}

await bff["POST /api/recommend-bff/v2/events/recommendation-click"]({
an: ctx.account,
}, {
body: {
correlationId: props.correlationId,
userId,
product: props.productId,
},
headers: {
"x-vtex-rec-origin": origin,
},
});

return { ok: true };
}
59 changes: 59 additions & 0 deletions vtex/actions/events/recommendationView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { AppContext } from "../../mod.ts";
import { getOrigin, parseCookie } from "../../utils/recommendations.ts";

interface Props {
/**
* @title User ID
* @description The ID of the user who viewed the recommendation.
*/
userId?: string;
/**
* @title Correlation ID
* @description The correlation ID of the recommendation request.
*/
correlationId: string;
/**
* @title Products
* @description The products SKU IDs that were viewed.
*/
products: string[];
/**
* @description The origin of the recommendation request. E.g: apiexamples/storefront/vtex.recommendation-shelf@2.x
*/
"x-vtex-rec-origin"?: string;
}

export default async function loader(
props: Props,
req: Request,
ctx: AppContext,
) {
const { bff } = ctx;

const origin = getOrigin(req, ctx.account, props["x-vtex-rec-origin"]);
if (!origin) {
throw new Error("x-vtex-rec-origin header is required");
}

const cookies = parseCookie(req.headers);
const userId = props.userId ?? cookies?.userId;

if (!userId) {
throw new Error("userId is required");
}

await bff["POST /api/recommend-bff/v2/events/recommendation-view"]({
an: ctx.account,
}, {
body: {
correlationId: props.correlationId,
userId,
products: props.products,
},
headers: {
"x-vtex-rec-origin": origin,
},
});

return { ok: true };
}
43 changes: 43 additions & 0 deletions vtex/actions/recommendation/startSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getSetCookies } from "std/http/cookie.ts";
import type { AppContext } from "../../mod.ts";
import { proxySetCookie } from "../../utils/cookies.ts";
import { parseCookie } from "../../utils/orderForm.ts";

export default async function action(
_: unknown,
req: Request,
ctx: AppContext,
) {
const { bff } = ctx;
const { orderFormId } = parseCookie(req.headers);

const url = new URL(req.url);
const host = url.host;

const headers = new Headers();
headers.set(
"x-vtex-rec-origin",
`${ctx.account}/storefront/deco.recommendations@1.x`,
);
Comment on lines +21 to +25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Respect caller-provided x-vtex-rec-origin.

The PR contract says callers can send {account}/{source}/{app}, but Lines 21-25 always overwrite that with the default value. That loses source/app attribution for any non-default integration. Prefer the inbound header and only fall back to the default when it is missing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vtex/actions/recommendation/startSession.ts` around lines 21 - 25, The code
unconditionally overwrites the caller-provided x-vtex-rec-origin header; change
it to respect an incoming header first by reading the request header (e.g.,
ctx.request.headers.get('x-vtex-rec-origin') or equivalent) and only set
headers.set('x-vtex-rec-origin',
`${ctx.account}/storefront/deco.recommendations@1.x`) when that incoming value
is missing/empty. Update the logic around the local headers variable in
startSession (where headers is created) to use the existing header value if
present, otherwise fall back to the default constructed from ctx.account and
storefront/deco.recommendations@1.x.

headers.set("x-forwarded-host", host);
headers.set("host", `${ctx.account}.vtexcommercestable.com.br`);

const cookie = req.headers.get("cookie");
if (cookie) {
headers.set("cookie", cookie);
}

const response = await bff["POST /api/recommend-bff/v2/users/start-session"]({
an: ctx.account,
}, {
body: { orderFormId },
headers,
});

const data = await response.json();

console.log(data, getSetCookies(response.headers));
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
proxySetCookie(response.headers, ctx.response.headers, req.url);

return data;
}
Loading
Loading