verify-cloudflare-compression.mjs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. const DEFAULT_TARGETS = [
  2. {
  3. label: 'official site',
  4. url: 'https://file-viewer.app/',
  5. encodings: ['br', 'zstd', 'gzip']
  6. },
  7. {
  8. label: 'docs site',
  9. url: 'https://doc.file-viewer.app/',
  10. encodings: ['br', 'zstd', 'gzip']
  11. },
  12. {
  13. label: 'demo site',
  14. url: 'https://demo.file-viewer.app/',
  15. encodings: ['br', 'zstd', 'gzip']
  16. },
  17. {
  18. label: 'Typst compiler WASM',
  19. url: 'https://demo.file-viewer.app/wasm/typst/typst_ts_web_compiler_bg.wasm',
  20. encodings: ['br'],
  21. contentType: 'application/wasm'
  22. }
  23. ];
  24. const timeoutMs = Number.parseInt(process.env.CLOUDFLARE_COMPRESSION_TIMEOUT_MS || '20000', 10);
  25. async function requestHead(target) {
  26. const controller = new AbortController();
  27. const timeout = setTimeout(() => controller.abort(), timeoutMs);
  28. try {
  29. const response = await fetch(target.url, {
  30. method: 'HEAD',
  31. headers: {
  32. 'Accept-Encoding': target.encodings.join(', ')
  33. },
  34. redirect: 'follow',
  35. signal: controller.signal
  36. });
  37. return response;
  38. } finally {
  39. clearTimeout(timeout);
  40. }
  41. }
  42. function headerValue(headers, name) {
  43. return headers.get(name)?.toLowerCase() || '';
  44. }
  45. const failures = [];
  46. for (const target of DEFAULT_TARGETS) {
  47. try {
  48. const response = await requestHead(target);
  49. const contentEncoding = headerValue(response.headers, 'content-encoding');
  50. const contentType = headerValue(response.headers, 'content-type');
  51. const server = headerValue(response.headers, 'server');
  52. if (!response.ok) {
  53. failures.push(`${target.label}: ${response.status} ${response.statusText}`);
  54. continue;
  55. }
  56. if (!target.encodings.includes(contentEncoding)) {
  57. failures.push(
  58. `${target.label}: expected Content-Encoding ${target.encodings.join('/')} but got ${contentEncoding || '(none)'}`
  59. );
  60. }
  61. if (target.contentType && !contentType.includes(target.contentType)) {
  62. failures.push(`${target.label}: expected Content-Type ${target.contentType} but got ${contentType || '(none)'}`);
  63. }
  64. if (!server.includes('cloudflare')) {
  65. failures.push(`${target.label}: expected Cloudflare server header but got ${server || '(none)'}`);
  66. }
  67. console.log(
  68. `[cloudflare-compression] ${target.label}: ${response.status} ${contentEncoding || 'identity'} ${contentType || 'unknown'}`
  69. );
  70. } catch (error) {
  71. failures.push(`${target.label}: ${error instanceof Error ? error.message : String(error)}`);
  72. }
  73. }
  74. if (failures.length) {
  75. console.error('[cloudflare-compression] Verification failed:');
  76. for (const failure of failures) {
  77. console.error(` - ${failure}`);
  78. }
  79. process.exit(1);
  80. }
  81. console.log('[cloudflare-compression] Verified Cloudflare compression for site, docs, demo, and Typst WASM.');