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