Merge pull request #4198 from mempool/mononaut/unfurler-fixes
More unfurler fixes
This commit is contained in:
		
						commit
						063d7e96a1
					
				| @ -70,10 +70,12 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|     this.canvas.nativeElement.addEventListener('webglcontextlost', this.handleContextLost, false); | ||||
|     this.canvas.nativeElement.addEventListener('webglcontextrestored', this.handleContextRestored, false); | ||||
|     this.gl = this.canvas.nativeElement.getContext('webgl'); | ||||
|     this.initCanvas(); | ||||
| 
 | ||||
|     if (this.gl) { | ||||
|       this.initCanvas(); | ||||
|       this.resizeCanvas(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   ngOnChanges(changes): void { | ||||
|     if (changes.orientation || changes.flip) { | ||||
| @ -195,11 +197,17 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|     cancelAnimationFrame(this.animationFrameRequest); | ||||
|     this.animationFrameRequest = null; | ||||
|     this.running = false; | ||||
|     this.gl = null; | ||||
|   } | ||||
| 
 | ||||
|   handleContextRestored(event): void { | ||||
|     if (this.canvas?.nativeElement) { | ||||
|       this.gl = this.canvas.nativeElement.getContext('webgl'); | ||||
|       if (this.gl) { | ||||
|         this.initCanvas(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @HostListener('window:resize', ['$event']) | ||||
|   resizeCanvas(): void { | ||||
| @ -224,6 +232,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|   } | ||||
| 
 | ||||
|   compileShader(src, type): WebGLShader { | ||||
|     if (!this.gl) { | ||||
|       return; | ||||
|     } | ||||
|     const shader = this.gl.createShader(type); | ||||
| 
 | ||||
|     this.gl.shaderSource(shader, src); | ||||
| @ -237,6 +248,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|   } | ||||
| 
 | ||||
|   buildShaderProgram(shaderInfo): WebGLProgram { | ||||
|     if (!this.gl) { | ||||
|       return; | ||||
|     } | ||||
|     const program = this.gl.createProgram(); | ||||
| 
 | ||||
|     shaderInfo.forEach((desc) => { | ||||
| @ -273,7 +287,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|       now = performance.now(); | ||||
|     } | ||||
|     // skip re-render if there's no change to the scene
 | ||||
|     if (this.scene) { | ||||
|     if (this.scene && this.gl) { | ||||
|       /* SET UP SHADER UNIFORMS */ | ||||
|       // screen dimensions
 | ||||
|       this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight); | ||||
|  | ||||
| @ -90,7 +90,7 @@ export const download = (href, name) => { | ||||
| 
 | ||||
| export function detectWebGL(): boolean { | ||||
|   const canvas = document.createElement('canvas'); | ||||
|   const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); | ||||
|   const gl = canvas.getContext('webgl'); | ||||
|   return !!(gl && gl instanceof WebGLRenderingContext); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -110,7 +110,7 @@ export default class ReusablePage extends ConcurrencyImplementation { | ||||
|           page.waitForSelector('meta[property="og:preview:fail"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 }).then(() => false) | ||||
|         ]) | ||||
|       } catch (e) { | ||||
|         logger.err(`failed to load frontend during page initialization: ` + (e instanceof Error ? e.message : `${e}`)); | ||||
|         logger.err(`failed to load frontend during page initialization  ${page.clusterGroup}:${page.index}: ` + (e instanceof Error ? e.message : `${e}`)); | ||||
|         page.repairRequested = true; | ||||
|       } | ||||
|     } | ||||
| @ -131,7 +131,7 @@ export default class ReusablePage extends ConcurrencyImplementation { | ||||
| 
 | ||||
|   protected async repairPage(page) { | ||||
|     // create a new page
 | ||||
|     logger.debug(`Repairing page ${page.clusterGroup}:${page.index}`); | ||||
|     logger.info(`Repairing page ${page.clusterGroup}:${page.index}`); | ||||
|     const newPage = await this.initPage(); | ||||
|     newPage.free = true; | ||||
|     // replace the old page
 | ||||
| @ -142,8 +142,9 @@ export default class ReusablePage extends ConcurrencyImplementation { | ||||
|       await page.goto('about:blank', {timeout: 200}); // prevents memory leak (maybe?)
 | ||||
|     } catch (e) { | ||||
|       logger.err(`unexpected page repair error ${page.clusterGroup}:${page.index}`); | ||||
|     } | ||||
|     } finally { | ||||
|       await page.close(); | ||||
|     } | ||||
|     return newPage; | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -22,7 +22,7 @@ export default class ReusableSSRPage extends ReusablePage { | ||||
|     page.clusterGroup = 'slurper'; | ||||
|     page.language = null; | ||||
|     page.createdAt = Date.now(); | ||||
|     const defaultUrl = mempoolHost + '/about'; | ||||
|     const defaultUrl = mempoolHost + '/preview/block/1'; | ||||
| 
 | ||||
|     page.on('pageerror', (err) => { | ||||
|       console.log(err); | ||||
| @ -39,7 +39,7 @@ export default class ReusableSSRPage extends ReusablePage { | ||||
|           headers: {"Access-Control-Allow-Origin": "*"}, | ||||
|           body: mockImageBuffer | ||||
|         }); | ||||
|       } else if (!['document', 'script', 'xhr', 'fetch'].includes(req.resourceType())) { | ||||
|       } else if (req.resourceType() === 'media') { | ||||
|         return req.abort(); | ||||
|       } else { | ||||
|         return req.continue(); | ||||
| @ -49,7 +49,7 @@ export default class ReusableSSRPage extends ReusablePage { | ||||
|       await page.goto(defaultUrl, { waitUntil: "networkidle0" }); | ||||
|       await page.waitForSelector('meta[property="og:meta:ready"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 }); | ||||
|     } catch (e) { | ||||
|       logger.err(`failed to load frontend during ssr page initialization: ` + (e instanceof Error ? e.message : `${e}`)); | ||||
|       logger.err(`failed to load frontend during ssr page initialization ${page.clusterGroup}:${page.index}: ` + (e instanceof Error ? e.message : `${e}`)); | ||||
|       page.repairRequested = true; | ||||
|     } | ||||
|     page.free = true; | ||||
|  | ||||
| @ -28,13 +28,18 @@ class Server { | ||||
|   mempoolUrl: URL; | ||||
|   network: string; | ||||
|   secureHost = true; | ||||
|   secureMempoolHost = true; | ||||
|   canonicalHost: string; | ||||
| 
 | ||||
|   seoQueueLength: number = 0; | ||||
|   unfurlQueueLength: number = 0; | ||||
| 
 | ||||
|   constructor() { | ||||
|     this.app = express(); | ||||
|     this.mempoolHost = config.MEMPOOL.HTTP_HOST + (config.MEMPOOL.HTTP_PORT ? ':' + config.MEMPOOL.HTTP_PORT : ''); | ||||
|     this.mempoolUrl = new URL(this.mempoolHost); | ||||
|     this.secureHost = config.SERVER.HOST.startsWith('https'); | ||||
|     this.secureMempoolHost = config.MEMPOOL.HTTP_HOST.startsWith('https'); | ||||
|     this.network = config.MEMPOOL.NETWORK || 'bitcoin'; | ||||
| 
 | ||||
|     let canonical; | ||||
| @ -121,6 +126,7 @@ class Server { | ||||
|   } | ||||
| 
 | ||||
|   async clusterTask({ page, data: { url, path, action, reqUrl } }) { | ||||
|     const start = Date.now(); | ||||
|     try { | ||||
|       logger.info(`rendering "${reqUrl}" on tab ${page.clusterGroup}:${page.index}`); | ||||
|       const urlParts = parseLanguageUrl(path); | ||||
| @ -155,6 +161,7 @@ class Server { | ||||
|           captureBeyondViewport: false, | ||||
|           clip: { width: 1200, height: 600, x: 0, y: 0, scale: 1 }, | ||||
|         }); | ||||
|         logger.info(`rendered unfurl img in ${Date.now() - start}ms for "${reqUrl}" on tab ${page.clusterGroup}:${page.index}`); | ||||
|         return screenshot; | ||||
|       } else if (success === false) { | ||||
|         logger.warn(`failed to render ${reqUrl} for ${action} due to client-side error, e.g. requested an invalid txid`); | ||||
| @ -170,13 +177,14 @@ class Server { | ||||
|   } | ||||
| 
 | ||||
|   async ssrClusterTask({ page, data: { url, path, action, reqUrl } }) { | ||||
|     const start = Date.now(); | ||||
|     try { | ||||
|       logger.info(`slurping "${reqUrl}" on tab ${page.clusterGroup}:${page.index}`); | ||||
|       const urlParts = parseLanguageUrl(path); | ||||
|       if (page.language !== urlParts.lang) { | ||||
|         // switch language
 | ||||
|         page.language = urlParts.lang; | ||||
|         const localizedUrl = urlParts.lang ? `${this.mempoolHost}/${urlParts.lang}${urlParts.path}` : `${this.mempoolHost}${urlParts.path}` ; | ||||
|         const localizedUrl = urlParts.lang ? `${this.mempoolHost}/${urlParts.lang}${urlParts.path}` : `${this.mempoolHost}${urlParts.path}`; | ||||
|         await page.goto(localizedUrl, { waitUntil: "load" }); | ||||
|       } else { | ||||
|         const loaded = await page.evaluate(async (path) => { | ||||
| @ -199,14 +207,17 @@ class Server { | ||||
|         return !!window['soft404']; | ||||
|       }); | ||||
|       if (is404) { | ||||
|         logger.info(`slurp 404 in ${Date.now() - start}ms for "${reqUrl}" on tab ${page.clusterGroup}:${page.index}`); | ||||
|         return '404'; | ||||
|       } else { | ||||
|         let html = await page.content(); | ||||
|         logger.info(`rendered slurp in ${Date.now() - start}ms for "${reqUrl}" on tab ${page.clusterGroup}:${page.index}`); | ||||
|         return html; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       if (e instanceof TimeoutError) { | ||||
|         let html = await page.content(); | ||||
|         logger.info(`rendered partial slurp in ${Date.now() - start}ms for "${reqUrl}" on tab ${page.clusterGroup}:${page.index}`); | ||||
|         return html; | ||||
|       } else { | ||||
|         logger.err(`failed to render ${reqUrl} for ${action}: ` + (e instanceof Error ? e.message : `${e}`)); | ||||
| @ -221,6 +232,8 @@ class Server { | ||||
| 
 | ||||
|   async renderPreview(req, res) { | ||||
|     try { | ||||
|       this.unfurlQueueLength++; | ||||
|       const start = Date.now(); | ||||
|       const rawPath = req.params[0]; | ||||
| 
 | ||||
|       let img = null; | ||||
| @ -231,6 +244,7 @@ class Server { | ||||
|       // don't bother unless the route is definitely renderable
 | ||||
|       if (rawPath.includes('/preview/') && matchedRoute.render) { | ||||
|         img = await this.cluster?.execute({ url: this.mempoolHost + rawPath, path: rawPath, action: 'screenshot', reqUrl: req.url }); | ||||
|         logger.info(`unfurl returned "${req.url}" in ${Date.now() - start}ms | ${this.unfurlQueueLength - 1} tasks in queue`); | ||||
|       } else { | ||||
|         logger.info('rendering not enabled for page "' + req.url + '"'); | ||||
|       } | ||||
| @ -245,6 +259,8 @@ class Server { | ||||
|     } catch (e) { | ||||
|       logger.err(e instanceof Error ? e.message : `${e} ${req.params[0]}`); | ||||
|       res.status(500).send(e instanceof Error ? e.message : e); | ||||
|     } finally { | ||||
|       this.unfurlQueueLength--; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -263,13 +279,13 @@ class Server { | ||||
|         return; | ||||
|       } else { | ||||
|         logger.info('proxying resource "' + req.url + '"'); | ||||
|         if (this.secureHost) { | ||||
|           https.get(config.SERVER.HOST + rawPath, { headers: { 'user-agent': 'mempoolunfurl' }}, (got) => { | ||||
|         if (this.secureMempoolHost) { | ||||
|           https.get(this.mempoolHost + rawPath, { headers: { 'user-agent': 'mempoolunfurl' }}, (got) => { | ||||
|             res.writeHead(got.statusCode, got.headers); | ||||
|             return got.pipe(res); | ||||
|           }); | ||||
|         } else { | ||||
|           http.get(config.SERVER.HOST + rawPath, { headers: { 'user-agent': 'mempoolunfurl' }}, (got) => { | ||||
|           http.get(this.mempoolHost + rawPath, { headers: { 'user-agent': 'mempoolunfurl' }}, (got) => { | ||||
|             res.writeHead(got.statusCode, got.headers); | ||||
|             return got.pipe(res); | ||||
|           }); | ||||
| @ -284,7 +300,10 @@ class Server { | ||||
|         logger.info('unfurling "' + req.url + '"'); | ||||
|         result = await this.renderUnfurlMeta(rawPath); | ||||
|       } else { | ||||
|         this.seoQueueLength++; | ||||
|         const start = Date.now(); | ||||
|         result = await this.renderSEOPage(rawPath, req.url); | ||||
|         logger.info(`slurp returned "${req.url}" in ${Date.now() - start}ms | ${this.seoQueueLength - 1} tasks in queue`); | ||||
|       } | ||||
|       if (result && result.length) { | ||||
|         if (result === '404') { | ||||
| @ -298,6 +317,10 @@ class Server { | ||||
|     } catch (e) { | ||||
|       logger.err(e instanceof Error ? e.message : `${e} ${req.params[0]}`); | ||||
|       res.status(500).send(e instanceof Error ? e.message : e); | ||||
|     } finally { | ||||
|       if (!unfurl) { | ||||
|         this.seoQueueLength--; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user