@faststore/api 1.12.50 → 1.12.52
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/api.cjs.development.js +302 -12
- package/dist/api.cjs.development.js.map +1 -1
- package/dist/api.cjs.production.min.js +1 -1
- package/dist/api.cjs.production.min.js.map +1 -1
- package/dist/api.esm.js +302 -12
- package/dist/api.esm.js.map +1 -1
- package/dist/platforms/vtex/clients/commerce/index.d.ts +16 -2
- package/dist/platforms/vtex/clients/fetch.d.ts +6 -1
- package/dist/platforms/vtex/index.d.ts +5 -0
- package/dist/platforms/vtex/utils/cookies.d.ts +52 -0
- package/package.json +2 -2
- package/src/platforms/vtex/clients/commerce/index.ts +121 -7
- package/src/platforms/vtex/clients/fetch.ts +65 -2
- package/src/platforms/vtex/index.ts +6 -0
- package/src/platforms/vtex/resolvers/subscribeToNewsletter.ts +27 -1
- package/src/platforms/vtex/utils/cookies.ts +157 -0
package/dist/api.esm.js
CHANGED
|
@@ -18,7 +18,7 @@ import { trace, context, SpanKind } from '@opentelemetry/api';
|
|
|
18
18
|
import { SeverityNumber } from '@opentelemetry/api-logs';
|
|
19
19
|
|
|
20
20
|
var name = "@faststore/api";
|
|
21
|
-
var version = "1.12.
|
|
21
|
+
var version = "1.12.51";
|
|
22
22
|
var license = "MIT";
|
|
23
23
|
var main = "dist/index.js";
|
|
24
24
|
var typings = "dist/index.d.ts";
|
|
@@ -87,8 +87,39 @@ var packageJson = {
|
|
|
87
87
|
peerDependencies: peerDependencies
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
const USER_AGENT = `${packageJson.name}@${packageJson.version}`;
|
|
91
|
-
|
|
90
|
+
const USER_AGENT = `${packageJson.name}@${packageJson.version}`; // Headers that may carry credentials: store API keys (`X-VTEX-API-AppKey`,
|
|
91
|
+
// `X-VTEX-API-AppToken`), shopper cookies (`Cookie`, `Set-Cookie`,
|
|
92
|
+
// `VtexIdclientAutCookie_*`), `Authorization`, and generic token, secret or
|
|
93
|
+
// API key headers. They are replaced before a failed request and its response
|
|
94
|
+
// are logged. Matched by substring so new credential headers are covered too.
|
|
95
|
+
|
|
96
|
+
const SENSITIVE_HEADER = /authorization|cookie|token|secret|api-?key|appkey/i;
|
|
97
|
+
const REDACTED = '[redacted]';
|
|
98
|
+
const redactHeaders = headers => {
|
|
99
|
+
if (!headers) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const redacted = {};
|
|
104
|
+
|
|
105
|
+
const add = (name, value) => {
|
|
106
|
+
redacted[name] = SENSITIVE_HEADER.test(name) ? REDACTED : value;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (Array.isArray(headers)) {
|
|
110
|
+
headers.forEach(([name, value]) => add(name, value));
|
|
111
|
+
} else if (typeof headers.forEach === 'function') {
|
|
112
|
+
// Matches both the global `Headers` and the polyfill returned by
|
|
113
|
+
// `isomorphic-unfetch` on Node, which isn't an instance of the global one.
|
|
114
|
+
const headerList = headers;
|
|
115
|
+
headerList.forEach((value, name) => add(name, value));
|
|
116
|
+
} else {
|
|
117
|
+
Object.entries(headers).forEach(([name, value]) => add(name, value));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return redacted;
|
|
121
|
+
};
|
|
122
|
+
const fetchAPI = async (info, init, options) => {
|
|
92
123
|
const response = await fetch(info, { ...init,
|
|
93
124
|
headers: { ...(init == null ? void 0 : init.headers),
|
|
94
125
|
'User-Agent': USER_AGENT
|
|
@@ -96,25 +127,200 @@ const fetchAPI = async (info, init) => {
|
|
|
96
127
|
});
|
|
97
128
|
|
|
98
129
|
if (response.ok) {
|
|
130
|
+
if (options != null && options.storeCookies) {
|
|
131
|
+
options.storeCookies(response.headers);
|
|
132
|
+
}
|
|
133
|
+
|
|
99
134
|
return response.status !== 204 ? response.json() : undefined;
|
|
100
|
-
}
|
|
135
|
+
} // Log a summary instead of the raw Response, whose headers may include
|
|
136
|
+
// `Set-Cookie` with shopper credentials. The request body is omitted because
|
|
137
|
+
// it can carry personal data (e.g. the newsletter email and name).
|
|
138
|
+
|
|
101
139
|
|
|
102
|
-
console.error(
|
|
140
|
+
console.error( // Only the URL: a `Request` instance could carry unredacted headers.
|
|
141
|
+
typeof info === 'string' ? info : info.url, init && { ...init,
|
|
142
|
+
headers: redactHeaders(init.headers),
|
|
143
|
+
body: init.body ? REDACTED : undefined
|
|
144
|
+
}, {
|
|
145
|
+
status: response.status,
|
|
146
|
+
statusText: response.statusText,
|
|
147
|
+
headers: redactHeaders(response.headers)
|
|
148
|
+
});
|
|
103
149
|
const text = await response.text();
|
|
104
150
|
throw new Error(text);
|
|
105
151
|
};
|
|
106
152
|
|
|
153
|
+
const MATCH_FIRST_SET_COOKIE_KEY_VALUE = /^([^=]+)=([^;]*)/;
|
|
154
|
+
/**
|
|
155
|
+
* Splits a combined `set-cookie` header value (cookies joined by `, `) into
|
|
156
|
+
* individual cookies. Commas inside `expires=...` dates are kept.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
const splitSetCookieHeader = value => value.split(/,(?=\s*[^;,=\s]+=)/).map(setCookie => setCookie.trim());
|
|
160
|
+
/**
|
|
161
|
+
* Returns every `set-cookie` value of a fetch response.
|
|
162
|
+
*
|
|
163
|
+
* `Headers.getSetCookie()` only exists on Node >= 18.14 (undici). FastStore v1
|
|
164
|
+
* stores may still run on Node 16, where isomorphic-unfetch falls back to
|
|
165
|
+
* node-fetch v2, which exposes repeated headers through `raw()` instead.
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
const getSetCookies = headers => {
|
|
170
|
+
const maybeHeaders = headers;
|
|
171
|
+
|
|
172
|
+
if (typeof maybeHeaders.getSetCookie === 'function') {
|
|
173
|
+
return maybeHeaders.getSetCookie();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (typeof maybeHeaders.raw === 'function') {
|
|
177
|
+
var _maybeHeaders$raw$set;
|
|
178
|
+
|
|
179
|
+
return (_maybeHeaders$raw$set = maybeHeaders.raw()['set-cookie']) != null ? _maybeHeaders$raw$set : [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const setCookie = headers.get('set-cookie');
|
|
183
|
+
return setCookie ? splitSetCookieHeader(setCookie) : [];
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* This function updates the ctx.storage.cookies, that is used in each request.
|
|
187
|
+
*
|
|
188
|
+
* ctx.storage.cookies is a Map<string[1], Record<string, string>[2]> where,
|
|
189
|
+
* [1] cookie key
|
|
190
|
+
* [2] {
|
|
191
|
+
* value: cookie value,
|
|
192
|
+
* setCookie: setCookie used in browser response
|
|
193
|
+
* }
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
const updatesContextStorageCookies = (ctx, setCookieValue) => {
|
|
197
|
+
const matchCookie = setCookieValue.match(MATCH_FIRST_SET_COOKIE_KEY_VALUE);
|
|
198
|
+
|
|
199
|
+
if (matchCookie) {
|
|
200
|
+
const cookieKey = matchCookie[1];
|
|
201
|
+
const cookieValue = matchCookie[2];
|
|
202
|
+
ctx.storage.cookies.set(cookieKey, {
|
|
203
|
+
value: cookieValue,
|
|
204
|
+
setCookie: setCookieValue
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const setCookie = (ctx, headers) => {
|
|
209
|
+
getSetCookies(headers).forEach(setCookieValue => updatesContextStorageCookies(ctx, setCookieValue));
|
|
210
|
+
};
|
|
211
|
+
const getStoreCookie = ctx => headers => setCookie(ctx, headers);
|
|
212
|
+
/**
|
|
213
|
+
* This function returns a modified copy of the original cookie header (ctx.headers.cookie from the first request)
|
|
214
|
+
* with the cookie values that comes in each request (ctx.storage.cookies).
|
|
215
|
+
* If there is no cookies in storage, the ctx.headers?.cookie is used
|
|
216
|
+
*
|
|
217
|
+
* ctx.storage.cookies is a Map<string[1], Record<string, string>[2]> where,
|
|
218
|
+
* [1] cookie key
|
|
219
|
+
* [2] {
|
|
220
|
+
* value: cookie value,
|
|
221
|
+
* setCookie: setCookie used in browser response
|
|
222
|
+
* }
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
const getUpdatedCookie = ctx => {
|
|
226
|
+
var _ctx$headers;
|
|
227
|
+
|
|
228
|
+
if (!((_ctx$headers = ctx.headers) != null && _ctx$headers.cookie)) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const contextStorageCookies = Array.from(ctx.storage.cookies.entries());
|
|
233
|
+
|
|
234
|
+
if (contextStorageCookies.length === 0) {
|
|
235
|
+
return ctx.headers.cookie;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return contextStorageCookies.reduce((existingCookies, [storageCookieKey, {
|
|
239
|
+
value: storageCookieValue
|
|
240
|
+
}]) => updatesCookieValueByKey(existingCookies, storageCookieKey, storageCookieValue), ctx.headers.cookie);
|
|
241
|
+
};
|
|
242
|
+
const getWithCookie = ctx => function withCookie(headers) {
|
|
243
|
+
const updatedCookie = getUpdatedCookie(ctx);
|
|
244
|
+
|
|
245
|
+
if (!updatedCookie) {
|
|
246
|
+
return headers;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { ...headers,
|
|
250
|
+
cookie: updatedCookie
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* This function updates the cookie value based on its key
|
|
255
|
+
*
|
|
256
|
+
* const existingCookies = 'key=value1; key2=; key3=value3';
|
|
257
|
+
* const storageCookieKey = 'key2';
|
|
258
|
+
* const storageCookieValue = 'value2'
|
|
259
|
+
*
|
|
260
|
+
* updatesCookieValueByKey(existingCookies, storageCookieKey, storageCookieValue) returns 'key=value1; key2=value2; key3=value3';
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
const updatesCookieValueByKey = (existingCookies, storageCookieKey, storageCookieValue) => {
|
|
264
|
+
const MATCH_COOKIE_KEY_VALUE = new RegExp(`(${storageCookieKey})=([^;]*)`);
|
|
265
|
+
const cookieParts = existingCookies.match(MATCH_COOKIE_KEY_VALUE); // replaces original cookie with the one coming from storage
|
|
266
|
+
|
|
267
|
+
if (cookieParts) {
|
|
268
|
+
return existingCookies.replace(cookieParts[0], `${cookieParts[1]}=${storageCookieValue}`);
|
|
269
|
+
} // add new storage cookie to the original list of cookies
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
return `${existingCookies};${storageCookieKey}=${storageCookieValue}`;
|
|
273
|
+
};
|
|
274
|
+
|
|
107
275
|
const BASE_INIT = {
|
|
108
276
|
method: 'POST',
|
|
109
277
|
headers: {
|
|
110
278
|
'content-type': 'application/json'
|
|
111
279
|
}
|
|
280
|
+
}; // Accounts already warned that they rely on public `NL` fields. The commerce
|
|
281
|
+
// client is created per request, so the warning is kept once per account
|
|
282
|
+
// instead of per instance, to avoid flooding the logs. The account comes from
|
|
283
|
+
// the store's API options, not from the request, so the set holds at most one
|
|
284
|
+
// entry per account the process is configured for (usually one). It's still
|
|
285
|
+
// capped, so it can't grow without bound if that ever changes.
|
|
286
|
+
|
|
287
|
+
const MAX_WARNED_NEWSLETTER_ACCOUNTS = 100;
|
|
288
|
+
const accountsWarnedAboutAnonymousNewsletter = /*#__PURE__*/new Set();
|
|
289
|
+
|
|
290
|
+
const getHeader = (headers, name) => {
|
|
291
|
+
const value = headers == null ? void 0 : headers[name];
|
|
292
|
+
return Array.isArray(value) ? value[0] : value;
|
|
112
293
|
};
|
|
294
|
+
/**
|
|
295
|
+
* Host the Checkout API uses as the domain of the cookies it sets. Checkout
|
|
296
|
+
* only honors it for hosts registered in the account, so a forged value is
|
|
297
|
+
* ignored there.
|
|
298
|
+
*/
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
const getForwardedHost = headers => {
|
|
302
|
+
var _getHeader;
|
|
303
|
+
|
|
304
|
+
// Proxies may append values: "client, proxy". Only the first one is the store host.
|
|
305
|
+
const host = ((_getHeader = getHeader(headers, 'x-forwarded-host')) == null ? void 0 : _getHeader.split(',')[0].trim()) || getHeader(headers, 'host') || '';
|
|
306
|
+
return host.trim().replace(/^www\./, '');
|
|
307
|
+
};
|
|
308
|
+
|
|
113
309
|
const VtexCommerce = ({
|
|
114
310
|
account,
|
|
115
311
|
environment
|
|
116
312
|
}, ctx) => {
|
|
117
313
|
const base = `https://${account}.${environment}.com.br`;
|
|
314
|
+
const storeCookies = getStoreCookie(ctx);
|
|
315
|
+
const withCookie = getWithCookie(ctx);
|
|
316
|
+
const forwardedHost = getForwardedHost(ctx.headers); // Checkout calls forward the shopper cookies and keep the cookies Checkout
|
|
317
|
+
// sets (e.g. CheckoutOrderFormOwnership) so they can reach the browser.
|
|
318
|
+
|
|
319
|
+
const checkoutHeaders = () => withCookie({
|
|
320
|
+
'content-type': 'application/json',
|
|
321
|
+
'X-FORWARDED-HOST': forwardedHost
|
|
322
|
+
});
|
|
323
|
+
|
|
118
324
|
return {
|
|
119
325
|
catalog: {
|
|
120
326
|
salesChannel: sc => fetchAPI(`${base}/api/catalog_system/pub/saleschannel/${sc}`),
|
|
@@ -165,7 +371,10 @@ const VtexCommerce = ({
|
|
|
165
371
|
}
|
|
166
372
|
|
|
167
373
|
return fetchAPI(`${base}/api/checkout/pub/orderForm/${id}/attachments/shippingData`, { ...BASE_INIT,
|
|
374
|
+
headers: checkoutHeaders(),
|
|
168
375
|
body: JSON.stringify(body)
|
|
376
|
+
}, {
|
|
377
|
+
storeCookies
|
|
169
378
|
});
|
|
170
379
|
},
|
|
171
380
|
orderForm: ({
|
|
@@ -180,7 +389,11 @@ const VtexCommerce = ({
|
|
|
180
389
|
refreshOutdatedData: refreshOutdatedData.toString(),
|
|
181
390
|
sc: salesChannel
|
|
182
391
|
});
|
|
183
|
-
return fetchAPI(`${base}/api/checkout/pub/orderForm/${id}?${params.toString()}`, BASE_INIT
|
|
392
|
+
return fetchAPI(`${base}/api/checkout/pub/orderForm/${id}?${params.toString()}`, { ...BASE_INIT,
|
|
393
|
+
headers: checkoutHeaders()
|
|
394
|
+
}, {
|
|
395
|
+
storeCookies
|
|
396
|
+
});
|
|
184
397
|
},
|
|
185
398
|
updateOrderFormItems: ({
|
|
186
399
|
id,
|
|
@@ -194,11 +407,14 @@ const VtexCommerce = ({
|
|
|
194
407
|
sc: salesChannel
|
|
195
408
|
});
|
|
196
409
|
return fetchAPI(`${base}/api/checkout/pub/orderForm/${id}/items?${params}`, { ...BASE_INIT,
|
|
410
|
+
headers: checkoutHeaders(),
|
|
197
411
|
body: JSON.stringify({
|
|
198
412
|
orderItems,
|
|
199
413
|
noSplitItem: !shouldSplitItem
|
|
200
414
|
}),
|
|
201
415
|
method: 'PATCH'
|
|
416
|
+
}, {
|
|
417
|
+
storeCookies
|
|
202
418
|
});
|
|
203
419
|
},
|
|
204
420
|
setCustomData: ({
|
|
@@ -208,10 +424,13 @@ const VtexCommerce = ({
|
|
|
208
424
|
value
|
|
209
425
|
}) => {
|
|
210
426
|
return fetchAPI(`${base}/api/checkout/pub/orderForm/${id}/customData/${appId}/${key}`, { ...BASE_INIT,
|
|
427
|
+
headers: checkoutHeaders(),
|
|
211
428
|
body: JSON.stringify({
|
|
212
429
|
value
|
|
213
430
|
}),
|
|
214
431
|
method: 'PUT'
|
|
432
|
+
}, {
|
|
433
|
+
storeCookies
|
|
215
434
|
});
|
|
216
435
|
},
|
|
217
436
|
region: async ({
|
|
@@ -240,11 +459,58 @@ const VtexCommerce = ({
|
|
|
240
459
|
body: '{}'
|
|
241
460
|
});
|
|
242
461
|
},
|
|
243
|
-
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Creates a newsletter subscription in the `NL` Master Data entity.
|
|
465
|
+
*
|
|
466
|
+
* With `FS_NEWSLETTER_APP_KEY`/`FS_NEWSLETTER_APP_TOKEN` set, the document is
|
|
467
|
+
* created with a `POST` authenticated by that dedicated key, so `NL` fields
|
|
468
|
+
* can stay private. The request is create-only: the body is built here from
|
|
469
|
+
* the validated fields and never carries a document id, so the caller can't
|
|
470
|
+
* reach an existing record.
|
|
471
|
+
*
|
|
472
|
+
* Without the credentials, it falls back to the legacy anonymous `PATCH`,
|
|
473
|
+
* which only works while `NL` fields are public.
|
|
474
|
+
*/
|
|
475
|
+
subscribeToNewsletter: ({
|
|
476
|
+
name,
|
|
477
|
+
email
|
|
478
|
+
}) => {
|
|
479
|
+
const body = JSON.stringify({ ...(name ? {
|
|
480
|
+
name
|
|
481
|
+
} : {}),
|
|
482
|
+
email,
|
|
483
|
+
isNewsletterOptIn: true
|
|
484
|
+
});
|
|
485
|
+
const appKey = process.env.FS_NEWSLETTER_APP_KEY;
|
|
486
|
+
const appToken = process.env.FS_NEWSLETTER_APP_TOKEN;
|
|
487
|
+
|
|
488
|
+
if (appKey && appToken) {
|
|
489
|
+
return fetchAPI(`${base}/api/dataentities/NL/documents`, {
|
|
490
|
+
method: 'POST',
|
|
491
|
+
headers: {
|
|
492
|
+
'content-type': 'application/json',
|
|
493
|
+
accept: 'application/json',
|
|
494
|
+
'X-VTEX-API-AppKey': appKey,
|
|
495
|
+
'X-VTEX-API-AppToken': appToken
|
|
496
|
+
},
|
|
497
|
+
body
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!accountsWarnedAboutAnonymousNewsletter.has(account)) {
|
|
502
|
+
if (accountsWarnedAboutAnonymousNewsletter.size >= MAX_WARNED_NEWSLETTER_ACCOUNTS) {
|
|
503
|
+
accountsWarnedAboutAnonymousNewsletter.clear();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
accountsWarnedAboutAnonymousNewsletter.add(account); // Name only the missing variables, so a partial setup is easy to spot.
|
|
507
|
+
|
|
508
|
+
const missing = [!appKey && 'FS_NEWSLETTER_APP_KEY', !appToken && 'FS_NEWSLETTER_APP_TOKEN'].filter(Boolean);
|
|
509
|
+
console.warn(`[subscribeToNewsletter] ${missing.join(' and ')} ${missing.length > 1 ? 'are' : 'is'} not set. Falling back to an anonymous Master Data request, which requires public fields in the NL entity. Configure both credentials and make the NL fields private.`);
|
|
510
|
+
}
|
|
511
|
+
|
|
244
512
|
return fetchAPI(`${base}/api/dataentities/NL/documents/`, { ...BASE_INIT,
|
|
245
|
-
body
|
|
246
|
-
isNewsletterOptIn: true
|
|
247
|
-
}),
|
|
513
|
+
body,
|
|
248
514
|
method: 'PATCH'
|
|
249
515
|
});
|
|
250
516
|
}
|
|
@@ -1036,6 +1302,11 @@ const StoreFacetValueBoolean = {
|
|
|
1036
1302
|
}) => quantity
|
|
1037
1303
|
};
|
|
1038
1304
|
|
|
1305
|
+
const EMAIL_MAX_LENGTH = 254;
|
|
1306
|
+
const NAME_MAX_LENGTH = 100; // Character classes exclude `.`, so each domain label is matched once and the
|
|
1307
|
+
// pattern stays linear on long inputs.
|
|
1308
|
+
|
|
1309
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/;
|
|
1039
1310
|
const subscribeToNewsletter = async (_, {
|
|
1040
1311
|
data
|
|
1041
1312
|
}, {
|
|
@@ -1043,7 +1314,25 @@ const subscribeToNewsletter = async (_, {
|
|
|
1043
1314
|
commerce
|
|
1044
1315
|
}
|
|
1045
1316
|
}) => {
|
|
1046
|
-
|
|
1317
|
+
var _data$email$trim$toLo, _data$email, _data$name$trim, _data$name;
|
|
1318
|
+
|
|
1319
|
+
// The subscription is written with a store credential, so only validated
|
|
1320
|
+
// values reach Master Data.
|
|
1321
|
+
const email = (_data$email$trim$toLo = (_data$email = data.email) == null ? void 0 : _data$email.trim().toLowerCase()) != null ? _data$email$trim$toLo : '';
|
|
1322
|
+
const name = (_data$name$trim = (_data$name = data.name) == null ? void 0 : _data$name.trim()) != null ? _data$name$trim : '';
|
|
1323
|
+
|
|
1324
|
+
if (email.length > EMAIL_MAX_LENGTH || !EMAIL_PATTERN.test(email)) {
|
|
1325
|
+
throw new BadRequestError('Invalid email');
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (name.length > NAME_MAX_LENGTH) {
|
|
1329
|
+
throw new BadRequestError(`Invalid name: must have at most ${NAME_MAX_LENGTH} characters`);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
const response = await commerce.subscribeToNewsletter({
|
|
1333
|
+
email,
|
|
1334
|
+
name: name || undefined
|
|
1335
|
+
});
|
|
1047
1336
|
return {
|
|
1048
1337
|
id: response == null ? void 0 : response.Id
|
|
1049
1338
|
};
|
|
@@ -2464,7 +2753,8 @@ const getContextFactory = options => ctx => {
|
|
|
2464
2753
|
ctx.storage = {
|
|
2465
2754
|
channel: ChannelMarshal.parse(options.channel),
|
|
2466
2755
|
flags: (_options$flags = options.flags) != null ? _options$flags : {},
|
|
2467
|
-
locale: options.locale
|
|
2756
|
+
locale: options.locale,
|
|
2757
|
+
cookies: new Map()
|
|
2468
2758
|
};
|
|
2469
2759
|
ctx.clients = getClients(options, ctx);
|
|
2470
2760
|
ctx.loaders = getLoaders(options, ctx);
|