@vtex/faststore-plugin-buyer-portal 2.0.31 → 2.0.33

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/faststore-plugin-buyer-portal",
3
- "version": "2.0.31",
3
+ "version": "2.0.33",
4
4
  "description": "A plugin for faststore with buyer portal",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -27,7 +27,9 @@ export const tokenizeCreditCardService = async ({
27
27
  complement: creditCard.address.streetAddress2,
28
28
  coutry: creditCard.address.countryCode,
29
29
  neighborhood: creditCard.address.neighborhood ?? "",
30
- geoCoordinates: creditCard.address.geoCoordinates.split(",") ?? [],
30
+ geoCoordinates: creditCard.address.geoCoordinates
31
+ ? creditCard.address.geoCoordinates.split(",")
32
+ : [],
31
33
  postalCode: creditCard.address.zip,
32
34
  number: creditCard.address.streetNumber ?? "",
33
35
  receiverName: "NOT EMPTY",
@@ -1,9 +1,14 @@
1
+ import { getApiUrl } from "../utils/api";
2
+ import { CURRENT_VERSION } from "../utils/constants";
1
3
  import {
2
4
  getAuthFromCookie,
3
5
  getCookieWithoutSessionObjects,
4
6
  } from "../utils/cookie";
5
7
  import { toQueryParams } from "../utils/toQueryParams";
6
8
 
9
+ const BUYER_PORTAL_CLIENT_HEADER = "x-buyer-portal-client";
10
+ const BUYER_PORTAL_CLIENT = `organization-account/${CURRENT_VERSION}`;
11
+
7
12
  interface RequestConfig<Input = unknown> {
8
13
  url: string;
9
14
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
@@ -48,8 +53,13 @@ export class ClientError extends Error {
48
53
  class Client {
49
54
  private baseURL: string;
50
55
 
56
+ // Other hosts (saved cards, tokenization, postal code) would have to allow
57
+ // the header in their CORS preflight.
58
+ private isBffClient: boolean;
59
+
51
60
  constructor(baseURL = "") {
52
61
  this.baseURL = baseURL;
62
+ this.isBffClient = baseURL.startsWith(getApiUrl());
53
63
  }
54
64
 
55
65
  private createClientError(
@@ -86,11 +96,20 @@ class Client {
86
96
  cache = "no-cache",
87
97
  componentName,
88
98
  }: RequestConfig<Input>): Promise<Return> {
99
+ // fetch joins names that differ only in case into one comma-separated
100
+ // value, which the BFF rejects.
101
+ const callerSetsClientHeader = Object.keys(headers).some(
102
+ (name) => name.toLowerCase() === BUYER_PORTAL_CLIENT_HEADER
103
+ );
104
+
89
105
  const config: RequestInit = {
90
106
  method,
91
107
  credentials: "include",
92
108
  headers: {
93
109
  ...(ignoreContentType ? {} : { "Content-Type": "application/json" }),
110
+ ...(this.isBffClient && !callerSetsClientHeader
111
+ ? { [BUYER_PORTAL_CLIENT_HEADER]: BUYER_PORTAL_CLIENT }
112
+ : {}),
94
113
  ...(headers.Cookie
95
114
  ? getCookieWithoutSessionObjects(headers.Cookie ?? "")
96
115
  : {}),
@@ -7,8 +7,18 @@ vi.mock("discovery.config", () => ({
7
7
  },
8
8
  }));
9
9
 
10
+ import { getApiUrl } from "../../utils/api";
11
+ import { CURRENT_VERSION } from "../../utils/constants";
10
12
  import { Client, ClientError } from "../Client";
11
13
 
14
+ const CLIENT_HEADER = "x-buyer-portal-client";
15
+
16
+ function sentHeaders(fetchMock: ReturnType<typeof makeFetchMock>) {
17
+ const [, init] = fetchMock.mock.calls[0] as [string, RequestInit];
18
+
19
+ return init.headers as Record<string, string>;
20
+ }
21
+
12
22
  function makeFetchMock(
13
23
  body: unknown,
14
24
  status = 200,
@@ -65,4 +75,63 @@ describe("Client", () => {
65
75
  await expect(client.get("resource")).resolves.toEqual({ ok: true });
66
76
  });
67
77
  });
78
+
79
+ describe("x-buyer-portal-client", () => {
80
+ it("identifies the plugin on BFF requests", async () => {
81
+ const fetchMock = makeFetchMock({ ok: true });
82
+ vi.stubGlobal("fetch", fetchMock);
83
+
84
+ await new Client(getApiUrl()).get("units/unit-1/summary");
85
+
86
+ expect(sentHeaders(fetchMock)).toMatchObject({
87
+ "Content-Type": "application/json",
88
+ [CLIENT_HEADER]: `organization-account/${CURRENT_VERSION}`,
89
+ });
90
+ });
91
+
92
+ it("sends it next to the cookie and auth headers on server-side requests", async () => {
93
+ const fetchMock = makeFetchMock({ ok: true });
94
+ vi.stubGlobal("fetch", fetchMock);
95
+
96
+ await new Client(getApiUrl()).get("granted", {
97
+ headers: {
98
+ Cookie: "VtexIdclientAutCookie_test-store=token-value;other=1",
99
+ },
100
+ });
101
+
102
+ expect(sentHeaders(fetchMock)).toMatchObject({
103
+ [CLIENT_HEADER]: `organization-account/${CURRENT_VERSION}`,
104
+ "VtexIdclientAutCookie_test-store": "token-value",
105
+ Cookie: "VtexIdclientAutCookie_test-store=token-value;other=1",
106
+ });
107
+ });
108
+
109
+ it.each(["", "https://test.vtex.com/api/", "https://api.example.com/"])(
110
+ "is not sent to base URL %j",
111
+ async (baseURL) => {
112
+ const fetchMock = makeFetchMock({ ok: true });
113
+ vi.stubGlobal("fetch", fetchMock);
114
+
115
+ await new Client(baseURL).get("resource");
116
+
117
+ expect(sentHeaders(fetchMock)).not.toHaveProperty(CLIENT_HEADER);
118
+ }
119
+ );
120
+
121
+ it.each([CLIENT_HEADER, "X-Buyer-Portal-Client"])(
122
+ "lets a header passed by the caller as %s win",
123
+ async (name) => {
124
+ const fetchMock = makeFetchMock({ ok: true });
125
+ vi.stubGlobal("fetch", fetchMock);
126
+
127
+ await new Client(getApiUrl()).get("resource", {
128
+ headers: { [name]: "organization-account" },
129
+ });
130
+
131
+ expect(new Headers(sentHeaders(fetchMock)).get(CLIENT_HEADER)).toBe(
132
+ "organization-account"
133
+ );
134
+ }
135
+ );
136
+ });
68
137
  });
@@ -33,4 +33,4 @@ export const STOREFRONT_RESOURCE_NAMES = {
33
33
  export type StorefrontResourceName =
34
34
  (typeof STOREFRONT_RESOURCE_NAMES)[keyof typeof STOREFRONT_RESOURCE_NAMES];
35
35
 
36
- export const CURRENT_VERSION = "2.0.31";
36
+ export const CURRENT_VERSION = "2.0.33";