don't use puppeteer to render unfurl meta tags
This commit is contained in:
		
							parent
							
								
									94d1aeb287
								
							
						
					
					
						commit
						31ced9e23c
					
				@ -53,8 +53,8 @@ export class OpenGraphService {
 | 
			
		||||
    this.metaService.updateTag({ property: 'og:image', content: ogImageUrl });
 | 
			
		||||
    this.metaService.updateTag({ property: 'twitter:image:src', content: ogImageUrl });
 | 
			
		||||
    this.metaService.updateTag({ property: 'og:image:type', content: 'image/png' });
 | 
			
		||||
    this.metaService.updateTag({ property: 'og:image:width', content: '1024' });
 | 
			
		||||
    this.metaService.updateTag({ property: 'og:image:height', content: '512' });
 | 
			
		||||
    this.metaService.updateTag({ property: 'og:image:width', content: '1200' });
 | 
			
		||||
    this.metaService.updateTag({ property: 'og:image:height', content: '600' });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearOgImage() {
 | 
			
		||||
 | 
			
		||||
@ -5,11 +5,13 @@
 | 
			
		||||
  },
 | 
			
		||||
  "MEMPOOL": {
 | 
			
		||||
    "HTTP_HOST": "http://localhost",
 | 
			
		||||
    "HTTP_PORT": 4200
 | 
			
		||||
    "HTTP_PORT": 4200,
 | 
			
		||||
    "NETWORK": "bitcoin" // "bitcoin" | "liquid" | "bisq" (optional - defaults to "bitcoin")
 | 
			
		||||
  },
 | 
			
		||||
  "PUPPETEER": {
 | 
			
		||||
    "CLUSTER_SIZE": 2,
 | 
			
		||||
    "EXEC_PATH": "/usr/local/bin/chrome", // optional
 | 
			
		||||
    "MAX_PAGE_AGE": 86400 // maximum lifetime of a page session (in seconds)
 | 
			
		||||
    "MAX_PAGE_AGE": 86400, // maximum lifetime of a page session (in seconds)
 | 
			
		||||
    "RENDER_TIMEOUT": 3000, // timeout for preview image rendering (in ms) (optional)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,11 +8,13 @@ interface IConfig {
 | 
			
		||||
  MEMPOOL: {
 | 
			
		||||
    HTTP_HOST: string;
 | 
			
		||||
    HTTP_PORT: number;
 | 
			
		||||
    NETWORK?: string;
 | 
			
		||||
  };
 | 
			
		||||
  PUPPETEER: {
 | 
			
		||||
    CLUSTER_SIZE: number;
 | 
			
		||||
    EXEC_PATH?: string;
 | 
			
		||||
    MAX_PAGE_AGE?: number;
 | 
			
		||||
    RENDER_TIMEOUT?: number;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,10 +16,14 @@ class Server {
 | 
			
		||||
  private app: Application;
 | 
			
		||||
  cluster?: Cluster;
 | 
			
		||||
  mempoolHost: string;
 | 
			
		||||
  network: string;
 | 
			
		||||
  defaultImageUrl: string;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.app = express();
 | 
			
		||||
    this.mempoolHost = config.MEMPOOL.HTTP_HOST + (config.MEMPOOL.HTTP_PORT ? ':' + config.MEMPOOL.HTTP_PORT : '');
 | 
			
		||||
    this.network = config.MEMPOOL.NETWORK || 'bitcoin';
 | 
			
		||||
    this.defaultImageUrl = this.getDefaultImageUrl();
 | 
			
		||||
    this.startServer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -97,16 +101,11 @@ class Server {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (action === 'screenshot') {
 | 
			
		||||
      const waitForReady = await page.$('meta[property="og:preview:loading"]');
 | 
			
		||||
      if (waitForReady != null) {
 | 
			
		||||
          await page.waitForSelector('meta[property="og:preview:ready"]', { timeout: 3000 });
 | 
			
		||||
        await page.waitForSelector('meta[property="og:preview:ready"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 });
 | 
			
		||||
      }
 | 
			
		||||
      return page.screenshot();
 | 
			
		||||
      } else if (action === 'html') {
 | 
			
		||||
        await page.waitForSelector('meta[property="og:meta:ready"]', { timeout: 3000 });
 | 
			
		||||
        return page.content();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(`failed to render page for ${action}`, e instanceof Error ? e.message : e);
 | 
			
		||||
      page.repairRequested = true;
 | 
			
		||||
@ -132,22 +131,73 @@ class Server {
 | 
			
		||||
 | 
			
		||||
  async renderHTML(req, res) {
 | 
			
		||||
    // drop requests for static files
 | 
			
		||||
    const path = req.params[0];
 | 
			
		||||
    const match = path.match(/\.[\w]+$/);
 | 
			
		||||
    const rawPath = req.params[0];
 | 
			
		||||
    const match = rawPath.match(/\.[\w]+$/);
 | 
			
		||||
    if (match?.length && match[0] !== '.html') {
 | 
			
		||||
      res.status(404).send();
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      let html = await this.cluster?.execute({ url: this.mempoolHost + path, path: path, action: 'html' });
 | 
			
		||||
      if (!html) {
 | 
			
		||||
        throw new Error('failed to render preview image');
 | 
			
		||||
    let previewSupported = true;
 | 
			
		||||
    let mode = 'mainnet'
 | 
			
		||||
    let ogImageUrl = this.defaultImageUrl;
 | 
			
		||||
    let ogTitle;
 | 
			
		||||
    const { lang, path } = parseLanguageUrl(rawPath);
 | 
			
		||||
    const parts = path.slice(1).split('/');
 | 
			
		||||
 | 
			
		||||
    // handle network mode modifiers
 | 
			
		||||
    if (['testnet', 'signet'].includes(parts[0])) {
 | 
			
		||||
      mode = parts.shift();
 | 
			
		||||
    }
 | 
			
		||||
      res.send(html);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
 | 
			
		||||
    // handle supported preview routes
 | 
			
		||||
    if (parts[0] === 'block') {
 | 
			
		||||
      ogTitle = `Block: ${parts[1]}`;
 | 
			
		||||
    } else if (parts[0] === 'address') {
 | 
			
		||||
      ogTitle = `Address: ${parts[1]}`;
 | 
			
		||||
    } else {
 | 
			
		||||
      previewSupported = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (previewSupported) {
 | 
			
		||||
      ogImageUrl = `${config.SERVER.HOST}/render/${lang || 'en'}/preview${path}`;
 | 
			
		||||
      ogTitle = `${this.network ? capitalize(this.network) + ' ' : ''}${mode !== 'mainnet' ? capitalize(mode) + ' ' : ''}${ogTitle}`;
 | 
			
		||||
    } else {
 | 
			
		||||
      ogTitle = 'The Mempool Open Source Project™';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.send(`
 | 
			
		||||
      <!doctype html>
 | 
			
		||||
      <html lang="en-US" dir="ltr">
 | 
			
		||||
      <head>
 | 
			
		||||
        <meta charset="utf-8">
 | 
			
		||||
        <title>${ogTitle}</title>
 | 
			
		||||
        <meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the ${capitalize(this.network)} community."/>
 | 
			
		||||
        <meta property="og:image" content="${ogImageUrl}"/>
 | 
			
		||||
        <meta property="og:image:type" content="image/png"/>
 | 
			
		||||
        <meta property="og:image:width" content="${previewSupported ? 1200 : 1000}"/>
 | 
			
		||||
        <meta property="og:image:height" content="${previewSupported ? 600 : 500}"/>
 | 
			
		||||
        <meta property="og:title" content="${ogTitle}">
 | 
			
		||||
        <meta property="twitter:card" content="summary_large_image">
 | 
			
		||||
        <meta property="twitter:site" content="@mempool">
 | 
			
		||||
        <meta property="twitter:creator" content="@mempool">
 | 
			
		||||
        <meta property="twitter:title" content="${ogTitle}">
 | 
			
		||||
        <meta property="twitter:description" content="Our self-hosted mempool explorer for the ${capitalize(this.network)} community."/>
 | 
			
		||||
        <meta property="twitter:image:src" content="${ogImageUrl}"/>
 | 
			
		||||
        <meta property="twitter:domain" content="mempool.space">
 | 
			
		||||
      <body></body>
 | 
			
		||||
      </html>
 | 
			
		||||
    `);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getDefaultImageUrl() {
 | 
			
		||||
    switch (this.network) {
 | 
			
		||||
      case 'liquid':
 | 
			
		||||
        return '/resources/liquid/liquid-network-preview.png';
 | 
			
		||||
      case 'bisq':
 | 
			
		||||
        return '/resources/bisq/bisq-markets-preview.png';
 | 
			
		||||
      default:
 | 
			
		||||
        return '/resources/mempool-space-preview.png';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -159,3 +209,11 @@ process.on('SIGTERM', async () => {
 | 
			
		||||
  await server.stopServer();
 | 
			
		||||
  process.exit(0);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function capitalize(str) {
 | 
			
		||||
  if (str && str.length) {
 | 
			
		||||
    return str[0].toUpperCase() + str.slice(1);
 | 
			
		||||
  } else {
 | 
			
		||||
    return str;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user