tango-app-api-trax 3.9.71 → 3.9.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-trax",
3
- "version": "3.9.71",
3
+ "version": "3.9.72",
4
4
  "description": "Trax",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -29,7 +29,8 @@ import { insertSingleProcessData } from '../controllers/trax.controller.js';
29
29
  import handlebars from './handlebar-helper.js';
30
30
  import { buildVisitChecklistTemplateData, getCompiledVisitChecklistTemplate, createImageCache, resolveTemplateUrls } from '../utils/visitChecklistPdf.utils.js';
31
31
  import archiver from 'archiver';
32
- import { getBrowser as getBrowserInstance, withPdfSlot } from '../utils/browserPool.utils.js';
32
+ import puppeteer from 'puppeteer';
33
+ import { getBrowser as getBrowserInstance } from '../utils/browserPool.utils.js';
33
34
  import fs from 'fs';
34
35
  import path from 'path';
35
36
  import { fileURLToPath as toPath } from 'url';
@@ -1565,17 +1566,18 @@ export async function list( req, res ) {
1565
1566
 
1566
1567
  export async function aomupdateCollection( req, res ) {
1567
1568
  try {
1568
- let url = 'https://webservice.pos.lenskart.com/v1/auth';
1569
+ const lenskart = JSON.parse( process.env.LENSKART );
1570
+ let url = lenskart.authUrl;
1569
1571
  let authOptions = {
1570
1572
  method: 'POST',
1571
1573
  headers: {
1572
1574
  'Content-Type': 'application/json',
1573
1575
  'Accept': 'application/json',
1574
- 'X-Lenskart-App-Id': 'connect',
1575
- 'X-Lenskart-API-Key': 'valyoo123',
1576
- 'Cookie': '__cf_bm=Pbosu4MHkZsUH02XQVJ7rPP5kN6BGYZsvydcFUR6wnk-1727068950-1.0.1.1-0c_G2gw0yUGhwOsrhnDJUBzRyUaXrbeQpw8hNcxLwdOXVeRradjFTnwm.PimMADZ2ctZPc793LMQArnsz6UIqw; __cfruid=3a09251544865452d64b8fe4c8ccabafbe765f53-1727068950',
1576
+ 'X-Lenskart-App-Id': lenskart.appId,
1577
+ 'X-Lenskart-API-Key': lenskart.apiKey,
1578
+ 'Cookie': lenskart.authCookie,
1577
1579
  },
1578
- body: JSON.stringify( { isEncodingRequired: true, password: '55eyetango123', userName: 'tango.eye' } ),
1580
+ body: JSON.stringify( { isEncodingRequired: true, password: lenskart.password, userName: lenskart.userName } ),
1579
1581
  };
1580
1582
  const authResponse = await fetch( url, authOptions );
1581
1583
 
@@ -1584,16 +1586,16 @@ export async function aomupdateCollection( req, res ) {
1584
1586
  }
1585
1587
 
1586
1588
  let response = await authResponse.json();
1587
- url = 'https://central.pos.lenskart.com/v1/user/api/storeAmMappings?pageNumber=1&pageSize=100';
1589
+ url = `${lenskart.amMappingUrl}?pageNumber=1&pageSize=100`;
1588
1590
  let mappingOptions = {
1589
1591
  method: 'GET',
1590
1592
  headers: {
1591
1593
  'accept': '*/*',
1592
- 'X-Lenskart-API-Key': 'valyoo123',
1593
- 'X-Lenskart-App-Id': 'connect',
1594
+ 'X-Lenskart-API-Key': lenskart.apiKey,
1595
+ 'X-Lenskart-App-Id': lenskart.appId,
1594
1596
  'X-Lenskart-Session-Token': `${response.sessionToken}`,
1595
1597
  'X-Api-Client': 'tango',
1596
- 'Cookie': '__cf_bm=wZmE1A4v745PfwlA92BGxPH2R5kHh3R9MdM_BEyjl1k-1726754172-1.0.1.1-yfUjGhyOedlNlQIZMKnOAIO1HBm336bjsnynmcCXCDOlnZfAz5eUq7nfOff4ZUfFCNwri_T08aeJsj8J1jo79w; __cf_bm=10uJnkQI0Ou_knGdtgd4lxAFzlPbIKYFj3pyt3epj9w-1727069223-1.0.1.1-AiAGvsODGksNcDRNbwy1o9kNEEfGJdcPh_se9d1HPjFPz3cogtSTfrEBbGCTRyoOTi.2YaC0pvAggs_IqMz7aQ; __cfruid=3a09251544865452d64b8fe4c8ccabafbe765f53-1727068950',
1598
+ 'Cookie': lenskart.dataCookie,
1597
1599
  },
1598
1600
  };
1599
1601
  let mappingResponse = await fetch( url, mappingOptions );
@@ -1601,16 +1603,16 @@ export async function aomupdateCollection( req, res ) {
1601
1603
  const mappingPages = mappingResponse?.content.pageable.total_pages;
1602
1604
  for ( let index = mappingPages; index > 0; index-- ) {
1603
1605
  const page = index;
1604
- url = `https://central.pos.lenskart.com/v1/user/api/storeAmMappings?pageNumber=${page}&pageSize=100`;
1606
+ url = `${lenskart.amMappingUrl}?pageNumber=${page}&pageSize=100`;
1605
1607
  const mappingOptions = {
1606
1608
  method: 'GET',
1607
1609
  headers: {
1608
1610
  'accept': '*/*',
1609
- 'X-Lenskart-API-Key': 'valyoo123',
1610
- 'X-Lenskart-App-Id': 'connect',
1611
+ 'X-Lenskart-API-Key': lenskart.apiKey,
1612
+ 'X-Lenskart-App-Id': lenskart.appId,
1611
1613
  'X-Lenskart-Session-Token': `${response.sessionToken}`,
1612
1614
  'X-Api-Client': 'tango',
1613
- 'Cookie': '__cf_bm=wZmE1A4v745PfwlA92BGxPH2R5kHh3R9MdM_BEyjl1k-1726754172-1.0.1.1-yfUjGhyOedlNlQIZMKnOAIO1HBm336bjsnynmcCXCDOlnZfAz5eUq7nfOff4ZUfFCNwri_T08aeJsj8J1jo79w; __cf_bm=10uJnkQI0Ou_knGdtgd4lxAFzlPbIKYFj3pyt3epj9w-1727069223-1.0.1.1-AiAGvsODGksNcDRNbwy1o9kNEEfGJdcPh_se9d1HPjFPz3cogtSTfrEBbGCTRyoOTi.2YaC0pvAggs_IqMz7aQ; __cfruid=3a09251544865452d64b8fe4c8ccabafbe765f53-1727068950',
1615
+ 'Cookie': lenskart.dataCookie,
1614
1616
  },
1615
1617
  };
1616
1618
  let mappingResponse = await fetch( url, mappingOptions );
@@ -1637,21 +1639,18 @@ export async function aomupdateCollection( req, res ) {
1637
1639
 
1638
1640
  export async function saleUpdateCollection( req, res ) {
1639
1641
  try {
1640
- let url = 'https://webservice.pos.lenskart.com/v1/auth';
1642
+ const lenskart = JSON.parse( process.env.LENSKART );
1643
+ let url = lenskart.authUrl;
1641
1644
  const authOptions = {
1642
1645
  method: 'POST',
1643
1646
  headers: {
1644
1647
  'Content-Type': 'application/json',
1645
1648
  'Accept': 'application/json',
1646
- 'X-Lenskart-App-Id': 'connect',
1647
- 'X-Lenskart-API-Key': 'valyoo123',
1648
- 'Cookie': '__cf_bm=Pbosu4MHkZsUH02XQVJ7rPP5kN6BGYZsvydcFUR6wnk-1727068950-1.0.1.1-0c_G2gw0yUGhwOsrhnDJUBzRyUaXrbeQpw8hNcxLwdOXVeRradjFTnwm.PimMADZ2ctZPc793LMQArnsz6UIqw; __cfruid=3a09251544865452d64b8fe4c8ccabafbe765f53-1727068950',
1649
+ 'X-Lenskart-App-Id': lenskart.appId,
1650
+ 'X-Lenskart-API-Key': lenskart.apiKey,
1651
+ 'Cookie': lenskart.authCookie,
1649
1652
  },
1650
- body: JSON.stringify( {
1651
- isEncodingRequired: true,
1652
- password: '55eyetango123',
1653
- userName: 'tango.eye',
1654
- } ),
1653
+ body: JSON.stringify( { isEncodingRequired: true, password: lenskart.password, userName: lenskart.userName } ),
1655
1654
  };
1656
1655
  let authResponse = await fetch( url, authOptions );
1657
1656
 
@@ -1661,16 +1660,16 @@ export async function saleUpdateCollection( req, res ) {
1661
1660
 
1662
1661
  authResponse = await authResponse.json();
1663
1662
 
1664
- url='https://central.pos.lenskart.com/v1/user/api/storeSalesmanDetails?pageNumber=1&pageSize=100';
1663
+ url=`${lenskart.salesmanUrl}?pageNumber=1&pageSize=100`;
1665
1664
  const salesmanDetailsOptions = {
1666
1665
  method: 'GET',
1667
1666
  headers: {
1668
1667
  'accept': '*/*',
1669
- 'X-Lenskart-API-Key': 'valyoo123',
1670
- 'X-Lenskart-App-Id': 'connect',
1668
+ 'X-Lenskart-API-Key': lenskart.apiKey,
1669
+ 'X-Lenskart-App-Id': lenskart.appId,
1671
1670
  'X-Lenskart-Session-Token': `${authResponse.sessionToken}`,
1672
1671
  'X-Api-Client': 'tango',
1673
- 'Cookie': '__cf_bm=wZmE1A4v745PfwlA92BGxPH2R5kHh3R9MdM_BEyjl1k-1726754172-1.0.1.1-yfUjGhyOedlNlQIZMKnOAIO1HBm336bjsnynmcCXCDOlnZfAz5eUq7nfOff4ZUfFCNwri_T08aeJsj8J1jo79w; __cf_bm=10uJnkQI0Ou_knGdtgd4lxAFzlPbIKYFj3pyt3epj9w-1727069223-1.0.1.1-AiAGvsODGksNcDRNbwy1o9kNEEfGJdcPh_se9d1HPjFPz3cogtSTfrEBbGCTRyoOTi.2YaC0pvAggs_IqMz7aQ; __cfruid=3a09251544865452d64b8fe4c8ccabafbe765f53-1727068950',
1672
+ 'Cookie': lenskart.dataCookie,
1674
1673
  },
1675
1674
  };
1676
1675
  let salesmanDetailsResponse = await fetch( url, salesmanDetailsOptions );
@@ -1678,16 +1677,16 @@ export async function saleUpdateCollection( req, res ) {
1678
1677
  const salesmanDetailsPages = salesmanDetailsResponse?.content.pageable.total_pages;
1679
1678
  for ( let index = salesmanDetailsPages; index > 0; index-- ) {
1680
1679
  const page = index;
1681
- url= `https://central.pos.lenskart.com/v1/user/api/storeSalesmanDetails?pageNumber=${page}&pageSize=100`;
1680
+ url= `${lenskart.salesmanUrl}?pageNumber=${page}&pageSize=100`;
1682
1681
  const mappingOptions = {
1683
1682
  method: 'GET',
1684
1683
  headers: {
1685
1684
  'accept': '*/*',
1686
- 'X-Lenskart-API-Key': 'valyoo123',
1687
- 'X-Lenskart-App-Id': 'connect',
1685
+ 'X-Lenskart-API-Key': lenskart.apiKey,
1686
+ 'X-Lenskart-App-Id': lenskart.appId,
1688
1687
  'X-Lenskart-Session-Token': `${authResponse.sessionToken}`,
1689
1688
  'X-Api-Client': 'tango',
1690
- 'Cookie': '__cf_bm=wZmE1A4v745PfwlA92BGxPH2R5kHh3R9MdM_BEyjl1k-1726754172-1.0.1.1-yfUjGhyOedlNlQIZMKnOAIO1HBm336bjsnynmcCXCDOlnZfAz5eUq7nfOff4ZUfFCNwri_T08aeJsj8J1jo79w; __cf_bm=10uJnkQI0Ou_knGdtgd4lxAFzlPbIKYFj3pyt3epj9w-1727069223-1.0.1.1-AiAGvsODGksNcDRNbwy1o9kNEEfGJdcPh_se9d1HPjFPz3cogtSTfrEBbGCTRyoOTi.2YaC0pvAggs_IqMz7aQ; __cfruid=3a09251544865452d64b8fe4c8ccabafbe765f53-1727068950',
1689
+ 'Cookie': lenskart.dataCookie,
1691
1690
  },
1692
1691
  };
1693
1692
  let salesmanDetailsResponse = await fetch( url, mappingOptions );
@@ -2433,7 +2432,6 @@ export async function internalSendPushNotification( req, res ) {
2433
2432
  }
2434
2433
 
2435
2434
  let fcmToken;
2436
- // fcmToken = 'dwm2tz9WfUq1jdz1H8hfSf:APA91bF9BhTsG9ZDnutQfseGueXk7FZ4RhB8v3G_xJOZhkiZz8vsw3SUWk5su8ZN37lx3-H50eouBKinbwg4zE_br6f483jUswA_44f1XG8k7Sok995f77M';
2437
2435
  if ( requestHeader.email && requestHeader.email !='' ) {
2438
2436
  fcmToken = await getUserToken( requestHeader.clientid, requestHeader.email );
2439
2437
  } else {
@@ -3764,34 +3762,57 @@ export const downloadInsertPdf = async ( req, res ) => {
3764
3762
  }
3765
3763
  const cdnBase = JSON.parse( process.env.CDNURL )?.TraxAnswerCDN || '';
3766
3764
  const pdfTemplate = getCompiledVisitChecklistTemplate();
3765
+ const imageCache = createImageCache();
3767
3766
 
3768
3767
  const safeName = ( str ) =>
3769
3768
  ( str || '' ).toString().replace( /[<>:"/\\|?*]+/g, '_' );
3770
3769
 
3771
- // 1) Fetch the checklist document. The browser is acquired later, at
3772
- // point-of-use inside the render slot — grabbing it here and holding it
3773
- // across the slow image work below risks a stale (recycled) browser.
3774
- const aiDetails = await processedchecklist.findOne( { _id: req.body.checklistId } );
3770
+ // const query = {
3771
+ // query: {
3772
+ // bool: {
3773
+ // must: [
3774
+ // {
3775
+ // term: {
3776
+ // _id: req.body.checklistId,
3777
+ // },
3778
+ // },
3779
+ // ],
3780
+ // },
3781
+ // },
3782
+ // };
3783
+
3784
+
3785
+ // 1) Launch browser page + fetch OpenSearch data in parallel
3786
+ const [ browser, aiDetails ] = await Promise.all( [
3787
+ getBrowserInstance(),
3788
+ // getOpenSearchData( JSON.parse( process.env.OPENSEARCH ).traxIndex, query ),
3789
+ processedchecklist.findOne( { _id: req.body.checklistId } ),
3790
+ ] );
3791
+ console.log( aiDetails );
3775
3792
 
3776
3793
  if ( !aiDetails ) {
3777
3794
  return res.sendError( 'Checklist not found', 404 );
3778
3795
  }
3779
3796
 
3780
- const doc = aiDetails.toObject();
3797
+ const doc = { ...aiDetails?.toObject() };
3781
3798
 
3782
- // 2) Fetch brandInfo + compliance data in parallel.
3799
+ console.log( doc );
3800
+
3801
+ // 2) Fetch brandInfo + compliance data in parallel
3783
3802
  const complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
3803
+ const detectionPayload = {
3804
+ 'storeId': [ doc.store_id ],
3805
+ 'userEmail': doc.userEmail,
3806
+ 'sourceChecklist_id': doc.sourceCheckList_id,
3807
+ };
3808
+
3784
3809
  const [ brandInfo, complianceData ] = await Promise.all( [
3785
3810
  getBrandInfo( doc.client_id ),
3786
- LamdaServiceCall( complianceURL, {
3787
- storeId: [ doc.store_id ],
3788
- userEmail: doc.userEmail,
3789
- sourceChecklist_id: doc.sourceCheckList_id,
3790
- } ),
3811
+ LamdaServiceCall( complianceURL, detectionPayload ),
3791
3812
  ] );
3792
3813
 
3793
3814
  if ( complianceData?.data?.length ) {
3794
- doc.historyData = complianceData.data;
3815
+ doc['historyData'] = complianceData.data;
3795
3816
  }
3796
3817
 
3797
3818
  // CDN fix
@@ -3806,146 +3827,62 @@ export const downloadInsertPdf = async ( req, res ) => {
3806
3827
  } );
3807
3828
 
3808
3829
  const templateData = buildVisitChecklistTemplateData( doc, brandInfo );
3809
- // Keep images as CDN URLs (do NOT inline as base64) — Chrome fetches them
3810
- // at render time. Inlining produced a multi-MB HTML string that crashed
3811
- // puppeteer's setContent CDP transfer ("Target closed").
3812
3830
  const resolvedData = resolveTemplateUrls( templateData, cdnBase );
3831
+ await imageCache.resolveAllImages( resolvedData );
3813
3832
 
3814
3833
  const html = pdfTemplate( resolvedData );
3815
3834
 
3816
- // 3) Render inside a concurrency slot. Acquire a *fresh* browser from the
3817
- // pool at point-of-use, and retry on a new page if the render frame
3818
- // detaches (renderer crash / recycled browser) that surfaces as
3819
- // "Execution context is not available in detached frame".
3820
- // Render inside the concurrency slot; the slot returns just the PDF buffer.
3821
- const finalBuffer = await withPdfSlot( async () => {
3822
- const MAX_RENDER_ATTEMPTS = 2;
3823
- let buffer = null;
3824
- let lastError;
3825
-
3826
- for ( let attempt = 1; attempt <= MAX_RENDER_ATTEMPTS; attempt++ ) {
3827
- const browser = await getBrowserInstance();
3828
- page = await browser.newPage();
3835
+ // 3) Create page and render images are already base64 so use domcontentloaded
3836
+ page = await browser.newPage();
3837
+ await page.setViewport( { width: 1280, height: 1800 } );
3829
3838
 
3830
- try {
3831
- await page.setViewport( { width: 1280, height: 1800 } );
3832
- await page.setContent( html, {
3833
- waitUntil: 'domcontentloaded',
3834
- timeout: 30000,
3835
- } );
3836
- await page.emulateMediaType( 'screen' );
3837
-
3838
- // Images load from CDN URLs (not inlined). Wait for them to finish,
3839
- // capped at 30s so a slow/broken image can't hang the render.
3840
- await Promise.race( [
3841
- page.evaluate( async () => {
3842
- const images = Array.from( document.images );
3843
- await Promise.all(
3844
- images.map( ( img ) => {
3845
- if ( img.complete ) return;
3846
- return new Promise( ( resolve ) => {
3847
- img.onload = img.onerror = resolve;
3848
- } );
3849
- } ),
3850
- );
3851
- } ),
3852
- new Promise( ( resolve ) => setTimeout( resolve, 30000 ) ),
3853
- ] );
3854
-
3855
- // Downscale images in-page before printing. They're embedded at
3856
- // source (camera) resolution otherwise, which bloats the PDF and
3857
- // print memory. Cap the largest side at 1200px — plenty for the
3858
- // small display sizes. Processed one at a time to bound peak memory.
3859
- await page.evaluate( async ( maxDim ) => {
3860
- const imgs = Array.from( document.images );
3861
- for ( const img of imgs ) {
3862
- try {
3863
- const nw = img.naturalWidth;
3864
- const nh = img.naturalHeight;
3865
- if ( !nw || !nh ) continue;
3866
- const scale = maxDim / Math.max( nw, nh );
3867
- if ( scale >= 1 ) continue; // already small enough
3868
- const canvas = document.createElement( 'canvas' );
3869
- canvas.width = Math.round( nw * scale );
3870
- canvas.height = Math.round( nh * scale );
3871
- canvas.getContext( '2d' ).drawImage( img, 0, 0, canvas.width, canvas.height );
3872
- const dataUrl = canvas.toDataURL( 'image/jpeg', 0.75 );
3873
- canvas.width = 0;
3874
- canvas.height = 0;
3875
- img.src = dataUrl;
3876
- if ( img.decode ) await img.decode().catch( () => {} );
3877
- } catch ( e ) {
3878
- // Keep the original image if downscaling fails.
3879
- }
3880
- }
3881
- }, 900 );
3882
-
3883
- const pdfBuffer = await page.pdf( {
3884
- format: 'A4',
3885
- printBackground: true,
3886
- preferCSSPageSize: true,
3887
- displayHeaderFooter: true,
3888
- headerTemplate: '<span></span>',
3889
- footerTemplate: [
3890
- '<div style="width:100%;padding:0 10mm;font-size:10px;',
3891
- 'font-family:Arial,sans-serif;display:flex;',
3892
- 'justify-content:space-between;align-items:center;',
3893
- 'border-top:1px solid #d9d9d9;padding-top:4px;">',
3894
- '<span style="color:#999;">Page ',
3895
- '<span class="pageNumber"></span>',
3896
- ' of <span class="totalPages"></span></span>',
3897
- '<span style="display:flex;align-items:center;gap:6px;">',
3898
- '<span style="color:#666;font-weight:400;font-size:10px;">',
3899
- 'Generated by</span>',
3900
- tangoyeLogoSvg,
3901
- '</span></div>',
3902
- ].join( '' ),
3903
- } );
3904
-
3905
- if ( !pdfBuffer || pdfBuffer.length === 0 ) {
3906
- throw new Error( 'PDF generation resulted in empty buffer' );
3907
- }
3908
-
3909
- buffer = Buffer.isBuffer( pdfBuffer ) ? pdfBuffer : Buffer.from( pdfBuffer );
3910
- break;
3911
- } catch ( err ) {
3912
- lastError = err;
3913
- logger.error( {
3914
- functionName: 'downloadInsertPdf',
3915
- message: `PDF render attempt ${attempt} failed`,
3916
- docId: doc?._id,
3917
- error: err,
3918
- } );
3919
-
3920
- const msg = String( err?.message || '' );
3921
- const retriable =
3922
- msg.includes( 'detached frame' ) ||
3923
- msg.includes( 'Execution context' ) ||
3924
- msg.includes( 'Target closed' ) ||
3925
- msg.includes( 'Session closed' );
3926
-
3927
- if ( !retriable || attempt === MAX_RENDER_ATTEMPTS ) break;
3928
- } finally {
3929
- // Free the page (and its renderer) as soon as this attempt ends.
3930
- if ( page ) {
3931
- await page.close().catch( () => {} );
3932
- page = null;
3933
- }
3934
- }
3935
- }
3936
-
3937
- if ( !buffer ) {
3938
- logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed after retries', error: lastError } );
3939
- }
3940
- return buffer;
3941
- } );
3942
-
3943
- if ( !finalBuffer ) {
3839
+ try {
3840
+ await page.setContent( html, {
3841
+ waitUntil: 'domcontentloaded',
3842
+ timeout: 15000,
3843
+ } );
3844
+ await page.emulateMediaType( 'screen' );
3845
+ } catch ( err ) {
3846
+ logger.error( { functionName: 'downloadInsertPdf', message: 'setContent failed', error: err } );
3847
+ }
3848
+
3849
+ let pdfBuffer;
3850
+ try {
3851
+ pdfBuffer = await page.pdf( {
3852
+ format: 'A4',
3853
+ printBackground: true,
3854
+ preferCSSPageSize: true,
3855
+ displayHeaderFooter: true,
3856
+ headerTemplate: '<span></span>',
3857
+ footerTemplate: [
3858
+ '<div style="width:100%;padding:0 10mm;font-size:10px;',
3859
+ 'font-family:Arial,sans-serif;display:flex;',
3860
+ 'justify-content:space-between;align-items:center;',
3861
+ 'border-top:1px solid #d9d9d9;padding-top:4px;">',
3862
+ '<span style="color:#999;">Page ',
3863
+ '<span class="pageNumber"></span>',
3864
+ ' of <span class="totalPages"></span></span>',
3865
+ '<span style="display:flex;align-items:center;gap:6px;">',
3866
+ '<span style="color:#666;font-weight:400;font-size:10px;">',
3867
+ 'Generated by</span>',
3868
+ tangoyeLogoSvg,
3869
+ '</span></div>',
3870
+ ].join( '' ),
3871
+ } );
3872
+ } catch ( err ) {
3873
+ logger.error( { functionName: 'downloadInsertPdf', message: 'PDF generation failed', error: err } );
3944
3874
  return res.sendError( 'PDF generation failed', 500 );
3945
3875
  }
3946
3876
 
3947
- // Upload the PDF to S3 and return a signed download URL — a tiny JSON
3948
- // response, so the client never hits the "max response size" limit.
3877
+ if ( !pdfBuffer || pdfBuffer.length === 0 ) {
3878
+ logger.error( { functionName: 'downloadInsertPdf', message: 'Empty PDF', docId: doc._id } );
3879
+ return res.sendError( 'PDF generation resulted in empty buffer', 500 );
3880
+ }
3881
+
3882
+ const finalBuffer = Buffer.isBuffer( pdfBuffer ) ?
3883
+ pdfBuffer :
3884
+ Buffer.from( pdfBuffer );
3885
+
3949
3886
  let pdfName;
3950
3887
  if ( doc?.store_id ) {
3951
3888
  pdfName = `${safeName(
@@ -3957,17 +3894,13 @@ export const downloadInsertPdf = async ( req, res ) => {
3957
3894
  )}.pdf`;
3958
3895
  }
3959
3896
 
3960
- const bucket = JSON.parse( process.env.BUCKET );
3961
- const uploaded = await fileUpload( {
3962
- fileName: pdfName,
3963
- Key: 'traxDailyReports/',
3964
- Bucket: bucket.sop,
3965
- body: finalBuffer,
3966
- ContentType: 'application/pdf',
3897
+ res.set( {
3898
+ 'Content-Type': 'application/pdf',
3899
+ 'Content-Disposition': `attachment; filename="${pdfName}"`,
3900
+ 'Content-Length': finalBuffer.length,
3967
3901
  } );
3968
- const downloadUrl = await signedUrl( { file_path: uploaded.Key, Bucket: bucket.sop } );
3969
3902
 
3970
- return res.sendSuccess( { url: downloadUrl, key: uploaded.Key, fileName: pdfName } );
3903
+ return res.send( finalBuffer );
3971
3904
  } catch ( e ) {
3972
3905
  logger.error( { functionName: 'downloadInsertPdf', error: e } );
3973
3906
  return res.sendError( e, 500 );
@@ -4094,11 +4027,13 @@ export const downloadInsertPdfOld = async ( req, res ) => {
4094
4027
  },
4095
4028
  } );
4096
4029
 
4097
- const browser = await getBrowserInstance();
4098
- let page = null;
4030
+ const browser = await puppeteer.launch( {
4031
+ headless: 'new',
4032
+ args: [ '--no-sandbox', '--disable-dev-shm-usage' ],
4033
+ } );
4099
4034
 
4100
4035
  try {
4101
- page = await browser.newPage();
4036
+ const page = await browser.newPage();
4102
4037
 
4103
4038
  await page.setViewport( {
4104
4039
  width: 1280,
@@ -4177,8 +4112,7 @@ export const downloadInsertPdfOld = async ( req, res ) => {
4177
4112
  };
4178
4113
 
4179
4114
 
4180
- // let complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
4181
- let complianceURL = 'https://pbxdm4nstcpbaz6gf6c53ofeee0zvmfy.lambda-url.ap-south-1.on.aws/';
4115
+ let complianceURL = JSON.parse( process.env.LAMBDAURL ).complianceHistory;
4182
4116
  const complianceData = await LamdaServiceCall( complianceURL, detectionPayload );
4183
4117
  if ( complianceData?.data.length ) {
4184
4118
  doc['historyData'] = complianceData.data;
@@ -4334,7 +4268,7 @@ export const downloadInsertPdfOld = async ( req, res ) => {
4334
4268
  }
4335
4269
  }
4336
4270
  } finally {
4337
- if ( page ) await page.close().catch( () => {} );
4271
+ await browser.close();
4338
4272
  }
4339
4273
 
4340
4274
  // console.log( '🎉 All processing completed' );
@@ -28,7 +28,7 @@ import isSameOrBefore from 'dayjs/plugin/isSameOrBefore.js';
28
28
  import * as cameraService from '../services/camera.service.js';
29
29
  import * as recurringFlagTracker from '../services/recurringFlagTracker.service.js';
30
30
  dayjs.extend( isSameOrBefore );
31
- import { getBrowser, withPdfSlot } from '../utils/browserPool.utils.js';
31
+ import puppeteer from 'puppeteer';
32
32
  import handlebars from './handlebar-helper.js';
33
33
  import fs from 'fs';
34
34
  import path from 'path';
@@ -237,13 +237,12 @@ export async function startChecklist( req, res ) {
237
237
  } );
238
238
  let getupdatedchecklist = await processedchecklist.aggregate( findQuery );
239
239
  // let bucket = JSON.parse( process.env.BUCKET );
240
- const cdnurl = JSON.parse( process.env.CDNURL );
241
240
  getupdatedchecklist[0].questionAnswers.forEach( ( section ) => {
242
- section.questions.forEach( ( question ) => {
241
+ section.questions.forEach( async ( question ) => {
243
242
  if ( question.questionReferenceImage && question.questionReferenceImage!='' ) {
244
243
  question.questionReferenceImage = `${cdnurl.TraxAnswerCDN}${question.questionReferenceImage}`;
245
244
  }
246
- question.answers.forEach( ( answer ) => {
245
+ question.answers.forEach( async ( answer ) => {
247
246
  if ( answer.referenceImage != '' ) {
248
247
  answer.referenceImage = `${cdnurl.TraxAnswerCDN}${answer.referenceImage}`;
249
248
  }
@@ -5511,7 +5510,6 @@ export async function chunkUpload( req, res ) {
5511
5510
  }
5512
5511
 
5513
5512
  export async function downloadChecklist( req, res ) {
5514
- let page = null;
5515
5513
  try {
5516
5514
  let requestData = req.body;
5517
5515
 
@@ -5596,53 +5594,54 @@ export async function downloadChecklist( req, res ) {
5596
5594
  const template = handlebars.compile( templateHtml );
5597
5595
  const html = template( { data: checklistDetails } );
5598
5596
 
5599
- // Generate PDF on a page from the shared browser pool, capped by withPdfSlot
5600
- return await withPdfSlot( async () => {
5601
- const browser = await getBrowser();
5597
+ // Generate PDF using puppeteer instead of deprecated html-pdf
5598
+ const browser = await puppeteer.launch( {
5599
+ headless: 'new',
5600
+ args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ],
5601
+ } );
5602
5602
 
5603
- page = await browser.newPage();
5603
+ const page = await browser.newPage();
5604
5604
 
5605
- await page.setContent( html, {
5606
- waitUntil: 'domcontentloaded',
5607
- } );
5605
+ await page.setContent( html, {
5606
+ waitUntil: 'domcontentloaded',
5607
+ } );
5608
5608
 
5609
- /* -------- WAIT FOR ALL IMAGES TO LOAD -------- */
5609
+ /* -------- WAIT FOR ALL IMAGES TO LOAD -------- */
5610
5610
 
5611
- await page.evaluate( async () => {
5612
- const images = Array.from( document.images );
5611
+ await page.evaluate( async () => {
5612
+ const images = Array.from( document.images );
5613
5613
 
5614
- await Promise.all(
5615
- images.map( ( img ) => {
5616
- if ( img.complete ) return;
5614
+ await Promise.all(
5615
+ images.map( ( img ) => {
5616
+ if ( img.complete ) return;
5617
5617
 
5618
- return new Promise( ( resolve ) => {
5619
- img.onload = img.onerror = resolve;
5620
- } );
5621
- } ),
5622
- );
5623
- } );
5618
+ return new Promise( ( resolve ) => {
5619
+ img.onload = img.onerror = resolve;
5620
+ } );
5621
+ } ),
5622
+ );
5623
+ } );
5624
5624
 
5625
- /* ---------------- GENERATE PDF ---------------- */
5625
+ /* ---------------- GENERATE PDF ---------------- */
5626
5626
 
5627
- const pdfBuffer = await page.pdf( {
5628
- format: 'A4',
5629
- printBackground: true,
5630
- } );
5627
+ const pdfBuffer = await page.pdf( {
5628
+ format: 'A4',
5629
+ printBackground: true,
5630
+ } );
5631
5631
 
5632
- /* ---------------- RESPONSE ---------------- */
5632
+ await browser.close();
5633
5633
 
5634
- res.setHeader( 'Content-Type', 'application/pdf' );
5635
- res.setHeader(
5636
- 'Content-Disposition',
5637
- 'attachment; filename=checkListDetails.pdf',
5638
- );
5634
+ /* ---------------- RESPONSE ---------------- */
5639
5635
 
5640
- return res.end( pdfBuffer );
5641
- } );
5636
+ res.setHeader( 'Content-Type', 'application/pdf' );
5637
+ res.setHeader(
5638
+ 'Content-Disposition',
5639
+ 'attachment; filename=checkListDetails.pdf',
5640
+ );
5641
+
5642
+ return res.end( pdfBuffer );
5642
5643
  } catch ( e ) {
5643
5644
  logger.error( { functionName: 'getChecklistQuestionAnswers', error: e } );
5644
5645
  return res.sendError( e, 500 );
5645
- } finally {
5646
- if ( page ) await page.close().catch( () => {} );
5647
5646
  }
5648
5647
  }
@@ -447,7 +447,7 @@ export const flagCardsV1 = async ( req, res ) => {
447
447
  // These three lookups are independent of one another, so run them concurrently
448
448
  // instead of sequentially (Mongo aggregation + config find + detection Lambda).
449
449
  const detectionPayload = { fromDate, toDate, storeId, clientId };
450
- const lambdaURL = 'https://f65azvtljclaxp6l7rnx65cdmm0lcgvp.lambda-url.ap-south-1.on.aws/';
450
+ const lambdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
451
451
 
452
452
  const [ getOverallChecklistData, publishedAiChecklists, resultData ] = await Promise.all( [
453
453
  processedchecklistService.aggregate( findQuery ),
@@ -598,7 +598,7 @@ export const flagComparisonCardsV1 = async ( req, res ) => {
598
598
  return 0;
599
599
  }
600
600
 
601
- const LamdaURL = 'https://f65azvtljclaxp6l7rnx65cdmm0lcgvp.lambda-url.ap-south-1.on.aws/';
601
+ const LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
602
602
  const resultData = await LamdaServiceCall( LamdaURL, detectionPayload );
603
603
  const published = publishedAiChecklists.map( ( val ) => val?.checkListType );
604
604
  if ( resultData?.status_code === '200' ) {
@@ -854,7 +854,7 @@ export const flagTablesV1 = async ( req, res ) => {
854
854
  'clientId': requestData.clientId,
855
855
  };
856
856
 
857
- let LamdaURL = 'https://f65azvtljclaxp6l7rnx65cdmm0lcgvp.lambda-url.ap-south-1.on.aws/';
857
+ let LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
858
858
  let resultData = await LamdaServiceCall( LamdaURL, detectionPayload );
859
859
  if ( resultData ) {
860
860
  if ( resultData.status_code == '200' ) {
@@ -2005,7 +2005,7 @@ export const flagComparisonCardsV2 = async ( req, res ) => {
2005
2005
  };
2006
2006
 
2007
2007
  // The config lookup and the detection Lambda are independent — run them together.
2008
- const LamdaURL = 'https://f65azvtljclaxp6l7rnx65cdmm0lcgvp.lambda-url.ap-south-1.on.aws/';
2008
+ const LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
2009
2009
  const [ publishedAiChecklists, resultData ] = await Promise.all( [
2010
2010
  checklistconfigService.find(
2011
2011
  {
@@ -2291,7 +2291,7 @@ export const flagTablesV2 = async ( req, res ) => {
2291
2291
  'storeId': requestData.storeId,
2292
2292
  'clientId': requestData.clientId,
2293
2293
  };
2294
- // const LamdaURL = 'https://f65azvtljclaxp6l7rnx65cdmm0lcgvp.lambda-url.ap-south-1.on.aws/';
2294
+ // const LamdaURL = JSON.parse( process.env.LAMBDAURL ).flagTablesV2;
2295
2295
 
2296
2296
  let LamdaURL = lamdaUrl.flagTablesV2;
2297
2297
 
@@ -82,13 +82,11 @@
82
82
  .q-answer-link{font-size:12px;color:#0085D2;text-decoration:underline;word-break:break-all}
83
83
  .q-answer-caption{font-size:11px;color:#666;margin-bottom:4px}
84
84
  .q-answer-remarks{font-size:11px;color:#666;margin-top:6px;white-space:pre-line}
85
- /* User verification (Acknowledged by) */
85
+ /* User verification (Submitted By) */
86
86
  .user-verify{margin-top:28px;padding-top:18px;border-top:1px solid #d9d9d9;break-inside:avoid}
87
87
  .user-verify-title{font-size:15px;font-weight:700;color:#1a1a1a;margin-bottom:14px}
88
88
  .user-verify-photo{display:block;width:200px;height:240px;object-fit:cover;border-radius:8px;margin-bottom:10px}
89
89
  .user-verify-name{font-size:14px;color:#1a1a1a;font-weight:600}
90
- .user-verify-ack{font-size:14px;color:#1a1a1a;line-height:1.5;max-width:240px;margin-top:10px}
91
- .user-verify-ack strong{font-weight:700}
92
90
  .user-verify-sign-label{font-size:12px;color:#666;margin-top:16px;margin-bottom:6px;font-weight:600}
93
91
  .user-verify-signature{display:block;width:220px;height:auto;max-height:110px;object-fit:contain;border:1px solid #eee;border-radius:6px;padding:6px;background:#fff}
94
92
  /* Footer */
@@ -301,12 +299,12 @@
301
299
  {{/each}}
302
300
  {{#if showUserVerification}}
303
301
  <div class="user-verify">
304
- <div class="user-verify-title">Acknowledged by</div>
302
+ <div class="user-verify-title">Submitted By</div>
305
303
  {{#if userImage}}
306
- <img class="user-verify-photo" src="{{userImage}}" alt="Acknowledged by photo" />
304
+ <img class="user-verify-photo" src="{{userImage}}" alt="Submitted by photo" />
307
305
  {{/if}}
308
306
  {{#if submittedBy}}
309
- <div class="user-verify-ack">I, <strong>{{submittedBy}}</strong> acknowledge that the checklist responses are accurate and correct.</div>
307
+ <div class="user-verify-name">{{userSignature}}</div>
310
308
  {{/if}}
311
309
  </div>
312
310
  {{/if}}
@@ -70,43 +70,3 @@ export async function getBrowser() {
70
70
  return launchPromise;
71
71
  }
72
72
 
73
-
74
- /**
75
-
76
- * Concurrency limiter for PDF generation.
77
-
78
- * Caps how many PDF jobs (pages/renderers) run at once so a burst of requests
79
-
80
- * can't spawn unbounded Chrome work and exhaust memory. Configurable via
81
-
82
- * MAX_CONCURRENT_PDF (default 3). Run each PDF job inside withPdfSlot(); the
83
-
84
- * slot is always released, even if the task throws.
85
-
86
- */
87
-
88
- const MAX_CONCURRENT_PDF = Number( process.env.MAX_CONCURRENT_PDF ) || 3;
89
-
90
- let activePdf = 0;
91
-
92
- const pdfWaiters = [];
93
-
94
-
95
- export async function withPdfSlot( task ) {
96
- if ( activePdf >= MAX_CONCURRENT_PDF ) {
97
- await new Promise( ( resolve ) => pdfWaiters.push( resolve ) );
98
- }
99
-
100
- activePdf++;
101
-
102
- try {
103
- return await task();
104
- } finally {
105
- activePdf--;
106
-
107
- const next = pdfWaiters.shift();
108
-
109
- if ( next ) next();
110
- }
111
- }
112
-
@@ -4,7 +4,7 @@ import path from 'path';
4
4
 
5
5
  import { fileURLToPath } from 'url';
6
6
 
7
- import { getBrowser, withPdfSlot } from './browserPool.utils.js';
7
+ import puppeteer from 'puppeteer';
8
8
 
9
9
  import handlebars from '../controllers/handlebar-helper.js';
10
10
 
@@ -1285,19 +1285,14 @@ export function createImageCache() {
1285
1285
  }
1286
1286
 
1287
1287
 
1288
- // Compiled once and reused for every request. Reading the .hbs from disk and
1289
- // recompiling it on each PDF request was pure per-request overhead.
1290
- // NOTE: editing visit-checklist.hbs now requires a server restart to take effect.
1291
- let compiledVisitChecklistTemplate = null;
1292
-
1293
1288
  export function getCompiledVisitChecklistTemplate() {
1294
- if ( !compiledVisitChecklistTemplate ) {
1295
- const templatePath = path.join( __dirname, '../hbs/visit-checklist.hbs' );
1296
- const templateHtml = fs.readFileSync( templatePath, 'utf8' );
1297
- compiledVisitChecklistTemplate = handlebars.compile( templateHtml );
1298
- }
1289
+ const templatePath = path.join( __dirname, '../hbs/visit-checklist.hbs' );
1290
+
1299
1291
 
1300
- return compiledVisitChecklistTemplate;
1292
+ const templateHtml = fs.readFileSync( templatePath, 'utf8' );
1293
+
1294
+
1295
+ return handlebars.compile( templateHtml );
1301
1296
  }
1302
1297
 
1303
1298
 
@@ -1534,51 +1529,53 @@ export async function generateVisitChecklistPDF( templateData, baseUrl = 'https:
1534
1529
  const html = template( resolvedData );
1535
1530
 
1536
1531
 
1537
- // Render on a page from the shared browser pool, capped by withPdfSlot
1538
- return withPdfSlot( async () => {
1539
- const browser = await getBrowser();
1532
+ const browser = await puppeteer.launch( {
1540
1533
 
1541
- let page = null;
1534
+ headless: 'new',
1542
1535
 
1543
- try {
1544
- page = await browser.newPage();
1536
+ args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ],
1545
1537
 
1546
- await page.setContent( html, { waitUntil: 'domcontentloaded' } );
1538
+ } );
1547
1539
 
1548
1540
 
1549
- await page.evaluate( async () => {
1550
- const images = Array.from( document.images );
1541
+ try {
1542
+ const page = await browser.newPage();
1551
1543
 
1552
- await Promise.all(
1544
+ await page.setContent( html, { waitUntil: 'domcontentloaded' } );
1553
1545
 
1554
- images.map( ( img ) => {
1555
- if ( img.complete ) return;
1556
1546
 
1557
- return new Promise( ( resolve ) => {
1558
- img.onload = img.onerror = resolve;
1559
- } );
1560
- } ),
1547
+ await page.evaluate( async () => {
1548
+ const images = Array.from( document.images );
1561
1549
 
1562
- );
1563
- } );
1550
+ await Promise.all(
1564
1551
 
1552
+ images.map( ( img ) => {
1553
+ if ( img.complete ) return;
1565
1554
 
1566
- const pdfBuffer = await page.pdf( {
1555
+ return new Promise( ( resolve ) => {
1556
+ img.onload = img.onerror = resolve;
1557
+ } );
1558
+ } ),
1567
1559
 
1568
- format: 'A4',
1560
+ );
1561
+ } );
1569
1562
 
1570
- printBackground: true,
1571
1563
 
1572
- margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
1564
+ const pdfBuffer = await page.pdf( {
1573
1565
 
1574
- } );
1566
+ format: 'A4',
1575
1567
 
1568
+ printBackground: true,
1576
1569
 
1577
- return pdfBuffer;
1578
- } finally {
1579
- if ( page ) await page.close().catch( () => {} );
1580
- }
1581
- } );
1570
+ margin: { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' },
1571
+
1572
+ } );
1573
+
1574
+
1575
+ return pdfBuffer;
1576
+ } finally {
1577
+ await browser.close();
1578
+ }
1582
1579
  }
1583
1580
 
1584
1581