@govtechsg/oobee 0.10.85 → 0.10.87
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/.github/workflows/publish.yml +10 -0
- package/DETAILS.md +29 -0
- package/dist/cli.js +18 -5
- package/dist/combine.js +3 -1
- package/dist/constants/cliFunctions.js +2 -2
- package/dist/constants/common.js +70 -17
- package/dist/constants/constants.js +604 -1
- package/dist/crawlers/commonCrawlerFunc.js +3 -2
- package/dist/crawlers/crawlDomain.js +38 -13
- package/dist/crawlers/crawlIntelligentSitemap.js +62 -30
- package/dist/crawlers/crawlSitemap.js +141 -84
- package/dist/crawlers/custom/utils.js +218 -71
- package/dist/crawlers/guards/urlGuard.js +8 -15
- package/dist/crawlers/runCustom.js +18 -11
- package/dist/generateHtmlReport.js +18 -11
- package/dist/generateOobeeClientScanner.js +570 -0
- package/dist/mergeAxeResults/itemReferences.js +60 -25
- package/dist/mergeAxeResults/sentryTelemetry.js +4 -1
- package/dist/mergeAxeResults.js +23 -13
- package/dist/npmIndex.js +10 -2
- package/dist/proxyService.js +18 -3
- package/dist/services/s3Uploader.js +21 -10
- package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +6 -3
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +2 -2
- package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +1 -761
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +38 -2
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +1 -1
- package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +4 -4
- package/dist/static/ejs/summary.ejs +19 -8
- package/dist/utils.js +4 -3
- package/fix-summary-html-oom-pr.md +62 -0
- package/oobee-client-scanner.js +34992 -0
- package/package.json +5 -5
- package/src/cli.ts +19 -5
- package/src/combine.ts +5 -1
- package/src/constants/cliFunctions.ts +2 -2
- package/src/constants/common.ts +87 -22
- package/src/constants/constants.ts +602 -1
- package/src/crawlers/commonCrawlerFunc.ts +4 -3
- package/src/crawlers/crawlDomain.ts +39 -13
- package/src/crawlers/crawlIntelligentSitemap.ts +63 -30
- package/src/crawlers/crawlSitemap.ts +165 -100
- package/src/crawlers/custom/utils.ts +241 -80
- package/src/crawlers/guards/urlGuard.ts +24 -31
- package/src/crawlers/runCustom.ts +29 -11
- package/src/generateHtmlReport.ts +21 -11
- package/src/generateOobeeClientScanner.ts +591 -0
- package/src/mergeAxeResults/itemReferences.ts +70 -26
- package/src/mergeAxeResults/sentryTelemetry.ts +4 -1
- package/src/mergeAxeResults.ts +26 -14
- package/src/npmIndex.ts +12 -2
- package/src/proxyService.ts +25 -4
- package/src/services/s3Uploader.ts +23 -11
- package/src/static/ejs/partials/scripts/decodeUnzipParse.ejs +6 -3
- package/src/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +2 -2
- package/src/static/ejs/partials/scripts/ruleModal/constants.ejs +1 -761
- package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +38 -2
- package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +1 -1
- package/src/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +4 -4
- package/src/static/ejs/summary.ejs +19 -8
- package/src/utils.ts +4 -3
- package/testStaticJSScanner.html +534 -0
|
@@ -40,6 +40,7 @@ const RESTRICT_OVERLAY_TO_ENTRY_DOMAIN = parseBoolEnv(
|
|
|
40
40
|
process.env.RESTRICT_OVERLAY_TO_ENTRY_DOMAIN,
|
|
41
41
|
false,
|
|
42
42
|
);
|
|
43
|
+
const OVERLAY_OPERATION_TIMEOUT_MS = 5000;
|
|
43
44
|
|
|
44
45
|
const isOverlayAllowed = (currentUrl: string, entryUrl: string) => {
|
|
45
46
|
try {
|
|
@@ -100,17 +101,17 @@ export const screenshotFullPage = async (page, screenshotsDir: string, screensho
|
|
|
100
101
|
window.scrollTo(0, document.body.scrollHeight);
|
|
101
102
|
});
|
|
102
103
|
|
|
103
|
-
const isLoadMoreContent = async () =>
|
|
104
|
-
new Promise(resolve =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
const isLoadMoreContent = async () => {
|
|
105
|
+
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
106
|
+
if (page.isClosed()) return false;
|
|
107
|
+
try {
|
|
108
|
+
await page.waitForLoadState('domcontentloaded');
|
|
109
|
+
const newHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
110
|
+
return newHeight > prevHeight;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
114
115
|
|
|
115
116
|
const result = await isLoadMoreContent();
|
|
116
117
|
return result;
|
|
@@ -306,7 +307,7 @@ export const addOverlayMenu = async (
|
|
|
306
307
|
collapsed: false,
|
|
307
308
|
},
|
|
308
309
|
) => {
|
|
309
|
-
await page.waitForLoadState('domcontentloaded');
|
|
310
|
+
await page.waitForLoadState('domcontentloaded', { timeout: OVERLAY_OPERATION_TIMEOUT_MS });
|
|
310
311
|
consoleLogger.info(`Overlay menu: adding to ${menuPos}...`);
|
|
311
312
|
|
|
312
313
|
// Add the overlay menu with initial styling
|
|
@@ -409,25 +410,72 @@ export const addOverlayMenu = async (
|
|
|
409
410
|
const h2 = document.createElement('h2');
|
|
410
411
|
h2.id = 'oobeeHPagesScanned';
|
|
411
412
|
h2.className = 'oobee-section-title';
|
|
412
|
-
h2.textContent =
|
|
413
|
-
|
|
413
|
+
h2.textContent = `Pages Scanned (${vars.urlsCrawled.scanned.length || 0})`;
|
|
414
|
+
|
|
415
|
+
const scanIcon = document.createElement('span');
|
|
416
|
+
scanIcon.className = 'oobee-btn-icon';
|
|
417
|
+
|
|
418
|
+
const SCAN_SVG = `
|
|
419
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
420
|
+
<g clip-path="url(#clip0_1421_431)">
|
|
421
|
+
<path d="M12.5763 11.5472L12.2958 11.2857L12.1037 11.1005C12.776 10.3183 12.9194 9.56432 12.9194 8.45969C12.9194 5.99657 10.9228 4 8.45969 4C5.99657 4 4 5.99657 4 8.45969C4 10.9228 5.99657 12.9194 8.45969 12.9194C9.56432 12.9194 10.3183 12.776 11.1005 12.1037L11.2857 12.2958L11.5472 12.5763L14.9777 16L16 14.9777L12.5763 11.5472ZM8.45969 11.5472C6.75129 11.5472 5.37221 10.1681 5.37221 8.45969C5.37221 6.75129 6.75129 5.37221 8.45969 5.37221C10.1681 5.37221 11.5472 6.75129 11.5472 8.45969C11.5472 10.1681 10.1681 11.5472 8.45969 11.5472Z" fill="white"/>
|
|
422
|
+
<path d="M18.5 0H19.5C19.7761 0 20 0.223858 20 0.5V5H18.5V0Z" fill="white"/>
|
|
423
|
+
<path d="M19.5 2.18552e-08L19.5 1.5L15 1.5L15 -2.18556e-07L19.5 2.18552e-08Z" fill="white"/>
|
|
424
|
+
<path d="M1.5 0H0.5C0.223858 0 0 0.223858 0 0.5V5H1.5V0Z" fill="white"/>
|
|
425
|
+
<path d="M0.5 2.18552e-08L0.5 1.5L5 1.5L5 -2.18556e-07L0.5 2.18552e-08Z" fill="white"/>
|
|
426
|
+
<path d="M1.5 20H0.5C0.223858 20 0 19.7761 0 19.5V15H1.5V20Z" fill="white"/>
|
|
427
|
+
<path d="M0.5 20L0.5 18.5L5 18.5L5 20L0.5 20Z" fill="white"/>
|
|
428
|
+
<path d="M18.5 20H19.5C19.7761 20 20 19.7761 20 19.5V15H18.5V20Z" fill="white"/>
|
|
429
|
+
<path d="M19.5 20L19.5 18.5L15 18.5L15 20L19.5 20Z" fill="white"/>
|
|
430
|
+
</g>
|
|
431
|
+
<defs>
|
|
432
|
+
<clipPath id="clip0_1421_431">
|
|
433
|
+
<rect width="20" height="20" fill="white"/>
|
|
434
|
+
</clipPath>
|
|
435
|
+
</defs>
|
|
436
|
+
</svg>
|
|
437
|
+
`;
|
|
438
|
+
|
|
439
|
+
scanIcon.innerHTML = SCAN_SVG;
|
|
414
440
|
const scanBtn = document.createElement('button');
|
|
415
441
|
scanBtn.id = 'oobeeBtnScan';
|
|
416
442
|
scanBtn.className = 'oobee-btn oobee-btn-primary';
|
|
417
|
-
scanBtn.innerText = 'Scan this page';
|
|
418
443
|
scanBtn.disabled = inProgress;
|
|
444
|
+
scanBtn.appendChild(scanIcon);
|
|
445
|
+
|
|
446
|
+
const scanText = document.createElement('span');
|
|
447
|
+
scanText.className = 'oobee-btn-text';
|
|
448
|
+
scanText.innerText = 'Scan page';
|
|
449
|
+
scanBtn.appendChild(scanText);
|
|
450
|
+
|
|
419
451
|
scanBtn.addEventListener('click', async () => customWindow.handleOnScanClick?.());
|
|
420
452
|
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
453
|
+
const endScanIcon = document.createElement('span');
|
|
454
|
+
endScanIcon.className = 'oobee-btn-icon';
|
|
455
|
+
|
|
456
|
+
const ENDSCAN_SVG =
|
|
457
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
458
|
+
<path d="M10 0C4.47 0 0 4.47 0 10C0 15.53 4.47 20 10 20C15.53 20 20 15.53 20 10C20 4.47 15.53 0 10 0ZM10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10 2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18ZM13.59 5L10 8.59L6.41 5L5 6.41L8.59 10L5 13.59L6.41 15L10 11.41L13.59 15L15 13.59L11.41 10L15 6.41L13.59 5Z" fill="#9021A6"/>
|
|
459
|
+
</svg>
|
|
460
|
+
`;
|
|
461
|
+
|
|
462
|
+
endScanIcon.innerHTML = ENDSCAN_SVG;
|
|
463
|
+
const endScanBtn = document.createElement('button');
|
|
464
|
+
endScanBtn.id = 'oobeeBtnEndScan';
|
|
465
|
+
endScanBtn.className = 'oobee-btn oobee-btn-secondary';
|
|
466
|
+
endScanBtn.appendChild(endScanIcon);
|
|
467
|
+
|
|
468
|
+
const endScanText = document.createElement('span');
|
|
469
|
+
endScanText.className = 'oobee-btn-text';
|
|
470
|
+
endScanText.innerText = 'End scan';
|
|
471
|
+
endScanBtn.appendChild(endScanText);
|
|
472
|
+
|
|
473
|
+
endScanBtn.addEventListener('click', async () => customWindow.handleOnStopClick?.());
|
|
426
474
|
|
|
427
475
|
const btnGroup = document.createElement('div');
|
|
428
476
|
btnGroup.className = 'oobee-actions';
|
|
429
477
|
btnGroup.appendChild(scanBtn);
|
|
430
|
-
btnGroup.appendChild(
|
|
478
|
+
btnGroup.appendChild(endScanBtn);
|
|
431
479
|
|
|
432
480
|
const listWrap = document.createElement('div');
|
|
433
481
|
listWrap.id = 'oobeeList';
|
|
@@ -503,7 +551,7 @@ export const addOverlayMenu = async (
|
|
|
503
551
|
border-right: 1px solid rgba(0,0,0,.08)
|
|
504
552
|
}
|
|
505
553
|
.oobee-panel.collapsed {
|
|
506
|
-
width:
|
|
554
|
+
width: 58px;
|
|
507
555
|
overflow: hidden
|
|
508
556
|
}
|
|
509
557
|
|
|
@@ -580,6 +628,12 @@ export const addOverlayMenu = async (
|
|
|
580
628
|
padding: 1rem;
|
|
581
629
|
}
|
|
582
630
|
|
|
631
|
+
.oobee-panel.collapsed .oobee-actions {
|
|
632
|
+
display: flex;
|
|
633
|
+
justify-content: center;
|
|
634
|
+
padding: 1rem 0.7rem;
|
|
635
|
+
}
|
|
636
|
+
|
|
583
637
|
/* Base button */
|
|
584
638
|
.oobee-btn {
|
|
585
639
|
width: 100%;
|
|
@@ -590,6 +644,10 @@ export const addOverlayMenu = async (
|
|
|
590
644
|
line-height: 1.2;
|
|
591
645
|
font-weight: 400;
|
|
592
646
|
cursor: pointer;
|
|
647
|
+
display: flex;
|
|
648
|
+
align-items: center;
|
|
649
|
+
justify-content: center;
|
|
650
|
+
gap: 10px;
|
|
593
651
|
transition: {
|
|
594
652
|
box-shadow .12s ease,
|
|
595
653
|
transform .02s ease,
|
|
@@ -603,6 +661,19 @@ export const addOverlayMenu = async (
|
|
|
603
661
|
cursor:not-allowed
|
|
604
662
|
}
|
|
605
663
|
|
|
664
|
+
.oobee-panel.collapsed .oobee-btn {
|
|
665
|
+
width: 44px !important;
|
|
666
|
+
height: 44px !important;
|
|
667
|
+
min-width: 44px !important;
|
|
668
|
+
min-height: 44px !important;
|
|
669
|
+
max-width: 44px !important;
|
|
670
|
+
max-height: 44px !important;
|
|
671
|
+
border-radius: 50% !important;
|
|
672
|
+
padding: 0 !important;
|
|
673
|
+
justify-content: center;
|
|
674
|
+
gap: 0;
|
|
675
|
+
}
|
|
676
|
+
|
|
606
677
|
/* Primary (filled) */
|
|
607
678
|
.oobee-btn-primary {
|
|
608
679
|
background: #9021a6;
|
|
@@ -658,6 +729,25 @@ export const addOverlayMenu = async (
|
|
|
658
729
|
display: none;
|
|
659
730
|
}
|
|
660
731
|
|
|
732
|
+
.oobee-btn-icon {
|
|
733
|
+
display: inline-flex;
|
|
734
|
+
align-items: center;
|
|
735
|
+
justify-content: center;
|
|
736
|
+
width: 20px;
|
|
737
|
+
height: 20px;
|
|
738
|
+
vertical-align: middle;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.oobee-btn-text {
|
|
742
|
+
display: inline;
|
|
743
|
+
white-space: nowrap;
|
|
744
|
+
vertical-align: middle;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.oobee-panel.collapsed .oobee-btn-text {
|
|
748
|
+
display: none;
|
|
749
|
+
}
|
|
750
|
+
|
|
661
751
|
#oobeeStopOverlay[hidden] {
|
|
662
752
|
display:none !important;
|
|
663
753
|
}
|
|
@@ -675,7 +765,10 @@ export const addOverlayMenu = async (
|
|
|
675
765
|
}
|
|
676
766
|
|
|
677
767
|
.oobee-panel.collapsed .oobee-section-title {
|
|
678
|
-
|
|
768
|
+
font-size: 14px;
|
|
769
|
+
display: flex;
|
|
770
|
+
justify-content: center;
|
|
771
|
+
text-align: center;
|
|
679
772
|
}
|
|
680
773
|
|
|
681
774
|
.oobee-ol {
|
|
@@ -1051,6 +1144,7 @@ export const addOverlayMenu = async (
|
|
|
1051
1144
|
})
|
|
1052
1145
|
.catch(error => {
|
|
1053
1146
|
consoleLogger.error('Overlay menu: failed to add', error);
|
|
1147
|
+
throw error;
|
|
1054
1148
|
});
|
|
1055
1149
|
};
|
|
1056
1150
|
|
|
@@ -1073,11 +1167,19 @@ export const removeOverlayMenu = async page => {
|
|
|
1073
1167
|
|
|
1074
1168
|
export const initNewPage = async (page, pageClosePromises, processPageParams, pagesDict) => {
|
|
1075
1169
|
let menuPos = MENU_POSITION.right;
|
|
1170
|
+
let overlayRefreshSeq = 0;
|
|
1171
|
+
let overlayRefreshChain = Promise.resolve();
|
|
1076
1172
|
|
|
1077
1173
|
// eslint-disable-next-line no-underscore-dangle
|
|
1078
1174
|
const pageId = page._guid;
|
|
1079
1175
|
|
|
1080
|
-
page.on('dialog',
|
|
1176
|
+
page.on('dialog', async dialog => {
|
|
1177
|
+
try {
|
|
1178
|
+
await dialog.dismiss();
|
|
1179
|
+
} catch {
|
|
1180
|
+
// dialog may already be closed
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1081
1183
|
|
|
1082
1184
|
const pageClosePromise = new Promise(resolve => {
|
|
1083
1185
|
page.on('close', () => {
|
|
@@ -1096,6 +1198,83 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1096
1198
|
};
|
|
1097
1199
|
}
|
|
1098
1200
|
|
|
1201
|
+
const reconcileOverlayMenu = async (trigger: string) => {
|
|
1202
|
+
// Mark this as the latest refresh so older ones can stop.
|
|
1203
|
+
const refreshSeq = ++overlayRefreshSeq;
|
|
1204
|
+
|
|
1205
|
+
// Serialize overlay updates so multiple navigation events do not add/remove concurrently.
|
|
1206
|
+
overlayRefreshChain = overlayRefreshChain
|
|
1207
|
+
.catch(() => {})
|
|
1208
|
+
.then(async () => {
|
|
1209
|
+
if (refreshSeq !== overlayRefreshSeq || page.isClosed()) return;
|
|
1210
|
+
|
|
1211
|
+
try {
|
|
1212
|
+
// `framenavigated` can fire before the new document is ready for DOM inspection/injection.
|
|
1213
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 5000 });
|
|
1214
|
+
} catch {
|
|
1215
|
+
// Best effort only. The page may still be mid-navigation.
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
try {
|
|
1219
|
+
// Give fast redirect chains a brief chance to advance before we inject/remove the overlay.
|
|
1220
|
+
await page.waitForTimeout(300);
|
|
1221
|
+
} catch {
|
|
1222
|
+
// Best effort only. The page may already be closing.
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Re-check staleness after waiting because a newer navigation may have happened meanwhile.
|
|
1226
|
+
if (refreshSeq !== overlayRefreshSeq || page.isClosed()) return;
|
|
1227
|
+
|
|
1228
|
+
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
1229
|
+
|
|
1230
|
+
if (!allowed) {
|
|
1231
|
+
await Promise.race([
|
|
1232
|
+
removeOverlayMenu(page),
|
|
1233
|
+
new Promise((_, reject) => {
|
|
1234
|
+
setTimeout(() => {
|
|
1235
|
+
reject(
|
|
1236
|
+
new Error(
|
|
1237
|
+
`removeOverlayMenu timed out after ${OVERLAY_OPERATION_TIMEOUT_MS}ms`,
|
|
1238
|
+
),
|
|
1239
|
+
);
|
|
1240
|
+
}, OVERLAY_OPERATION_TIMEOUT_MS);
|
|
1241
|
+
}),
|
|
1242
|
+
]);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const hasOverlay = await page.evaluate(() =>
|
|
1247
|
+
Boolean(document.querySelector('#oobeeShadowHost')),
|
|
1248
|
+
);
|
|
1249
|
+
|
|
1250
|
+
consoleLogger.info(`Overlay state (${trigger}): ${hasOverlay}`);
|
|
1251
|
+
|
|
1252
|
+
if (!hasOverlay) {
|
|
1253
|
+
// Recreate the overlay after allowed redirects while preserving current UI state.
|
|
1254
|
+
consoleLogger.info(`Adding overlay menu to page (${trigger}): ${page.url()}`);
|
|
1255
|
+
await Promise.race([
|
|
1256
|
+
addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1257
|
+
inProgress: !!pagesDict[pageId]?.isScanning,
|
|
1258
|
+
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1259
|
+
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1260
|
+
}),
|
|
1261
|
+
new Promise((_, reject) => {
|
|
1262
|
+
setTimeout(() => {
|
|
1263
|
+
reject(
|
|
1264
|
+
new Error(`addOverlayMenu timed out after ${OVERLAY_OPERATION_TIMEOUT_MS}ms`),
|
|
1265
|
+
);
|
|
1266
|
+
}, OVERLAY_OPERATION_TIMEOUT_MS);
|
|
1267
|
+
}),
|
|
1268
|
+
]);
|
|
1269
|
+
}
|
|
1270
|
+
})
|
|
1271
|
+
.catch(() => {
|
|
1272
|
+
consoleLogger.info('Error in adding overlay menu to page');
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
await overlayRefreshChain;
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1099
1278
|
type handleOnScanClickFunction = () => void;
|
|
1100
1279
|
|
|
1101
1280
|
// Window functions exposed in browser
|
|
@@ -1109,17 +1288,8 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1109
1288
|
log('Scan: success');
|
|
1110
1289
|
pagesDict[pageId].isScanning = false;
|
|
1111
1290
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
if (allowed) {
|
|
1115
|
-
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1116
|
-
inProgress: false,
|
|
1117
|
-
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1118
|
-
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1119
|
-
});
|
|
1120
|
-
} else {
|
|
1121
|
-
await removeOverlayMenu(page);
|
|
1122
|
-
}
|
|
1291
|
+
if (page.isClosed()) return;
|
|
1292
|
+
await reconcileOverlayMenu('scan-click');
|
|
1123
1293
|
} catch (error) {
|
|
1124
1294
|
log(`Scan failed ${error}`);
|
|
1125
1295
|
}
|
|
@@ -1150,10 +1320,10 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1150
1320
|
|
|
1151
1321
|
if (!inputValue?.confirmed) {
|
|
1152
1322
|
await page.evaluate(() => {
|
|
1153
|
-
const
|
|
1154
|
-
if (
|
|
1155
|
-
|
|
1156
|
-
|
|
1323
|
+
const endScanBtn = document.getElementById('oobeeBtnEndScan') as HTMLButtonElement | null;
|
|
1324
|
+
if (endScanBtn) {
|
|
1325
|
+
endScanBtn.disabled = false;
|
|
1326
|
+
endScanBtn.textContent = 'Stop';
|
|
1157
1327
|
}
|
|
1158
1328
|
});
|
|
1159
1329
|
return;
|
|
@@ -1182,55 +1352,46 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1182
1352
|
};
|
|
1183
1353
|
|
|
1184
1354
|
page.on('domcontentloaded', async () => {
|
|
1185
|
-
|
|
1186
|
-
|
|
1355
|
+
if (page.isClosed()) return;
|
|
1356
|
+
await reconcileOverlayMenu('domcontentloaded');
|
|
1187
1357
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1358
|
+
if (isCypressTest) {
|
|
1359
|
+
try {
|
|
1360
|
+
await handleOnScanClick();
|
|
1361
|
+
page.close();
|
|
1362
|
+
} catch {
|
|
1363
|
+
consoleLogger.info(
|
|
1364
|
+
`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`,
|
|
1365
|
+
);
|
|
1191
1366
|
}
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1192
1369
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1370
|
+
page.on('framenavigated', async (frame: any) => {
|
|
1371
|
+
if (frame !== page.mainFrame() || page.isClosed()) return;
|
|
1372
|
+
await reconcileOverlayMenu('framenavigated');
|
|
1373
|
+
});
|
|
1196
1374
|
|
|
1197
|
-
|
|
1375
|
+
try {
|
|
1376
|
+
if (page.isClosed()) return page;
|
|
1377
|
+
await page.exposeFunction('handleOnScanClick', handleOnScanClick);
|
|
1378
|
+
await page.exposeFunction('handleOnStopClick', handleOnStopClick);
|
|
1198
1379
|
|
|
1199
|
-
|
|
1200
|
-
consoleLogger.info(`Adding overlay menu to page: ${page.url()}`);
|
|
1201
|
-
await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos, {
|
|
1202
|
-
inProgress: !!pagesDict[pageId]?.isScanning,
|
|
1203
|
-
collapsed: !!pagesDict[pageId]?.collapsed,
|
|
1204
|
-
hideStopInput: !!processPageParams.customFlowLabel,
|
|
1205
|
-
});
|
|
1206
|
-
}
|
|
1380
|
+
type UpdateMenuPosFunction = (newPos: any) => void;
|
|
1207
1381
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
consoleLogger.info(`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`);
|
|
1214
|
-
}
|
|
1382
|
+
// Define the updateMenuPos function
|
|
1383
|
+
const updateMenuPos: UpdateMenuPosFunction = newPos => {
|
|
1384
|
+
const prevPos = menuPos;
|
|
1385
|
+
if (prevPos !== newPos) {
|
|
1386
|
+
menuPos = newPos;
|
|
1215
1387
|
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
await page.exposeFunction('handleOnScanClick', handleOnScanClick);
|
|
1222
|
-
await page.exposeFunction('handleOnStopClick', handleOnStopClick);
|
|
1223
|
-
|
|
1224
|
-
type UpdateMenuPosFunction = (newPos: any) => void;
|
|
1388
|
+
};
|
|
1389
|
+
await page.exposeFunction('updateMenuPos', updateMenuPos);
|
|
1390
|
+
} catch (e) {
|
|
1391
|
+
log(`Error exposing functions on page: ${e}`);
|
|
1392
|
+
}
|
|
1225
1393
|
|
|
1226
|
-
|
|
1227
|
-
const updateMenuPos: UpdateMenuPosFunction = newPos => {
|
|
1228
|
-
const prevPos = menuPos;
|
|
1229
|
-
if (prevPos !== newPos) {
|
|
1230
|
-
menuPos = newPos;
|
|
1231
|
-
}
|
|
1232
|
-
};
|
|
1233
|
-
await page.exposeFunction('updateMenuPos', updateMenuPos);
|
|
1394
|
+
await reconcileOverlayMenu('init');
|
|
1234
1395
|
|
|
1235
1396
|
return page;
|
|
1236
1397
|
};
|
|
@@ -5,41 +5,34 @@ export function addUrlGuardScript(context, opts = {}) {
|
|
|
5
5
|
|
|
6
6
|
const lastAllowedUrlByPage = new WeakMap();
|
|
7
7
|
|
|
8
|
-
const attachGuardsToPage =
|
|
8
|
+
const attachGuardsToPage = page => {
|
|
9
9
|
if (!lastAllowedUrlByPage.has(page) && fallbackUrl) {
|
|
10
10
|
lastAllowedUrlByPage.set(page, String(fallbackUrl));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
page
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
page
|
|
14
|
+
.addInitScript(() => {
|
|
15
|
+
const isAllowedProtocol = value => {
|
|
16
|
+
try {
|
|
17
|
+
const s = value instanceof URL ? value.toString() : String(value);
|
|
18
|
+
const { protocol } = new URL(s, window.location.href);
|
|
19
|
+
return protocol === 'http:' || protocol === 'https:';
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
const win = window;
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
win.location.assign = (nextUrl) => { if (isAllowedProtocol(nextUrl)) assignOriginal(nextUrl); };
|
|
36
|
-
win.location.replace = (nextUrl) => { if (isAllowedProtocol(nextUrl)) replaceOriginal(nextUrl); };
|
|
37
|
-
|
|
38
|
-
Object.defineProperty(win.location, 'href', {
|
|
39
|
-
get() { return String(win.location.toString()); },
|
|
40
|
-
set(nextUrl) { if (isAllowedProtocol(nextUrl)) assignOriginal(nextUrl); },
|
|
27
|
+
const openOriginal = win.open;
|
|
28
|
+
win.open = function (targetUrl, ...args) {
|
|
29
|
+
if (!isAllowedProtocol(targetUrl)) return null;
|
|
30
|
+
return openOriginal.call(this, targetUrl, ...args);
|
|
31
|
+
};
|
|
32
|
+
})
|
|
33
|
+
.catch(() => {
|
|
34
|
+
// page may have closed before addInitScript completed; safe to ignore
|
|
41
35
|
});
|
|
42
|
-
});
|
|
43
36
|
|
|
44
37
|
const restoreToSafeUrl = async (page, attemptedUrl) => {
|
|
45
38
|
try {
|
|
@@ -50,15 +43,15 @@ export function addUrlGuardScript(context, opts = {}) {
|
|
|
50
43
|
}
|
|
51
44
|
};
|
|
52
45
|
|
|
53
|
-
page.on('framenavigated', async
|
|
46
|
+
page.on('framenavigated', async frame => {
|
|
54
47
|
if (frame !== page.mainFrame()) return;
|
|
55
48
|
|
|
56
49
|
const urlStr = frame.url();
|
|
57
50
|
let urlObj;
|
|
58
51
|
try {
|
|
59
|
-
|
|
52
|
+
urlObj = new URL(urlStr);
|
|
60
53
|
} catch {
|
|
61
|
-
|
|
54
|
+
return restoreToSafeUrl(page, urlStr);
|
|
62
55
|
}
|
|
63
56
|
|
|
64
57
|
if (ALLOWED_PROTOCOLS.has(urlObj.protocol)) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-env browser */
|
|
2
|
-
import { chromium } from 'playwright';
|
|
3
2
|
import { createCrawleeSubFolders } from './commonCrawlerFunc.js';
|
|
4
3
|
import { cleanUpAndExit, register, registerSoftClose } from '../utils.js';
|
|
5
4
|
import constants, {
|
|
@@ -11,7 +10,12 @@ import { DEBUG, initNewPage, log } from './custom/utils.js';
|
|
|
11
10
|
import { guiInfoLog } from '../logs.js';
|
|
12
11
|
import { ViewportSettingsClass } from '../combine.js';
|
|
13
12
|
import { addUrlGuardScript } from './guards/urlGuard.js';
|
|
14
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getBrowserToRun,
|
|
15
|
+
getPlaywrightLaunchOptions,
|
|
16
|
+
initModifiedUserAgent,
|
|
17
|
+
} from '../constants/common.js';
|
|
18
|
+
import { BrowserTypes } from '../constants/constants.js';
|
|
15
19
|
|
|
16
20
|
// Export of classes
|
|
17
21
|
|
|
@@ -50,6 +54,8 @@ export class ProcessPageParams {
|
|
|
50
54
|
const runCustom = async (
|
|
51
55
|
url: string,
|
|
52
56
|
randomToken: string,
|
|
57
|
+
browserToRun: string,
|
|
58
|
+
userDataDirectory: string,
|
|
53
59
|
viewportSettings: ViewportSettingsClass,
|
|
54
60
|
blacklistedPatterns: string[] | null,
|
|
55
61
|
includeScreenshots: boolean,
|
|
@@ -81,10 +87,19 @@ const runCustom = async (
|
|
|
81
87
|
const pageClosePromises = [];
|
|
82
88
|
|
|
83
89
|
try {
|
|
90
|
+
const { browserToRun: resolvedBrowserToRun } = getBrowserToRun(
|
|
91
|
+
randomToken,
|
|
92
|
+
browserToRun as BrowserTypes,
|
|
93
|
+
false,
|
|
94
|
+
);
|
|
84
95
|
const deviceConfig = viewportSettings.playwrightDeviceDetailsObject;
|
|
85
96
|
const hasCustomViewport = !!deviceConfig;
|
|
97
|
+
const rawDevice = (deviceConfig || {}) as Record<string, unknown>;
|
|
98
|
+
const { userAgent: deviceUserAgent, ...contextDeviceOptions } = rawDevice;
|
|
86
99
|
|
|
87
|
-
|
|
100
|
+
await initModifiedUserAgent(resolvedBrowserToRun, viewportSettings.playwrightDeviceDetailsObject);
|
|
101
|
+
|
|
102
|
+
const baseLaunchOptions = getPlaywrightLaunchOptions(resolvedBrowserToRun);
|
|
88
103
|
|
|
89
104
|
// Merge base args with custom flow specific args
|
|
90
105
|
const baseArgs = baseLaunchOptions.args || [];
|
|
@@ -94,17 +109,15 @@ const runCustom = async (
|
|
|
94
109
|
...customArgs,
|
|
95
110
|
];
|
|
96
111
|
|
|
97
|
-
const
|
|
112
|
+
const context = await constants.launcher.launchPersistentContext(userDataDirectory, {
|
|
98
113
|
...baseLaunchOptions,
|
|
99
114
|
args: mergedArgs,
|
|
100
115
|
headless: false,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const context = await browser.newContext({
|
|
104
116
|
ignoreHTTPSErrors: true,
|
|
105
117
|
serviceWorkers: 'block',
|
|
106
118
|
viewport: null,
|
|
107
|
-
...(hasCustomViewport ?
|
|
119
|
+
...(hasCustomViewport ? contextDeviceOptions : {}),
|
|
120
|
+
userAgent: process.env.OOBEE_USER_AGENT || (deviceUserAgent as string | undefined),
|
|
108
121
|
});
|
|
109
122
|
|
|
110
123
|
register(context);
|
|
@@ -112,7 +125,6 @@ const runCustom = async (
|
|
|
112
125
|
processPageParams.stopAll = async () => {
|
|
113
126
|
try {
|
|
114
127
|
await context.close().catch(() => {});
|
|
115
|
-
await browser.close().catch(() => {});
|
|
116
128
|
} catch {}
|
|
117
129
|
};
|
|
118
130
|
|
|
@@ -121,12 +133,18 @@ const runCustom = async (
|
|
|
121
133
|
|
|
122
134
|
addUrlGuardScript(context, { fallbackUrl: url });
|
|
123
135
|
|
|
136
|
+
const page = context.pages().find(existingPage => !existingPage.isClosed()) || (await context.newPage());
|
|
137
|
+
await initNewPage(page, pageClosePromises, processPageParams, pagesDict);
|
|
138
|
+
|
|
124
139
|
// Detection of new page
|
|
125
140
|
context.on('page', async newPage => {
|
|
126
|
-
|
|
141
|
+
try {
|
|
142
|
+
await initNewPage(newPage, pageClosePromises, processPageParams, pagesDict);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
log(`Error initializing new page: ${e}`);
|
|
145
|
+
}
|
|
127
146
|
});
|
|
128
147
|
|
|
129
|
-
const page = await context.newPage();
|
|
130
148
|
await page.goto(url, { timeout: 0 });
|
|
131
149
|
|
|
132
150
|
// to execute and wait for all pages to close
|