@exdst-sitecore-content-sdk/astro 0.0.1

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.
Files changed (87) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +3 -0
  3. package/package.json +101 -0
  4. package/src/client/index.ts +12 -0
  5. package/src/client/sitecore-astro-client.test.ts +271 -0
  6. package/src/client/sitecore-astro-client.ts +137 -0
  7. package/src/components/AstroImage.astro +114 -0
  8. package/src/components/Date.astro +76 -0
  9. package/src/components/DefaultEmptyFieldEditingComponentImage.astro +24 -0
  10. package/src/components/DefaultEmptyFieldEditingComponentText.astro +12 -0
  11. package/src/components/EditingScripts.astro +49 -0
  12. package/src/components/EmptyRendering.astro +3 -0
  13. package/src/components/ErrorBoundary.astro +77 -0
  14. package/src/components/FieldMetadata.astro +30 -0
  15. package/src/components/File.astro +46 -0
  16. package/src/components/HiddenRendering.astro +22 -0
  17. package/src/components/Image.astro +155 -0
  18. package/src/components/Link.astro +105 -0
  19. package/src/components/MissingComponent.astro +39 -0
  20. package/src/components/Placeholder/EmptyPlaceholder.astro +9 -0
  21. package/src/components/Placeholder/Placeholder.astro +100 -0
  22. package/src/components/Placeholder/PlaceholderMetadata.astro +102 -0
  23. package/src/components/Placeholder/PlaceholderUtils.astro +153 -0
  24. package/src/components/Placeholder/index.ts +5 -0
  25. package/src/components/Placeholder/models.ts +82 -0
  26. package/src/components/Placeholder/placeholder-utils.test.ts +162 -0
  27. package/src/components/Placeholder/placeholder-utils.ts +80 -0
  28. package/src/components/RenderWrapper.astro +31 -0
  29. package/src/components/RichText.astro +59 -0
  30. package/src/components/Text.astro +97 -0
  31. package/src/components/sharedTypes/index.ts +1 -0
  32. package/src/components/sharedTypes/props.ts +17 -0
  33. package/src/config/define-config.test.ts +526 -0
  34. package/src/config/define-config.ts +99 -0
  35. package/src/config/index.ts +1 -0
  36. package/src/config-cli/define-cli-config.test.ts +95 -0
  37. package/src/config-cli/define-cli-config.ts +50 -0
  38. package/src/config-cli/index.ts +1 -0
  39. package/src/context.ts +68 -0
  40. package/src/editing/constants.ts +8 -0
  41. package/src/editing/editing-config-middleware.test.ts +166 -0
  42. package/src/editing/editing-config-middleware.ts +111 -0
  43. package/src/editing/editing-render-middleware.test.ts +801 -0
  44. package/src/editing/editing-render-middleware.ts +288 -0
  45. package/src/editing/index.ts +16 -0
  46. package/src/editing/render-middleware.test.ts +57 -0
  47. package/src/editing/render-middleware.ts +51 -0
  48. package/src/editing/utils.test.ts +852 -0
  49. package/src/editing/utils.ts +308 -0
  50. package/src/enhancers/WithEmptyFieldEditingComponent.astro +56 -0
  51. package/src/enhancers/WithFieldMetadata.astro +31 -0
  52. package/src/env.d.ts +12 -0
  53. package/src/index.ts +16 -0
  54. package/src/middleware/index.ts +24 -0
  55. package/src/middleware/middleware.test.ts +507 -0
  56. package/src/middleware/middleware.ts +167 -0
  57. package/src/middleware/multisite-middleware.test.ts +672 -0
  58. package/src/middleware/multisite-middleware.ts +147 -0
  59. package/src/middleware/robots-middleware.test.ts +113 -0
  60. package/src/middleware/robots-middleware.ts +47 -0
  61. package/src/middleware/sitemap-middleware.test.ts +152 -0
  62. package/src/middleware/sitemap-middleware.ts +65 -0
  63. package/src/services/component-props-service.ts +182 -0
  64. package/src/sharedTypes/component-props.ts +17 -0
  65. package/src/site/index.ts +1 -0
  66. package/src/test-data/components/Bar.astro +0 -0
  67. package/src/test-data/components/Baz.astro +0 -0
  68. package/src/test-data/components/Foo.astro +0 -0
  69. package/src/test-data/components/Hero.variant.astro +0 -0
  70. package/src/test-data/components/NotComponent.bsx +0 -0
  71. package/src/test-data/components/Qux.astro +0 -0
  72. package/src/test-data/components/folded/Folded.astro +0 -0
  73. package/src/test-data/components/folded/random-file-2.docx +0 -0
  74. package/src/test-data/components/random-file.txt +0 -0
  75. package/src/test-data/helpers.ts +46 -0
  76. package/src/test-data/personalizeData.ts +63 -0
  77. package/src/tools/generate-map.ts +83 -0
  78. package/src/tools/index.ts +8 -0
  79. package/src/tools/templating/components.test.ts +305 -0
  80. package/src/tools/templating/components.ts +49 -0
  81. package/src/tools/templating/constants.ts +4 -0
  82. package/src/tools/templating/default-component.test.ts +31 -0
  83. package/src/tools/templating/default-component.ts +63 -0
  84. package/src/tools/templating/index.ts +2 -0
  85. package/src/utils/index.ts +1 -0
  86. package/src/utils/utils.test.ts +48 -0
  87. package/src/utils/utils.ts +52 -0
