universalUrlGenerator.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /**
  2. * Universal URL Generator
  3. * Generates file URLs that work regardless of ROOT_URL and PORT settings
  4. * Ensures all attachments and avatars are always visible
  5. */
  6. import { Meteor } from 'meteor/meteor';
  7. /**
  8. * Generate a universal file URL that works regardless of ROOT_URL and PORT
  9. * @param {string} fileId - The file ID
  10. * @param {string} type - The file type ('attachment' or 'avatar')
  11. * @param {string} version - The file version (default: 'original')
  12. * @returns {string} - Universal file URL
  13. */
  14. export function generateUniversalFileUrl(fileId, type, version = 'original') {
  15. if (!fileId) {
  16. return '';
  17. }
  18. // Always use relative URLs to avoid ROOT_URL and PORT dependencies
  19. if (type === 'attachment') {
  20. return `/cdn/storage/attachments/${fileId}`;
  21. } else if (type === 'avatar') {
  22. return `/cdn/storage/avatars/${fileId}`;
  23. }
  24. return '';
  25. }
  26. /**
  27. * Generate a universal attachment URL
  28. * @param {string} attachmentId - The attachment ID
  29. * @param {string} version - The file version (default: 'original')
  30. * @returns {string} - Universal attachment URL
  31. */
  32. export function generateUniversalAttachmentUrl(attachmentId, version = 'original') {
  33. return generateUniversalFileUrl(attachmentId, 'attachment', version);
  34. }
  35. /**
  36. * Generate a universal avatar URL
  37. * @param {string} avatarId - The avatar ID
  38. * @param {string} version - The file version (default: 'original')
  39. * @returns {string} - Universal avatar URL
  40. */
  41. export function generateUniversalAvatarUrl(avatarId, version = 'original') {
  42. return generateUniversalFileUrl(avatarId, 'avatar', version);
  43. }
  44. /**
  45. * Clean and normalize a file URL to ensure it's universal
  46. * @param {string} url - The URL to clean
  47. * @param {string} type - The file type ('attachment' or 'avatar')
  48. * @returns {string} - Cleaned universal URL
  49. */
  50. export function cleanFileUrl(url, type) {
  51. if (!url) {
  52. return '';
  53. }
  54. // Remove any domain, port, or protocol from the URL
  55. let cleanUrl = url;
  56. // Remove protocol and domain
  57. cleanUrl = cleanUrl.replace(/^https?:\/\/[^\/]+/, '');
  58. // Remove ROOT_URL pathname if present
  59. if (Meteor.isServer && process.env.ROOT_URL) {
  60. try {
  61. const rootUrl = new URL(process.env.ROOT_URL);
  62. if (rootUrl.pathname && rootUrl.pathname !== '/') {
  63. cleanUrl = cleanUrl.replace(rootUrl.pathname, '');
  64. }
  65. } catch (e) {
  66. // Ignore URL parsing errors
  67. }
  68. }
  69. // Normalize path separators
  70. cleanUrl = cleanUrl.replace(/\/+/g, '/');
  71. // Ensure URL starts with /
  72. if (!cleanUrl.startsWith('/')) {
  73. cleanUrl = '/' + cleanUrl;
  74. }
  75. // Convert old CollectionFS URLs to new format
  76. if (type === 'attachment') {
  77. cleanUrl = cleanUrl.replace('/cfs/files/attachments/', '/cdn/storage/attachments/');
  78. } else if (type === 'avatar') {
  79. cleanUrl = cleanUrl.replace('/cfs/files/avatars/', '/cdn/storage/avatars/');
  80. }
  81. // Remove any query parameters that might cause issues
  82. cleanUrl = cleanUrl.split('?')[0];
  83. cleanUrl = cleanUrl.split('#')[0];
  84. return cleanUrl;
  85. }
  86. /**
  87. * Check if a URL is a universal file URL
  88. * @param {string} url - The URL to check
  89. * @param {string} type - The file type ('attachment' or 'avatar')
  90. * @returns {boolean} - True if it's a universal file URL
  91. */
  92. export function isUniversalFileUrl(url, type) {
  93. if (!url) {
  94. return false;
  95. }
  96. if (type === 'attachment') {
  97. return url.includes('/cdn/storage/attachments/') || url.includes('/cfs/files/attachments/');
  98. } else if (type === 'avatar') {
  99. return url.includes('/cdn/storage/avatars/') || url.includes('/cfs/files/avatars/');
  100. }
  101. return false;
  102. }
  103. /**
  104. * Extract file ID from a universal file URL
  105. * @param {string} url - The URL to extract from
  106. * @param {string} type - The file type ('attachment' or 'avatar')
  107. * @returns {string|null} - The file ID or null if not found
  108. */
  109. export function extractFileIdFromUrl(url, type) {
  110. if (!url) {
  111. return null;
  112. }
  113. let pattern;
  114. if (type === 'attachment') {
  115. pattern = /\/(?:cdn\/storage\/attachments|cfs\/files\/attachments)\/([^\/\?#]+)/;
  116. } else if (type === 'avatar') {
  117. pattern = /\/(?:cdn\/storage\/avatars|cfs\/files\/avatars)\/([^\/\?#]+)/;
  118. } else {
  119. return null;
  120. }
  121. const match = url.match(pattern);
  122. return match ? match[1] : null;
  123. }
  124. /**
  125. * Generate a fallback URL for when the primary URL fails
  126. * @param {string} fileId - The file ID
  127. * @param {string} type - The file type ('attachment' or 'avatar')
  128. * @returns {string} - Fallback URL
  129. */
  130. export function generateFallbackUrl(fileId, type) {
  131. if (!fileId) {
  132. return '';
  133. }
  134. // Try alternative route patterns
  135. if (type === 'attachment') {
  136. return `/attachments/${fileId}`;
  137. } else if (type === 'avatar') {
  138. return `/avatars/${fileId}`;
  139. }
  140. return '';
  141. }
  142. /**
  143. * Get all possible URLs for a file (for redundancy)
  144. * @param {string} fileId - The file ID
  145. * @param {string} type - The file type ('attachment' or 'avatar')
  146. * @returns {Array<string>} - Array of possible URLs
  147. */
  148. export function getAllPossibleUrls(fileId, type) {
  149. if (!fileId) {
  150. return [];
  151. }
  152. const urls = [];
  153. // Primary URL
  154. urls.push(generateUniversalFileUrl(fileId, type));
  155. // Fallback URL
  156. urls.push(generateFallbackUrl(fileId, type));
  157. // Legacy URLs for backward compatibility
  158. if (type === 'attachment') {
  159. urls.push(`/cfs/files/attachments/${fileId}`);
  160. } else if (type === 'avatar') {
  161. urls.push(`/cfs/files/avatars/${fileId}`);
  162. }
  163. return urls.filter(url => url); // Remove empty URLs
  164. }