@adobe/spacecat-shared-tokowaka-client 1.4.0 → 1.4.2
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/CHANGELOG.md +14 -0
- package/README.md +107 -7
- package/package.json +1 -1
- package/src/cdn/cdn-client-registry.js +2 -0
- package/src/cdn/fastly-cdn-client.js +156 -0
- package/src/index.d.ts +47 -11
- package/src/index.js +152 -86
- package/src/mappers/generic-mapper.js +5 -1
- package/src/utils/custom-html-utils.js +5 -5
- package/test/cdn/fastly-cdn-client.test.js +484 -0
- package/test/index.test.js +331 -118
- package/test/mappers/generic-mapper.test.js +82 -0
- package/test/utils/html-utils.test.js +12 -12
package/src/index.js
CHANGED
|
@@ -92,6 +92,35 @@ class TokowakaClient {
|
|
|
92
92
|
return error;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Gets the list of CDN providers from environment configuration
|
|
97
|
+
* Supports both single provider (string) and multiple providers (comma-separated string or array)
|
|
98
|
+
* @returns {Array<string>} Array of CDN provider names
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
#getCdnProviders() {
|
|
102
|
+
const providerConfig = this.env.TOKOWAKA_CDN_PROVIDER;
|
|
103
|
+
|
|
104
|
+
if (!providerConfig) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If it's already an array, return it
|
|
109
|
+
if (Array.isArray(providerConfig)) {
|
|
110
|
+
return providerConfig.filter(Boolean);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// If it's a comma-separated string, split it
|
|
114
|
+
if (typeof providerConfig === 'string') {
|
|
115
|
+
return providerConfig
|
|
116
|
+
.split(',')
|
|
117
|
+
.map((p) => p.trim())
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
95
124
|
/**
|
|
96
125
|
* Generates Tokowaka site configuration from suggestions for a specific URL
|
|
97
126
|
* @param {string} url - Full URL for which to generate config
|
|
@@ -155,16 +184,15 @@ class TokowakaClient {
|
|
|
155
184
|
/**
|
|
156
185
|
* Fetches domain-level metaconfig from S3
|
|
157
186
|
* @param {string} url - Full URL (used to extract domain)
|
|
158
|
-
* @param {boolean} isPreview - Whether to fetch from preview path (default: false)
|
|
159
187
|
* @returns {Promise<Object|null>} - Metaconfig object or null if not found
|
|
160
188
|
*/
|
|
161
|
-
async fetchMetaconfig(url
|
|
189
|
+
async fetchMetaconfig(url) {
|
|
162
190
|
if (!hasText(url)) {
|
|
163
191
|
throw this.#createError('URL is required', HTTP_BAD_REQUEST);
|
|
164
192
|
}
|
|
165
193
|
|
|
166
|
-
const s3Path = getTokowakaMetaconfigS3Path(url, this.log
|
|
167
|
-
const bucketName =
|
|
194
|
+
const s3Path = getTokowakaMetaconfigS3Path(url, this.log);
|
|
195
|
+
const bucketName = this.deployBucketName;
|
|
168
196
|
|
|
169
197
|
try {
|
|
170
198
|
const command = new GetObjectCommand({
|
|
@@ -195,10 +223,9 @@ class TokowakaClient {
|
|
|
195
223
|
* Uploads domain-level metaconfig to S3
|
|
196
224
|
* @param {string} url - Full URL (used to extract domain)
|
|
197
225
|
* @param {Object} metaconfig - Metaconfig object (siteId, prerender)
|
|
198
|
-
* @param {boolean} isPreview - Whether to upload to preview path (default: false)
|
|
199
226
|
* @returns {Promise<string>} - S3 key of uploaded metaconfig
|
|
200
227
|
*/
|
|
201
|
-
async uploadMetaconfig(url, metaconfig
|
|
228
|
+
async uploadMetaconfig(url, metaconfig) {
|
|
202
229
|
if (!hasText(url)) {
|
|
203
230
|
throw this.#createError('URL is required', HTTP_BAD_REQUEST);
|
|
204
231
|
}
|
|
@@ -207,8 +234,8 @@ class TokowakaClient {
|
|
|
207
234
|
throw this.#createError('Metaconfig object is required', HTTP_BAD_REQUEST);
|
|
208
235
|
}
|
|
209
236
|
|
|
210
|
-
const s3Path = getTokowakaMetaconfigS3Path(url, this.log
|
|
211
|
-
const bucketName =
|
|
237
|
+
const s3Path = getTokowakaMetaconfigS3Path(url, this.log);
|
|
238
|
+
const bucketName = this.deployBucketName;
|
|
212
239
|
|
|
213
240
|
try {
|
|
214
241
|
const command = new PutObjectCommand({
|
|
@@ -221,6 +248,10 @@ class TokowakaClient {
|
|
|
221
248
|
await this.s3Client.send(command);
|
|
222
249
|
this.log.info(`Successfully uploaded metaconfig to s3://${bucketName}/${s3Path}`);
|
|
223
250
|
|
|
251
|
+
// Invalidate CDN cache for the metaconfig (both CloudFront and Fastly)
|
|
252
|
+
this.log.info('Invalidating CDN cache for uploaded metaconfig');
|
|
253
|
+
await this.invalidateCdnCache({ paths: [`/${s3Path}`] });
|
|
254
|
+
|
|
224
255
|
return s3Path;
|
|
225
256
|
} catch (error) {
|
|
226
257
|
this.log.error(`Failed to upload metaconfig to S3: ${error.message}`, error);
|
|
@@ -341,41 +372,86 @@ class TokowakaClient {
|
|
|
341
372
|
}
|
|
342
373
|
|
|
343
374
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
* @param {
|
|
347
|
-
* @param {string}
|
|
348
|
-
* @param {
|
|
349
|
-
* @
|
|
375
|
+
* CDN cache invalidation method that supports invalidating URL configs
|
|
376
|
+
* or custom S3 paths across provided or default CDN providers
|
|
377
|
+
* @param {Object} options - Invalidation options
|
|
378
|
+
* @param {Array<string>} options.urls - Array of full URLs to invalidate (for URL configs)
|
|
379
|
+
* @param {Array<string>} options.paths - Custom S3 paths to invalidate directly
|
|
380
|
+
* @param {string|Array<string>} options.providers - CDN provider name(s)
|
|
381
|
+
* (default: all supported providers)
|
|
382
|
+
* @param {boolean} options.isPreview - Whether to invalidate preview paths (default: false)
|
|
383
|
+
* @returns {Promise<Array<Object>>} - Array of CDN invalidation results
|
|
350
384
|
*/
|
|
351
|
-
async invalidateCdnCache(
|
|
352
|
-
|
|
353
|
-
|
|
385
|
+
async invalidateCdnCache({
|
|
386
|
+
urls = [],
|
|
387
|
+
paths = [],
|
|
388
|
+
providers = this.#getCdnProviders(),
|
|
389
|
+
isPreview = false,
|
|
390
|
+
}) {
|
|
391
|
+
// Convert single provider to array for uniform handling
|
|
392
|
+
const providerList = Array.isArray(providers) ? providers : [providers].filter(Boolean);
|
|
393
|
+
|
|
394
|
+
if (providerList.length === 0) {
|
|
395
|
+
this.log.warn('No CDN providers specified for cache invalidation');
|
|
396
|
+
return [];
|
|
354
397
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
this.log.info(`CDN cache invalidation completed: ${JSON.stringify(result)}`);
|
|
364
|
-
return result;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
this.log.error(`Failed to invalidate Tokowaka CDN cache: ${error.message}`, error);
|
|
367
|
-
return {
|
|
368
|
-
status: 'error',
|
|
369
|
-
provider: 'cloudfront',
|
|
370
|
-
message: error.message,
|
|
371
|
-
};
|
|
398
|
+
|
|
399
|
+
// Build list of paths to invalidate
|
|
400
|
+
const pathsToInvalidate = [...paths];
|
|
401
|
+
|
|
402
|
+
// Add URL config paths
|
|
403
|
+
if (urls.length > 0) {
|
|
404
|
+
const urlPaths = urls.map((url) => `/${getTokowakaConfigS3Path(url, this.log, isPreview)}`);
|
|
405
|
+
pathsToInvalidate.push(...urlPaths);
|
|
372
406
|
}
|
|
407
|
+
|
|
408
|
+
// Return early if no paths to invalidate
|
|
409
|
+
if (pathsToInvalidate.length === 0) {
|
|
410
|
+
this.log.debug('No paths to invalidate for CDN cache');
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this.log.info(
|
|
415
|
+
`Invalidating CDN cache for ${pathsToInvalidate.length} path(s) `
|
|
416
|
+
+ `via providers: ${providerList.join(', ')}`,
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
// Invalidate all providers in parallel
|
|
420
|
+
const invalidationPromises = providerList.map(async (provider) => {
|
|
421
|
+
try {
|
|
422
|
+
const cdnClient = this.cdnClientRegistry.getClient(provider);
|
|
423
|
+
if (!cdnClient) {
|
|
424
|
+
this.log.warn(`No CDN client available for provider: ${provider}`);
|
|
425
|
+
return {
|
|
426
|
+
status: 'error',
|
|
427
|
+
provider,
|
|
428
|
+
message: `No CDN client available for provider: ${provider}`,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const result = await cdnClient.invalidateCache(pathsToInvalidate);
|
|
433
|
+
this.log.info(
|
|
434
|
+
`CDN cache invalidation completed for ${provider}: `
|
|
435
|
+
+ `${pathsToInvalidate.length} path(s)`,
|
|
436
|
+
);
|
|
437
|
+
return result;
|
|
438
|
+
} catch (error) {
|
|
439
|
+
this.log.warn(`Failed to invalidate ${provider} CDN cache: ${error.message}`, error);
|
|
440
|
+
return {
|
|
441
|
+
status: 'error',
|
|
442
|
+
provider,
|
|
443
|
+
message: error.message,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Wait for all provider invalidations to complete
|
|
449
|
+
const results = await Promise.all(invalidationPromises);
|
|
450
|
+
return results;
|
|
373
451
|
}
|
|
374
452
|
|
|
375
453
|
/**
|
|
376
|
-
* Deploys suggestions to Tokowaka by generating config and uploading to S3
|
|
377
|
-
* Now creates one file per URL instead of a single file with all URLs
|
|
378
|
-
* Also creates/updates domain-level metadata if needed
|
|
454
|
+
* Deploys suggestions to Tokowaka by generating patch config and uploading to S3
|
|
379
455
|
* @param {Object} site - Site entity
|
|
380
456
|
* @param {Object} opportunity - Opportunity entity
|
|
381
457
|
* @param {Array} suggestions - Array of suggestion entities to deploy
|
|
@@ -384,7 +460,6 @@ class TokowakaClient {
|
|
|
384
460
|
async deploySuggestions(site, opportunity, suggestions) {
|
|
385
461
|
const opportunityType = opportunity.getType();
|
|
386
462
|
const baseURL = getEffectiveBaseURL(site);
|
|
387
|
-
const siteId = site.getId();
|
|
388
463
|
const mapper = this.mapperRegistry.getMapper(opportunityType);
|
|
389
464
|
if (!mapper) {
|
|
390
465
|
throw this.#createError(
|
|
@@ -416,24 +491,21 @@ class TokowakaClient {
|
|
|
416
491
|
// Group suggestions by URL
|
|
417
492
|
const suggestionsByUrl = groupSuggestionsByUrlPath(eligibleSuggestions, baseURL, this.log);
|
|
418
493
|
|
|
419
|
-
// Check
|
|
494
|
+
// Check if domain-level metaconfig exists
|
|
420
495
|
const firstUrl = new URL(Object.keys(suggestionsByUrl)[0], baseURL).toString();
|
|
421
|
-
|
|
496
|
+
const metaconfig = await this.fetchMetaconfig(firstUrl);
|
|
422
497
|
|
|
423
498
|
if (!metaconfig) {
|
|
424
|
-
this
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
await this.uploadMetaconfig(firstUrl, metaconfig);
|
|
430
|
-
} else {
|
|
431
|
-
this.log.debug('Domain-level metaconfig already exists');
|
|
499
|
+
throw this.#createError(
|
|
500
|
+
'No domain-level metaconfig found. '
|
|
501
|
+
+ 'A domain-level metaconfig needs to be created first before deploying suggestions.',
|
|
502
|
+
HTTP_BAD_REQUEST,
|
|
503
|
+
);
|
|
432
504
|
}
|
|
433
505
|
|
|
434
506
|
// Process each URL separately
|
|
435
507
|
const s3Paths = [];
|
|
436
|
-
const
|
|
508
|
+
const deployedUrls = []; // Track URLs for batch CDN invalidation
|
|
437
509
|
|
|
438
510
|
for (const [urlPath, urlSuggestions] of Object.entries(suggestionsByUrl)) {
|
|
439
511
|
const fullUrl = new URL(urlPath, baseURL).toString();
|
|
@@ -468,18 +540,14 @@ class TokowakaClient {
|
|
|
468
540
|
// eslint-disable-next-line no-await-in-loop
|
|
469
541
|
const s3Path = await this.uploadConfig(fullUrl, config);
|
|
470
542
|
s3Paths.push(s3Path);
|
|
471
|
-
|
|
472
|
-
// Invalidate CDN cache (non-blocking, failures are logged but don't fail deployment)
|
|
473
|
-
// eslint-disable-next-line no-await-in-loop
|
|
474
|
-
const cdnInvalidationResult = await this.invalidateCdnCache(
|
|
475
|
-
fullUrl,
|
|
476
|
-
this.env.TOKOWAKA_CDN_PROVIDER,
|
|
477
|
-
);
|
|
478
|
-
cdnInvalidations.push(cdnInvalidationResult);
|
|
543
|
+
deployedUrls.push(fullUrl);
|
|
479
544
|
}
|
|
480
545
|
|
|
481
546
|
this.log.info(`Uploaded Tokowaka configs for ${s3Paths.length} URLs`);
|
|
482
547
|
|
|
548
|
+
// Invalidate CDN cache for all deployed URLs at once
|
|
549
|
+
const cdnInvalidations = await this.invalidateCdnCache({ urls: deployedUrls });
|
|
550
|
+
|
|
483
551
|
return {
|
|
484
552
|
s3Paths,
|
|
485
553
|
cdnInvalidations,
|
|
@@ -533,7 +601,7 @@ class TokowakaClient {
|
|
|
533
601
|
|
|
534
602
|
// Process each URL separately
|
|
535
603
|
const s3Paths = [];
|
|
536
|
-
const
|
|
604
|
+
const rolledBackUrls = []; // Track URLs for batch CDN invalidation
|
|
537
605
|
let totalRemovedCount = 0;
|
|
538
606
|
|
|
539
607
|
for (const [urlPath, urlSuggestions] of Object.entries(suggestionsByUrl)) {
|
|
@@ -567,14 +635,7 @@ class TokowakaClient {
|
|
|
567
635
|
// eslint-disable-next-line no-await-in-loop
|
|
568
636
|
const s3Path = await this.uploadConfig(fullUrl, updatedConfig);
|
|
569
637
|
s3Paths.push(s3Path);
|
|
570
|
-
|
|
571
|
-
// Invalidate CDN cache
|
|
572
|
-
// eslint-disable-next-line no-await-in-loop
|
|
573
|
-
const cdnInvalidationResult = await this.invalidateCdnCache(
|
|
574
|
-
fullUrl,
|
|
575
|
-
this.env.TOKOWAKA_CDN_PROVIDER,
|
|
576
|
-
);
|
|
577
|
-
cdnInvalidations.push(cdnInvalidationResult);
|
|
638
|
+
rolledBackUrls.push(fullUrl); // Collect URL for batch invalidation
|
|
578
639
|
|
|
579
640
|
totalRemovedCount += 1; // Count as 1 rollback
|
|
580
641
|
// eslint-disable-next-line no-continue
|
|
@@ -610,18 +671,15 @@ class TokowakaClient {
|
|
|
610
671
|
// eslint-disable-next-line no-await-in-loop
|
|
611
672
|
const s3Path = await this.uploadConfig(fullUrl, updatedConfig);
|
|
612
673
|
s3Paths.push(s3Path);
|
|
613
|
-
|
|
614
|
-
// Invalidate CDN cache (non-blocking, failures are logged but don't fail rollback)
|
|
615
|
-
// eslint-disable-next-line no-await-in-loop
|
|
616
|
-
const cdnInvalidationResult = await this.invalidateCdnCache(
|
|
617
|
-
fullUrl,
|
|
618
|
-
this.env.TOKOWAKA_CDN_PROVIDER,
|
|
619
|
-
);
|
|
620
|
-
cdnInvalidations.push(cdnInvalidationResult);
|
|
674
|
+
rolledBackUrls.push(fullUrl); // Collect URL for batch invalidation
|
|
621
675
|
}
|
|
622
676
|
|
|
623
677
|
this.log.info(`Updated Tokowaka configs for ${s3Paths.length} URLs, removed ${totalRemovedCount} patches total`);
|
|
624
678
|
|
|
679
|
+
// Batch invalidate CDN cache for all rolled back URLs at once
|
|
680
|
+
// (much more efficient than individual invalidations)
|
|
681
|
+
const cdnInvalidations = await this.invalidateCdnCache({ urls: rolledBackUrls });
|
|
682
|
+
|
|
625
683
|
return {
|
|
626
684
|
s3Paths,
|
|
627
685
|
cdnInvalidations,
|
|
@@ -642,11 +700,11 @@ class TokowakaClient {
|
|
|
642
700
|
*/
|
|
643
701
|
async previewSuggestions(site, opportunity, suggestions, options = {}) {
|
|
644
702
|
// Get site's forwarded host for preview
|
|
645
|
-
const { forwardedHost
|
|
703
|
+
const { forwardedHost } = site.getConfig()?.getTokowakaConfig() || {};
|
|
646
704
|
|
|
647
|
-
if (!hasText(forwardedHost)
|
|
705
|
+
if (!hasText(forwardedHost)) {
|
|
648
706
|
throw this.#createError(
|
|
649
|
-
'Site does not have a Tokowaka
|
|
707
|
+
'Site does not have a Tokowaka forwarded host configured. '
|
|
650
708
|
+ 'Please onboard the site to Tokowaka first.',
|
|
651
709
|
HTTP_BAD_REQUEST,
|
|
652
710
|
);
|
|
@@ -662,6 +720,15 @@ class TokowakaClient {
|
|
|
662
720
|
);
|
|
663
721
|
}
|
|
664
722
|
|
|
723
|
+
// TOKOWAKA_PREVIEW_API_KEY is mandatory for preview
|
|
724
|
+
const tokowakaPreviewApiKey = this.env.TOKOWAKA_PREVIEW_API_KEY;
|
|
725
|
+
if (!hasText(tokowakaPreviewApiKey)) {
|
|
726
|
+
throw this.#createError(
|
|
727
|
+
'TOKOWAKA_PREVIEW_API_KEY is required for preview functionality',
|
|
728
|
+
HTTP_INTERNAL_SERVER_ERROR,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
665
732
|
// TOKOWAKA_EDGE_URL is mandatory for preview
|
|
666
733
|
const tokowakaEdgeUrl = this.env.TOKOWAKA_EDGE_URL;
|
|
667
734
|
if (!hasText(tokowakaEdgeUrl)) {
|
|
@@ -745,12 +812,11 @@ class TokowakaClient {
|
|
|
745
812
|
this.log.info(`Uploading preview Tokowaka config with ${eligibleSuggestions.length} new suggestions`);
|
|
746
813
|
const s3Path = await this.uploadConfig(previewUrl, config, true);
|
|
747
814
|
|
|
748
|
-
// Invalidate CDN cache for preview path
|
|
749
|
-
const
|
|
750
|
-
previewUrl,
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
);
|
|
815
|
+
// Invalidate CDN cache for all providers in parallel (preview path)
|
|
816
|
+
const cdnInvalidationResults = await this.invalidateCdnCache({
|
|
817
|
+
urls: [previewUrl],
|
|
818
|
+
isPreview: true,
|
|
819
|
+
});
|
|
754
820
|
|
|
755
821
|
// Fetch HTML content for preview
|
|
756
822
|
let originalHtml = null;
|
|
@@ -760,7 +826,7 @@ class TokowakaClient {
|
|
|
760
826
|
// Fetch original HTML (without preview)
|
|
761
827
|
originalHtml = await fetchHtmlWithWarmup(
|
|
762
828
|
previewUrl,
|
|
763
|
-
|
|
829
|
+
tokowakaPreviewApiKey,
|
|
764
830
|
forwardedHost,
|
|
765
831
|
tokowakaEdgeUrl,
|
|
766
832
|
this.log,
|
|
@@ -770,7 +836,7 @@ class TokowakaClient {
|
|
|
770
836
|
// Then fetch optimized HTML (with preview)
|
|
771
837
|
optimizedHtml = await fetchHtmlWithWarmup(
|
|
772
838
|
previewUrl,
|
|
773
|
-
|
|
839
|
+
tokowakaPreviewApiKey,
|
|
774
840
|
forwardedHost,
|
|
775
841
|
tokowakaEdgeUrl,
|
|
776
842
|
this.log,
|
|
@@ -789,7 +855,7 @@ class TokowakaClient {
|
|
|
789
855
|
return {
|
|
790
856
|
s3Path,
|
|
791
857
|
config,
|
|
792
|
-
|
|
858
|
+
cdnInvalidations: cdnInvalidationResults,
|
|
793
859
|
succeededSuggestions: eligibleSuggestions,
|
|
794
860
|
failedSuggestions: ineligibleSuggestions,
|
|
795
861
|
html: {
|
|
@@ -57,7 +57,7 @@ export default class GenericMapper extends BaseOpportunityMapper {
|
|
|
57
57
|
...this.createBasePatch(suggestion, opportunityId),
|
|
58
58
|
op: transformRules.action,
|
|
59
59
|
selector: transformRules.selector,
|
|
60
|
-
value: data.format === 'hast' ? JSON.parse(data.patchValue) : data.patchValue,
|
|
60
|
+
value: (data.format === 'hast' || data.format === 'json') ? JSON.parse(data.patchValue) : data.patchValue,
|
|
61
61
|
valueFormat: data.format || 'text',
|
|
62
62
|
target: TARGET_USER_AGENTS_CATEGORIES.AI_BOTS,
|
|
63
63
|
};
|
|
@@ -66,6 +66,10 @@ export default class GenericMapper extends BaseOpportunityMapper {
|
|
|
66
66
|
patch.tag = data.tag;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
if (data.attrs) {
|
|
70
|
+
patch.attrs = JSON.parse(data.attrs);
|
|
71
|
+
}
|
|
72
|
+
|
|
69
73
|
patches.push(patch);
|
|
70
74
|
});
|
|
71
75
|
|
|
@@ -89,7 +89,7 @@ async function fetchWithRetry(url, options, maxRetries, retryDelayMs, log, fetch
|
|
|
89
89
|
* Fetches HTML content from Tokowaka edge with warmup call and retry logic
|
|
90
90
|
* Makes an initial warmup call, waits, then makes the actual call with retries
|
|
91
91
|
* @param {string} url - Full URL to fetch
|
|
92
|
-
* @param {string}
|
|
92
|
+
* @param {string} previewApiKey - Tokowaka preview API key (internal)
|
|
93
93
|
* @param {string} forwardedHost - Host to forward in x-forwarded-host header
|
|
94
94
|
* @param {string} tokowakaEdgeUrl - Tokowaka edge URL
|
|
95
95
|
* @param {boolean} isOptimized - Whether to fetch optimized HTML (with preview param)
|
|
@@ -103,7 +103,7 @@ async function fetchWithRetry(url, options, maxRetries, retryDelayMs, log, fetch
|
|
|
103
103
|
*/
|
|
104
104
|
export async function fetchHtmlWithWarmup(
|
|
105
105
|
url,
|
|
106
|
-
|
|
106
|
+
previewApiKey,
|
|
107
107
|
forwardedHost,
|
|
108
108
|
tokowakaEdgeUrl,
|
|
109
109
|
log,
|
|
@@ -115,8 +115,8 @@ export async function fetchHtmlWithWarmup(
|
|
|
115
115
|
throw new Error('URL is required for fetching HTML');
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
if (!hasText(
|
|
119
|
-
throw new Error('Tokowaka API key is required for fetching HTML');
|
|
118
|
+
if (!hasText(previewApiKey)) {
|
|
119
|
+
throw new Error('Tokowaka preview API key is required for fetching HTML');
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
if (!hasText(forwardedHost)) {
|
|
@@ -143,7 +143,7 @@ export async function fetchHtmlWithWarmup(
|
|
|
143
143
|
|
|
144
144
|
const headers = {
|
|
145
145
|
'x-forwarded-host': forwardedHost,
|
|
146
|
-
'x-tokowaka-api-key':
|
|
146
|
+
'x-tokowaka-preview-api-key': previewApiKey,
|
|
147
147
|
'x-tokowaka-url': urlPath,
|
|
148
148
|
};
|
|
149
149
|
|