@@ -0,0 +1,507 @@
1
+ /* eslint-disable dot-notation */
2
+ /* eslint-disable no-unused-expressions, @typescript-eslint/no-unused-expressions */
3
+ import * as chai from 'chai';
4
+ import { use } from 'chai';
5
+ import sinonChai from 'sinon-chai';
6
+ import sinon from 'sinon';
7
+ import chaiString from 'chai-string';
8
+ import { MiddlewareBase, REWRITE_HEADER_NAME } from './middleware';
9
+ import { SiteResolver } from '../site';
10
+ import { PreviewCookies } from '../editing';
11
+ import { APIContext, AstroCookieSetOptions, MiddlewareHandler, RewritePayload } from 'astro';
12
+
13
+ use(sinonChai);
14
+ const expect = chai.use(chaiString).expect;
15
+
16
+ class MockSiteResolver extends SiteResolver {
17
+ getByName = sinon.stub().callsFake((siteName: string) => ({
18
+ name: siteName,
19
+ language: 'en',
20
+ hostName: 'foo.net',
21
+ }));
22
+
23
+ getByHost = sinon.stub().callsFake((hostName: string) => ({
24
+ name: 'foo',
25
+ language: 'en',
26
+ hostName,
27
+ }));
28
+ }
29
+
30
+ const createContext = (props: any = {}) => {
31
+ const context = {
32
+ request: {
33
+ url: props?.url,
34
+ headers: {
35
+ get(key: string) {
36
+ const headers = {
37
+ ...context.request.headers,
38
+ ...props?.headerValues,
39
+ };
40
+ return headers[key];
41
+ },
42
+ append(key: string, value: string | Record<string, any>) {
43
+ context.request.headers[key] = value;
44
+ },
45
+ },
46
+ },
47
+ cookies: {
48
+ get(cookieName: string) {
49
+ const cookies = { ...props?.cookieValues };
50
+ return cookies[cookieName] ? { value: cookies[cookieName] } : undefined;
51
+ },
52
+ set(
53
+ cookieName: string,
54
+ value: string | Record<string, any>,
55
+ options?: AstroCookieSetOptions
56
+ ) {
57
+ context.cookies[cookieName] = { value, ...options };
58
+ },
59
+ ...props?.cookies,
60
+ ...props.cookieValues,
61
+ },
62
+ locals: props.locals || {},
63
+ url: props?.url,
64
+ currentLocale: props.currentLocale,
65
+ preferredLocale: props.preferredLocale,
66
+ // eslint-disable-next-line no-unused-vars
67
+ rewrite: (_) => props.response,
68
+ } as APIContext;
69
+
70
+ return context;
71
+ };
72
+
73
+ const createResponse = (props: any = {}) => {
74
+ const response = {
75
+ ...props,
76
+ url: props.url,
77
+ headers: {
78
+ get(key: string) {
79
+ const headers = {
80
+ ...response.headers,
81
+ ...props.headerValues,
82
+ };
83
+ return headers[key];
84
+ },
85
+ append(key: string, value: string | Record<string, any>) {
86
+ response.headers[key] = value;
87
+ },
88
+ ...props.headers,
89
+ },
90
+ } as Response;
91
+
92
+ return response;
93
+ };
94
+
95
+ describe('MiddlewareBase', () => {
96
+ class SampleMiddleware extends MiddlewareBase {
97
+ handle: MiddlewareHandler = () => {};
98
+ }
99
+
100
+ describe('defaultHostname', () => {
101
+ it('should set default hostname', () => {
102
+ const middleware = new SampleMiddleware({ sites: [] });
103
+
104
+ expect(middleware['defaultHostname']).to.equal('localhost');
105
+ });
106
+
107
+ it('should set custom hostname', () => {
108
+ const middleware = new SampleMiddleware({
109
+ sites: [],
110
+ defaultHostname: 'foo',
111
+ });
112
+
113
+ expect(middleware['defaultHostname']).to.equal('foo');
114
+ });
115
+ });
116
+
117
+ describe('isPreview', () => {
118
+ it('should return true when preview data cookie is provided', () => {
119
+ const middleware = new SampleMiddleware({ sites: [] });
120
+ const context = createContext({
121
+ cookieValues: {
122
+ [PreviewCookies.PREVIEW_DATA]: 'value',
123
+ },
124
+ });
125
+
126
+ expect(middleware['isPreview'](context)).to.equal(true);
127
+ });
128
+
129
+ it('should return false when required cookie is not provided', () => {
130
+ const middleware = new SampleMiddleware({ sites: [] });
131
+ const context = createContext({});
132
+
133
+ expect(middleware['isPreview'](context)).to.equal(false);
134
+ });
135
+ });
136
+ /*
137
+ describe('isPrefetch', () => {
138
+ it('should return true when purpose header is prefetch', () => {
139
+ const middleware = new SampleMiddleware({ sites: [] });
140
+ const req = createReq({
141
+ headerValues: {
142
+ purpose: 'prefetch',
143
+ },
144
+ });
145
+
146
+ expect(middleware['isPrefetch'](req)).to.equal(true);
147
+ });
148
+
149
+ it('should return true when Next-Router-Prefetch header is 1', () => {
150
+ const middleware = new SampleMiddleware({ sites: [] });
151
+ const req = createReq({
152
+ headerValues: {
153
+ 'Next-Router-Prefetch': '1',
154
+ },
155
+ });
156
+
157
+ expect(middleware['isPrefetch'](req)).to.equal(true);
158
+ });
159
+
160
+ it('should return true when x-middleware-prefetch header is 1', () => {
161
+ const middleware = new SampleMiddleware({ sites: [] });
162
+ const req = createReq({
163
+ headerValues: {
164
+ 'x-middleware-prefetch': '1',
165
+ },
166
+ });
167
+
168
+ expect(middleware['isPrefetch'](req)).to.equal(true);
169
+ });
170
+
171
+ it('should return false when required header is not provided', () => {
172
+ const middleware = new SampleMiddleware({ sites: [] });
173
+ const req = createReq();
174
+
175
+ expect(middleware['isPrefetch'](req)).to.equal(false);
176
+ });
177
+
178
+ it('returns false for known device with x-middleware-prefetch header', () => {
179
+ const middleware = new SampleMiddleware({ sites: [] });
180
+ const req = createReq({
181
+ headerValues: {
182
+ 'x-middleware-prefetch': '1',
183
+ 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X)',
184
+ },
185
+ });
186
+
187
+ expect(middleware['isPrefetch'](req)).to.equal(false);
188
+ });
189
+
190
+ it('should return true when it is a desktop device and purpose is prefetch', () => {
191
+ const middleware = new SampleMiddleware({ sites: [] });
192
+ const req = createReq({
193
+ headerValues: {
194
+ purpose: 'prefetch',
195
+ 'sec-ch-ua-mobile': '?0',
196
+ },
197
+ });
198
+
199
+ expect(middleware['isPrefetch'](req)).to.equal(true);
200
+ });
201
+ });
202
+ */
203
+ describe('disabled / skip', () => {
204
+ it('default', () => {
205
+ const middleware = new SampleMiddleware({ sites: [] });
206
+
207
+ expect(
208
+ middleware['disabled'](
209
+ createContext({
210
+ url: {
211
+ pathname: '/api/layout/render',
212
+ },
213
+ }),
214
+ createResponse()
215
+ )
216
+ ).to.equal(true);
217
+
218
+ expect(
219
+ middleware['disabled'](
220
+ createContext({
221
+ url: {
222
+ pathname: '/sitecore/render',
223
+ },
224
+ }),
225
+ createResponse()
226
+ )
227
+ ).to.equal(true);
228
+ });
229
+
230
+ it('custom function', () => {
231
+ const middleware = new SampleMiddleware({
232
+ sites: [],
233
+ skip(context: APIContext) {
234
+ return context.url.pathname === 'foo';
235
+ },
236
+ });
237
+
238
+ expect(
239
+ middleware['disabled'](
240
+ createContext({
241
+ url: {
242
+ pathname: 'bar',
243
+ },
244
+ }),
245
+ createResponse()
246
+ )
247
+ ).to.equal(false);
248
+ expect(
249
+ middleware['disabled'](
250
+ createContext({
251
+ url: {
252
+ pathname: 'foo',
253
+ },
254
+ }),
255
+ createResponse()
256
+ )
257
+ ).to.equal(true);
258
+ });
259
+ });
260
+
261
+ describe('disabled in chain', () => {
262
+ it('should return false if skipMiddleware local variable is not set or false', () => {
263
+ const middleware = new SampleMiddleware({ sites: [] });
264
+
265
+ expect(
266
+ middleware['disabledInChain'](
267
+ createContext({
268
+ url: {
269
+ pathname: '/api/layout/render',
270
+ },
271
+ locals: {
272
+ skipMiddleware: false,
273
+ },
274
+ }),
275
+ createResponse()
276
+ )
277
+ ).to.equal(false);
278
+
279
+ expect(
280
+ middleware['disabledInChain'](
281
+ createContext({
282
+ url: {
283
+ pathname: '/api/layout/render',
284
+ },
285
+ }),
286
+ createResponse()
287
+ )
288
+ ).to.equal(false);
289
+ });
290
+
291
+ it('should return true if skipMiddleware local variable is true', () => {
292
+ const middleware = new SampleMiddleware({ sites: [] });
293
+
294
+ expect(
295
+ middleware['disabledInChain'](
296
+ createContext({
297
+ url: {
298
+ pathname: '/api/layout/render',
299
+ },
300
+ locals: {
301
+ skipMiddleware: true,
302
+ },
303
+ }),
304
+ createResponse()
305
+ )
306
+ ).to.equal(true);
307
+ });
308
+ });
309
+
310
+ it('extractDebugHeaders', () => {
311
+ const middleware = new SampleMiddleware({ sites: [] });
312
+
313
+ const headers = new Headers({});
314
+ headers.set('foo', 'net');
315
+ headers.set('bar', 'one');
316
+
317
+ expect(middleware['extractDebugHeaders'](headers)).to.deep.equal({
318
+ foo: 'net',
319
+ bar: 'one',
320
+ });
321
+ });
322
+
323
+ describe('getHostHeader', () => {
324
+ it('should return default hostname when header is not present', () => {
325
+ const middleware = new SampleMiddleware({ sites: [] });
326
+ const context = createContext({
327
+ headerValues: {
328
+ foo: 'one',
329
+ },
330
+ });
331
+
332
+ expect(middleware['getHostHeader'](context)).to.equal(undefined);
333
+ });
334
+
335
+ it('should return host header', () => {
336
+ const middleware = new SampleMiddleware({ sites: [] });
337
+ const context = createContext({
338
+ headerValues: {
339
+ foo: 'one',
340
+ host: 'bar.net:9999',
341
+ },
342
+ });
343
+
344
+ expect(middleware['getHostHeader'](context)).to.equal('bar.net');
345
+ });
346
+ });
347
+
348
+ describe('getLanguage', () => {
349
+ it('should return defined language', () => {
350
+ const middleware = new SampleMiddleware({ sites: [] });
351
+ const context = createContext({
352
+ currentLocale: 'be',
353
+ preferredLocale: 'fr',
354
+ });
355
+
356
+ expect(middleware['getLanguage'](context)).to.equal('be');
357
+ });
358
+
359
+ it('should return defined default language', () => {
360
+ const middleware = new SampleMiddleware({ sites: [] });
361
+ const context = createContext({
362
+ preferredLocale: 'fr',
363
+ });
364
+
365
+ expect(middleware['getLanguage'](context)).to.equal('fr');
366
+ });
367
+
368
+ it('should use fallback language from config when present', () => {
369
+ const middleware = new SampleMiddleware({
370
+ sites: [],
371
+ defaultLanguage: 'es-ES',
372
+ });
373
+ const context = createContext();
374
+
375
+ expect(middleware['getLanguage'](context)).to.equal('es-ES');
376
+ });
377
+
378
+ it('should return fallback language', () => {
379
+ const middleware = new SampleMiddleware({ sites: [] });
380
+ const context = createContext();
381
+
382
+ expect(middleware['getLanguage'](context)).to.equal('en');
383
+ });
384
+ });
385
+
386
+ describe('getSite', () => {
387
+ it('should get site by name when site cookie is provided', () => {
388
+ const context = createContext();
389
+
390
+ const res = createResponse({
391
+ headers: {
392
+ 'Set-Cookie': 'sc_site=xxx',
393
+ },
394
+ });
395
+
396
+ const middleware = new SampleMiddleware({ sites: [] });
397
+ middleware['siteResolver'] = new MockSiteResolver([]);
398
+
399
+ expect(middleware['getSite'](context, res).name).to.equal('xxx');
400
+ expect(middleware['siteResolver'].getByName).to.be.calledWith('xxx');
401
+ });
402
+
403
+ it('should get default site info when site cookie is provided', () => {
404
+ class MockSiteResolver extends SiteResolver {
405
+ // eslint-disable-next-line no-unused-vars
406
+ getByName = sinon.stub().callsFake((_siteName: string) => undefined);
407
+ }
408
+
409
+ const context = createContext();
410
+
411
+ const res = createResponse({
412
+ headers: {
413
+ 'Set-Cookie': 'sc_site=xxx',
414
+ },
415
+ });
416
+
417
+ const middleware = new SampleMiddleware({ sites: [] });
418
+ middleware['siteResolver'] = new MockSiteResolver([]);
419
+
420
+ expect(middleware['getSite'](context, res)).deep.equal({
421
+ name: 'xxx',
422
+ language: 'en',
423
+ hostName: '*',
424
+ });
425
+ expect(middleware['siteResolver'].getByName).to.be.calledWith('xxx');
426
+ });
427
+ });
428
+
429
+ it('should get site by host header', () => {
430
+ const context = createContext({
431
+ headerValues: {
432
+ host: 'xxx.net:9999',
433
+ },
434
+ });
435
+ const middleware = new SampleMiddleware({ sites: [] });
436
+ middleware['siteResolver'] = new MockSiteResolver([]);
437
+
438
+ expect(middleware['getSite'](context).hostName).to.equal('xxx.net');
439
+ expect(middleware['siteResolver'].getByHost).to.be.calledWith('xxx.net');
440
+ });
441
+
442
+ it('should get site by default host', () => {
443
+ const context = createContext();
444
+ const middleware = new SampleMiddleware({ sites: [] });
445
+ middleware['siteResolver'] = new MockSiteResolver([]);
446
+
447
+ expect(middleware['getSite'](context).hostName).to.equal('localhost');
448
+ expect(middleware['siteResolver'].getByHost).to.be.calledWith('localhost');
449
+ });
450
+
451
+ it('should get site by custom default host', () => {
452
+ const context = createContext();
453
+
454
+ const middleware = new SampleMiddleware({
455
+ sites: [],
456
+ defaultHostname: 'yyy.net',
457
+ });
458
+ middleware['siteResolver'] = new MockSiteResolver([]);
459
+
460
+ expect(middleware['getSite'](context).hostName).to.equal('yyy.net');
461
+ expect(middleware['siteResolver'].getByHost).to.be.calledWith('yyy.net');
462
+ });
463
+
464
+ describe('rewrite', () => {
465
+ it('should add header by default', async () => {
466
+ const middleware = new SampleMiddleware({ sites: [] });
467
+ const url = new URL('http://localhost:3000/not-found');
468
+
469
+ const context = createContext({
470
+ url: url,
471
+ });
472
+
473
+ const mockNext = async (rewritePath?: RewritePayload) => {
474
+ return createResponse({
475
+ url: rewritePath,
476
+ headers: [],
477
+ });
478
+ };
479
+
480
+ const response = await middleware['rewrite']('/new', context, mockNext);
481
+
482
+ expect(response.headers.get(REWRITE_HEADER_NAME)).to.equal('/new');
483
+ expect(response.url.toString()).to.endWith('/new');
484
+ });
485
+
486
+ it('should not rewrite header when skipHeader is true', async () => {
487
+ const middleware = new SampleMiddleware({ sites: [] });
488
+ const url = new URL('http://localhost:3000/not-found');
489
+
490
+ const context = createContext({
491
+ url: url,
492
+ });
493
+
494
+ const mockNext = async (rewritePath?: RewritePayload) => {
495
+ return createResponse({
496
+ url: rewritePath,
497
+ headers: [],
498
+ });
499
+ };
500
+
501
+ const response = await middleware['rewrite']('/new', context, mockNext, true);
502
+
503
+ expect(response.headers.get(REWRITE_HEADER_NAME)).to.be.undefined;
504
+ expect(response.url.toString()).to.endWith('/new');
505
+ });
506
+ });
507
+ });
@@ -0,0 +1,167 @@
1
+ import { SITE_KEY, SiteInfo, SiteResolver } from '@sitecore-content-sdk/core/site';
2
+ import { GraphQLRequestClientFactory } from '@sitecore-content-sdk/core';
3
+ import {
4
+ createGraphQLClientFactory,
5
+ GraphQLClientOptions,
6
+ } from '@sitecore-content-sdk/core/client';
7
+ import { PreviewCookies } from '../editing';
8
+ import { APIContext, MiddlewareHandler, MiddlewareNext } from 'astro';
9
+ import * as cookie from 'cookie';
10
+
11
+ export const REWRITE_HEADER_NAME = 'x-sc-rewrite';
12
+
13
+ export type MiddlewareBaseConfig = {
14
+ /**
15
+ * function, determines if middleware execution should be skipped, based on cookie, header, or other considerations
16
+ * @param {APIContext} context the Astro context
17
+ */
18
+ skip?: (context: APIContext) => boolean;
19
+ /**
20
+ * Fallback hostname in case `host` header is not present
21
+ * @default localhost
22
+ */
23
+ defaultHostname?: string;
24
+ /**
25
+ * Fallback language in locale cannot be extracted from request URL
26
+ * @default 'en'
27
+ */
28
+ defaultLanguage?: string;
29
+ /**
30
+ * Site resolution implementation by name/hostname
31
+ */
32
+ sites: SiteInfo[];
33
+ };
34
+
35
+ /**
36
+ * Middleware class to be extended by all middleware implementations
37
+ */
38
+ export abstract class Middleware {
39
+ /**
40
+ * Handler method to execute middleware logic
41
+ * @param {MiddlewareHandler} handler MiddlewareHandler
42
+ */
43
+ abstract handle: MiddlewareHandler;
44
+ }
45
+
46
+ /**
47
+ * Base middleware class with common methods
48
+ */
49
+ export abstract class MiddlewareBase extends Middleware {
50
+ protected defaultHostname: string;
51
+ protected siteResolver: SiteResolver;
52
+
53
+ constructor(protected config: MiddlewareBaseConfig) {
54
+ super();
55
+ this.siteResolver = new SiteResolver(config.sites);
56
+ this.defaultHostname = config.defaultHostname || 'localhost';
57
+ }
58
+
59
+ /**
60
+ * Determines if mode is preview
61
+ * @param {APIContext} context Astro context
62
+ * @returns {boolean} is preview
63
+ */
64
+ protected isPreview(context: APIContext) {
65
+ return !!context.cookies.get(PreviewCookies.PREVIEW_DATA);
66
+ }
67
+
68
+ protected disabled(context: APIContext) {
69
+ const { pathname } = context.url;
70
+
71
+ return (
72
+ pathname.startsWith('/api/') || // Ignore API calls
73
+ pathname.startsWith('/sitecore/') || // Ignore Sitecore API calls
74
+ (this.config.skip && this.config.skip(context))
75
+ );
76
+ }
77
+
78
+ protected disabledInChain(context: APIContext) {
79
+ return context.locals.skipMiddleware || false; // Skip if disabled in one of the previous middlewares in chain
80
+ }
81
+
82
+ /**
83
+ * Safely extract all headers for debug logging
84
+ * Necessary to avoid middleware issue https://github.com/vercel/next.js/issues/39765
85
+ * @param {Headers} incomingHeaders Incoming headers
86
+ * @returns Object with headers as key/value pairs
87
+ */
88
+ protected extractDebugHeaders(incomingHeaders: Headers) {
89
+ const headers = {} as { [key: string]: string };
90
+ incomingHeaders.forEach((value, key) => (headers[key] = value));
91
+ return headers;
92
+ }
93
+
94
+ /**
95
+ * Provides used language
96
+ * @param {APIContext} context Astro context
97
+ * @returns {string} language
98
+ */
99
+ protected getLanguage(context: APIContext) {
100
+ return context.currentLocale || context.preferredLocale || this.config.defaultLanguage || 'en';
101
+ }
102
+
103
+ /**
104
+ * Extract 'host' header
105
+ * @param {APIContext} context Astro context
106
+ */
107
+ protected getHostHeader(context: APIContext) {
108
+ return context.request.headers.get('host')?.split(':')[0];
109
+ }
110
+
111
+ /**
112
+ * Get site information. If site name is stored in cookie, use it, otherwise resolve by hostname
113
+ * - If site can't be resolved by site name cookie use default site info based on provided parameters
114
+ * - If site can't be resolved by hostname throw an error
115
+ * @param {APIContext} context Astro context
116
+ * @param {Response} [res] response
117
+ * @returns {SiteInfo} site information
118
+ */
119
+ protected getSite(context: APIContext, res?: Response): SiteInfo {
120
+ const siteNameCookie = cookie.parse(res?.headers.get('Set-Cookie') || '')[SITE_KEY];
121
+ const hostname = this.getHostHeader(context) || this.defaultHostname;
122
+
123
+ if (siteNameCookie) {
124
+ // Usually we should be able to resolve site by cookie
125
+ // in case of Sitecore Preview mode, there can be a case that new site was created
126
+ // but it's not present in the sitemap, so we fallback to default site info
127
+ return (
128
+ this.siteResolver.getByName(siteNameCookie) || {
129
+ name: siteNameCookie,
130
+ language: this.getLanguage(context),
131
+ hostName: '*',
132
+ }
133
+ );
134
+ }
135
+
136
+ return this.siteResolver.getByHost(hostname);
137
+ }
138
+
139
+ protected getClientFactory(graphQLOptions: GraphQLClientOptions): GraphQLRequestClientFactory {
140
+ return createGraphQLClientFactory(graphQLOptions);
141
+ }
142
+
143
+ /**
144
+ * Create a rewrite response
145
+ * @param {string} rewritePath the destionation path
146
+ * @param {APIContext} context the middleware context
147
+ * @param {MiddlewareNext} next the middleware object to execute rewrite
148
+ * @param {boolean} [skipHeader] don't write 'x-sc-rewrite' header
149
+ */
150
+ protected async rewrite(
151
+ rewritePath: string,
152
+ context: APIContext,
153
+ next: MiddlewareNext,
154
+ skipHeader?: boolean
155
+ ): Promise<Response> {
156
+ const url = new URL(context.url);
157
+ url.pathname = rewritePath;
158
+ const response = await next(url);
159
+
160
+ // Share rewrite path with following executed middlewares
161
+ if (!skipHeader) {
162
+ response.headers.append(REWRITE_HEADER_NAME, rewritePath);
163
+ }
164
+
165
+ return response;
166
+ }
167
+ }