@govtechsg/oobee 0.10.85 → 0.10.86
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/image.yml +3 -2
- package/.github/workflows/publish.yml +10 -0
- package/DETAILS.md +29 -0
- package/dist/combine.js +1 -1
- package/dist/constants/common.js +15 -4
- package/dist/constants/constants.js +604 -1
- package/dist/crawlers/commonCrawlerFunc.js +3 -2
- package/dist/crawlers/crawlSitemap.js +98 -80
- package/dist/crawlers/custom/utils.js +137 -31
- package/dist/crawlers/guards/urlGuard.js +8 -15
- package/dist/crawlers/runCustom.js +18 -11
- package/dist/generateOobeeClientScanner.js +570 -0
- package/dist/mergeAxeResults.js +5 -4
- 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/header/aboutScanModal/ScanConfiguration.ejs +2 -2
- package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +1 -761
- package/dist/static/ejs/summary.ejs +10 -5
- package/oobee-client-scanner.js +34992 -0
- package/package.json +2 -2
- package/src/combine.ts +3 -1
- package/src/constants/common.ts +22 -10
- package/src/constants/constants.ts +602 -1
- package/src/crawlers/commonCrawlerFunc.ts +4 -3
- package/src/crawlers/crawlSitemap.ts +116 -98
- package/src/crawlers/custom/utils.ts +143 -38
- package/src/crawlers/guards/urlGuard.ts +24 -31
- package/src/crawlers/runCustom.ts +29 -11
- package/src/generateOobeeClientScanner.ts +591 -0
- package/src/mergeAxeResults.ts +5 -3
- 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/header/aboutScanModal/ScanConfiguration.ejs +2 -2
- package/src/static/ejs/partials/scripts/ruleModal/constants.ejs +1 -761
- package/src/static/ejs/summary.ejs +10 -5
- package/testStaticJSScanner.html +534 -0
|
@@ -196,7 +196,7 @@ export const filterAxeResults = (
|
|
|
196
196
|
const conformance = tags.filter(tag => tag.startsWith('wcag') || tag === 'best-practice');
|
|
197
197
|
|
|
198
198
|
nodes.forEach(node => {
|
|
199
|
-
const { html } = node;
|
|
199
|
+
const { html, target } = node;
|
|
200
200
|
if (!(rule in passed.rules)) {
|
|
201
201
|
passed.rules[rule] = {
|
|
202
202
|
description,
|
|
@@ -207,9 +207,10 @@ export const filterAxeResults = (
|
|
|
207
207
|
items: [],
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
|
-
|
|
210
|
+
|
|
211
211
|
const finalHtml = truncateHtml(html);
|
|
212
|
-
|
|
212
|
+
const xpath = target.length === 1 && typeof target[0] === 'string' ? target[0] : undefined;
|
|
213
|
+
passed.rules[rule].items.push({ html: finalHtml, screenshotPath: '', message: '', xpath: xpath || '' });
|
|
213
214
|
|
|
214
215
|
passed.totalItems += 1;
|
|
215
216
|
passed.rules[rule].totalItems += 1;
|
|
@@ -76,6 +76,7 @@ const crawlSitemap = async ({
|
|
|
76
76
|
let dataset: crawlee.Dataset;
|
|
77
77
|
let urlsCrawled: UrlsCrawled;
|
|
78
78
|
let durationExceeded = false;
|
|
79
|
+
let isAbortingScan = false;
|
|
79
80
|
|
|
80
81
|
if (fromCrawlIntelligentSitemap) {
|
|
81
82
|
dataset = datasetFromIntelligent;
|
|
@@ -244,135 +245,152 @@ const crawlSitemap = async ({
|
|
|
244
245
|
return;
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
|
|
248
|
+
try {
|
|
249
|
+
await waitForPageLoaded(page, 10000);
|
|
248
250
|
|
|
249
|
-
|
|
251
|
+
const actualUrl = page.url() || request.loadedUrl || request.url;
|
|
250
252
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
const hasExceededDuration =
|
|
254
|
+
scanDuration > 0 && Date.now() - crawlStartTime > scanDuration * 1000;
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
if (urlsCrawled.scanned.length >= maxRequestsPerCrawl || hasExceededDuration) {
|
|
257
|
+
isAbortingScan = true;
|
|
258
|
+
if (hasExceededDuration) {
|
|
259
|
+
console.log(`Crawl duration of ${scanDuration}s exceeded. Aborting sitemap crawl.`);
|
|
260
|
+
durationExceeded = true;
|
|
261
|
+
}
|
|
262
|
+
crawler.autoscaledPool.abort(); // stops new requests
|
|
263
|
+
return;
|
|
258
264
|
}
|
|
259
|
-
crawler.autoscaledPool.abort(); // stops new requests
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
265
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
266
|
+
if (request.skipNavigation && actualUrl === 'about:blank') {
|
|
267
|
+
if (isScanPdfs) {
|
|
268
|
+
// pushes download promise into pdfDownloads
|
|
269
|
+
const { pdfFileName, url } = handlePdfDownload(
|
|
270
|
+
randomToken,
|
|
271
|
+
pdfDownloads,
|
|
272
|
+
request,
|
|
273
|
+
sendRequest,
|
|
274
|
+
urlsCrawled,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
uuidToPdfMapping[pdfFileName] = url;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
guiInfoLog(guiInfoStatusTypes.SKIPPED, {
|
|
282
|
+
numScanned: urlsCrawled.scanned.length,
|
|
283
|
+
urlScanned: request.url,
|
|
284
|
+
});
|
|
285
|
+
urlsCrawled.userExcluded.push({
|
|
286
|
+
url: request.url,
|
|
287
|
+
pageTitle: request.url,
|
|
288
|
+
actualUrl: request.url, // because about:blank is not useful
|
|
289
|
+
metadata: STATUS_CODE_METADATA[1],
|
|
290
|
+
httpStatusCode: 1,
|
|
291
|
+
});
|
|
273
292
|
|
|
274
|
-
uuidToPdfMapping[pdfFileName] = url;
|
|
275
293
|
return;
|
|
276
294
|
}
|
|
277
295
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
urlScanned: request.url,
|
|
281
|
-
});
|
|
282
|
-
urlsCrawled.userExcluded.push({
|
|
283
|
-
url: request.url,
|
|
284
|
-
pageTitle: request.url,
|
|
285
|
-
actualUrl: request.url, // because about:blank is not useful
|
|
286
|
-
metadata: STATUS_CODE_METADATA[1],
|
|
287
|
-
httpStatusCode: 1,
|
|
288
|
-
});
|
|
296
|
+
const contentType = response?.headers?.()['content-type'] || '';
|
|
297
|
+
const status = response ? response.status() : 0;
|
|
289
298
|
|
|
290
|
-
|
|
291
|
-
|
|
299
|
+
if (isScanHtml && status < 300 && isWhitelistedContentType(contentType)) {
|
|
300
|
+
const isRedirected = !areLinksEqual(page.url(), request.url);
|
|
301
|
+
const isLoadedUrlInCrawledUrls = urlsCrawled.scanned.some(
|
|
302
|
+
item => (item.actualUrl || item.url) === page.url(),
|
|
303
|
+
);
|
|
292
304
|
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
if (isRedirected && isLoadedUrlInCrawledUrls) {
|
|
306
|
+
urlsCrawled.notScannedRedirects.push({
|
|
307
|
+
fromUrl: request.url,
|
|
308
|
+
toUrl: actualUrl, // i.e. actualUrl
|
|
309
|
+
});
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
295
312
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
313
|
+
// This logic is different from crawlDomain, as it also checks if the pae is redirected before checking if it is excluded using exclusions.txt
|
|
314
|
+
if (isRedirected && blacklistedPatterns && isSkippedUrl(actualUrl, blacklistedPatterns)) {
|
|
315
|
+
urlsCrawled.userExcluded.push({
|
|
316
|
+
url: request.url,
|
|
317
|
+
pageTitle: request.url,
|
|
318
|
+
actualUrl,
|
|
319
|
+
metadata: STATUS_CODE_METADATA[0],
|
|
320
|
+
httpStatusCode: 0,
|
|
321
|
+
});
|
|
301
322
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
323
|
+
guiInfoLog(guiInfoStatusTypes.SKIPPED, {
|
|
324
|
+
numScanned: urlsCrawled.scanned.length,
|
|
325
|
+
urlScanned: request.url,
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
309
329
|
|
|
310
|
-
|
|
311
|
-
if (isRedirected && blacklistedPatterns && isSkippedUrl(actualUrl, blacklistedPatterns)) {
|
|
312
|
-
urlsCrawled.userExcluded.push({
|
|
313
|
-
url: request.url,
|
|
314
|
-
pageTitle: request.url,
|
|
315
|
-
actualUrl,
|
|
316
|
-
metadata: STATUS_CODE_METADATA[0],
|
|
317
|
-
httpStatusCode: 0,
|
|
318
|
-
});
|
|
330
|
+
const results = await runAxeScript({ includeScreenshots, page, randomToken });
|
|
319
331
|
|
|
320
|
-
guiInfoLog(guiInfoStatusTypes.
|
|
332
|
+
guiInfoLog(guiInfoStatusTypes.SCANNED, {
|
|
321
333
|
numScanned: urlsCrawled.scanned.length,
|
|
322
334
|
urlScanned: request.url,
|
|
323
335
|
});
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
336
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
urlsCrawled.scanned.push({
|
|
335
|
-
url: request.url,
|
|
336
|
-
pageTitle: results.pageTitle,
|
|
337
|
-
actualUrl, // i.e. actualUrl
|
|
338
|
-
});
|
|
337
|
+
urlsCrawled.scanned.push({
|
|
338
|
+
url: request.url,
|
|
339
|
+
pageTitle: results.pageTitle,
|
|
340
|
+
actualUrl, // i.e. actualUrl
|
|
341
|
+
});
|
|
339
342
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
343
|
+
urlsCrawled.scannedRedirects.push({
|
|
344
|
+
fromUrl: request.url,
|
|
345
|
+
toUrl: actualUrl,
|
|
346
|
+
});
|
|
344
347
|
|
|
345
|
-
|
|
346
|
-
|
|
348
|
+
results.url = request.url;
|
|
349
|
+
results.actualUrl = actualUrl;
|
|
347
350
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
await dataset.pushData(results);
|
|
352
|
+
} else {
|
|
353
|
+
guiInfoLog(guiInfoStatusTypes.SKIPPED, {
|
|
354
|
+
numScanned: urlsCrawled.scanned.length,
|
|
355
|
+
urlScanned: request.url,
|
|
356
|
+
});
|
|
354
357
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
358
|
+
if (isScanHtml) {
|
|
359
|
+
// carry through the HTTP status metadata
|
|
360
|
+
const status = response?.status();
|
|
361
|
+
const metadata =
|
|
362
|
+
typeof status === 'number'
|
|
363
|
+
? STATUS_CODE_METADATA[status] || STATUS_CODE_METADATA[599]
|
|
364
|
+
: STATUS_CODE_METADATA[2];
|
|
365
|
+
|
|
366
|
+
urlsCrawled.invalid.push({
|
|
367
|
+
actualUrl,
|
|
368
|
+
url: request.url,
|
|
369
|
+
pageTitle: request.url,
|
|
370
|
+
metadata,
|
|
371
|
+
httpStatusCode: typeof status === 'number' ? status : 0,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} catch (e) {
|
|
376
|
+
if (!isAbortingScan) {
|
|
377
|
+
guiInfoLog(guiInfoStatusTypes.ERROR, {
|
|
378
|
+
numScanned: urlsCrawled.scanned.length,
|
|
379
|
+
urlScanned: request.url,
|
|
380
|
+
});
|
|
362
381
|
|
|
363
|
-
urlsCrawled.
|
|
364
|
-
actualUrl,
|
|
382
|
+
urlsCrawled.error.push({
|
|
365
383
|
url: request.url,
|
|
366
384
|
pageTitle: request.url,
|
|
367
|
-
|
|
368
|
-
|
|
385
|
+
actualUrl: request.url,
|
|
386
|
+
metadata: STATUS_CODE_METADATA[2],
|
|
387
|
+
httpStatusCode: 0,
|
|
369
388
|
});
|
|
370
389
|
}
|
|
371
390
|
}
|
|
372
391
|
},
|
|
373
392
|
failedRequestHandler: async ({ request, response, error }) => {
|
|
374
|
-
|
|
375
|
-
if (urlsCrawled.scanned.length >= maxRequestsPerCrawl) {
|
|
393
|
+
if (isAbortingScan) {
|
|
376
394
|
return;
|
|
377
395
|
}
|
|
378
396
|
|
|
@@ -100,17 +100,17 @@ export const screenshotFullPage = async (page, screenshotsDir: string, screensho
|
|
|
100
100
|
window.scrollTo(0, document.body.scrollHeight);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
const isLoadMoreContent = async () =>
|
|
104
|
-
new Promise(resolve =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
const isLoadMoreContent = async () => {
|
|
104
|
+
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
105
|
+
if (page.isClosed()) return false;
|
|
106
|
+
try {
|
|
107
|
+
await page.waitForLoadState('domcontentloaded');
|
|
108
|
+
const newHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
109
|
+
return newHeight > prevHeight;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
114
|
|
|
115
115
|
const result = await isLoadMoreContent();
|
|
116
116
|
return result;
|
|
@@ -409,25 +409,72 @@ export const addOverlayMenu = async (
|
|
|
409
409
|
const h2 = document.createElement('h2');
|
|
410
410
|
h2.id = 'oobeeHPagesScanned';
|
|
411
411
|
h2.className = 'oobee-section-title';
|
|
412
|
-
h2.textContent =
|
|
413
|
-
|
|
412
|
+
h2.textContent = `Pages Scanned (${vars.urlsCrawled.scanned.length || 0})`;
|
|
413
|
+
|
|
414
|
+
const scanIcon = document.createElement('span');
|
|
415
|
+
scanIcon.className = 'oobee-btn-icon';
|
|
416
|
+
|
|
417
|
+
const SCAN_SVG = `
|
|
418
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
419
|
+
<g clip-path="url(#clip0_1421_431)">
|
|
420
|
+
<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"/>
|
|
421
|
+
<path d="M18.5 0H19.5C19.7761 0 20 0.223858 20 0.5V5H18.5V0Z" fill="white"/>
|
|
422
|
+
<path d="M19.5 2.18552e-08L19.5 1.5L15 1.5L15 -2.18556e-07L19.5 2.18552e-08Z" fill="white"/>
|
|
423
|
+
<path d="M1.5 0H0.5C0.223858 0 0 0.223858 0 0.5V5H1.5V0Z" fill="white"/>
|
|
424
|
+
<path d="M0.5 2.18552e-08L0.5 1.5L5 1.5L5 -2.18556e-07L0.5 2.18552e-08Z" fill="white"/>
|
|
425
|
+
<path d="M1.5 20H0.5C0.223858 20 0 19.7761 0 19.5V15H1.5V20Z" fill="white"/>
|
|
426
|
+
<path d="M0.5 20L0.5 18.5L5 18.5L5 20L0.5 20Z" fill="white"/>
|
|
427
|
+
<path d="M18.5 20H19.5C19.7761 20 20 19.7761 20 19.5V15H18.5V20Z" fill="white"/>
|
|
428
|
+
<path d="M19.5 20L19.5 18.5L15 18.5L15 20L19.5 20Z" fill="white"/>
|
|
429
|
+
</g>
|
|
430
|
+
<defs>
|
|
431
|
+
<clipPath id="clip0_1421_431">
|
|
432
|
+
<rect width="20" height="20" fill="white"/>
|
|
433
|
+
</clipPath>
|
|
434
|
+
</defs>
|
|
435
|
+
</svg>
|
|
436
|
+
`;
|
|
437
|
+
|
|
438
|
+
scanIcon.innerHTML = SCAN_SVG;
|
|
414
439
|
const scanBtn = document.createElement('button');
|
|
415
440
|
scanBtn.id = 'oobeeBtnScan';
|
|
416
441
|
scanBtn.className = 'oobee-btn oobee-btn-primary';
|
|
417
|
-
scanBtn.innerText = 'Scan this page';
|
|
418
442
|
scanBtn.disabled = inProgress;
|
|
443
|
+
scanBtn.appendChild(scanIcon);
|
|
444
|
+
|
|
445
|
+
const scanText = document.createElement('span');
|
|
446
|
+
scanText.className = 'oobee-btn-text';
|
|
447
|
+
scanText.innerText = 'Scan page';
|
|
448
|
+
scanBtn.appendChild(scanText);
|
|
449
|
+
|
|
419
450
|
scanBtn.addEventListener('click', async () => customWindow.handleOnScanClick?.());
|
|
420
451
|
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
452
|
+
const endScanIcon = document.createElement('span');
|
|
453
|
+
endScanIcon.className = 'oobee-btn-icon';
|
|
454
|
+
|
|
455
|
+
const ENDSCAN_SVG =
|
|
456
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
457
|
+
<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"/>
|
|
458
|
+
</svg>
|
|
459
|
+
`;
|
|
460
|
+
|
|
461
|
+
endScanIcon.innerHTML = ENDSCAN_SVG;
|
|
462
|
+
const endScanBtn = document.createElement('button');
|
|
463
|
+
endScanBtn.id = 'oobeeBtnEndScan';
|
|
464
|
+
endScanBtn.className = 'oobee-btn oobee-btn-secondary';
|
|
465
|
+
endScanBtn.appendChild(endScanIcon);
|
|
466
|
+
|
|
467
|
+
const endScanText = document.createElement('span');
|
|
468
|
+
endScanText.className = 'oobee-btn-text';
|
|
469
|
+
endScanText.innerText = 'End scan';
|
|
470
|
+
endScanBtn.appendChild(endScanText);
|
|
471
|
+
|
|
472
|
+
endScanBtn.addEventListener('click', async () => customWindow.handleOnStopClick?.());
|
|
426
473
|
|
|
427
474
|
const btnGroup = document.createElement('div');
|
|
428
475
|
btnGroup.className = 'oobee-actions';
|
|
429
476
|
btnGroup.appendChild(scanBtn);
|
|
430
|
-
btnGroup.appendChild(
|
|
477
|
+
btnGroup.appendChild(endScanBtn);
|
|
431
478
|
|
|
432
479
|
const listWrap = document.createElement('div');
|
|
433
480
|
listWrap.id = 'oobeeList';
|
|
@@ -503,7 +550,7 @@ export const addOverlayMenu = async (
|
|
|
503
550
|
border-right: 1px solid rgba(0,0,0,.08)
|
|
504
551
|
}
|
|
505
552
|
.oobee-panel.collapsed {
|
|
506
|
-
width:
|
|
553
|
+
width: 58px;
|
|
507
554
|
overflow: hidden
|
|
508
555
|
}
|
|
509
556
|
|
|
@@ -580,6 +627,12 @@ export const addOverlayMenu = async (
|
|
|
580
627
|
padding: 1rem;
|
|
581
628
|
}
|
|
582
629
|
|
|
630
|
+
.oobee-panel.collapsed .oobee-actions {
|
|
631
|
+
display: flex;
|
|
632
|
+
justify-content: center;
|
|
633
|
+
padding: 1rem 0.7rem;
|
|
634
|
+
}
|
|
635
|
+
|
|
583
636
|
/* Base button */
|
|
584
637
|
.oobee-btn {
|
|
585
638
|
width: 100%;
|
|
@@ -590,6 +643,10 @@ export const addOverlayMenu = async (
|
|
|
590
643
|
line-height: 1.2;
|
|
591
644
|
font-weight: 400;
|
|
592
645
|
cursor: pointer;
|
|
646
|
+
display: flex;
|
|
647
|
+
align-items: center;
|
|
648
|
+
justify-content: center;
|
|
649
|
+
gap: 10px;
|
|
593
650
|
transition: {
|
|
594
651
|
box-shadow .12s ease,
|
|
595
652
|
transform .02s ease,
|
|
@@ -603,6 +660,19 @@ export const addOverlayMenu = async (
|
|
|
603
660
|
cursor:not-allowed
|
|
604
661
|
}
|
|
605
662
|
|
|
663
|
+
.oobee-panel.collapsed .oobee-btn {
|
|
664
|
+
width: 44px !important;
|
|
665
|
+
height: 44px !important;
|
|
666
|
+
min-width: 44px !important;
|
|
667
|
+
min-height: 44px !important;
|
|
668
|
+
max-width: 44px !important;
|
|
669
|
+
max-height: 44px !important;
|
|
670
|
+
border-radius: 50% !important;
|
|
671
|
+
padding: 0 !important;
|
|
672
|
+
justify-content: center;
|
|
673
|
+
gap: 0;
|
|
674
|
+
}
|
|
675
|
+
|
|
606
676
|
/* Primary (filled) */
|
|
607
677
|
.oobee-btn-primary {
|
|
608
678
|
background: #9021a6;
|
|
@@ -658,6 +728,25 @@ export const addOverlayMenu = async (
|
|
|
658
728
|
display: none;
|
|
659
729
|
}
|
|
660
730
|
|
|
731
|
+
.oobee-btn-icon {
|
|
732
|
+
display: inline-flex;
|
|
733
|
+
align-items: center;
|
|
734
|
+
justify-content: center;
|
|
735
|
+
width: 20px;
|
|
736
|
+
height: 20px;
|
|
737
|
+
vertical-align: middle;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.oobee-btn-text {
|
|
741
|
+
display: inline;
|
|
742
|
+
white-space: nowrap;
|
|
743
|
+
vertical-align: middle;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.oobee-panel.collapsed .oobee-btn-text {
|
|
747
|
+
display: none;
|
|
748
|
+
}
|
|
749
|
+
|
|
661
750
|
#oobeeStopOverlay[hidden] {
|
|
662
751
|
display:none !important;
|
|
663
752
|
}
|
|
@@ -675,7 +764,10 @@ export const addOverlayMenu = async (
|
|
|
675
764
|
}
|
|
676
765
|
|
|
677
766
|
.oobee-panel.collapsed .oobee-section-title {
|
|
678
|
-
|
|
767
|
+
font-size: 14px;
|
|
768
|
+
display: flex;
|
|
769
|
+
justify-content: center;
|
|
770
|
+
text-align: center;
|
|
679
771
|
}
|
|
680
772
|
|
|
681
773
|
.oobee-ol {
|
|
@@ -1077,7 +1169,13 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1077
1169
|
// eslint-disable-next-line no-underscore-dangle
|
|
1078
1170
|
const pageId = page._guid;
|
|
1079
1171
|
|
|
1080
|
-
page.on('dialog',
|
|
1172
|
+
page.on('dialog', async dialog => {
|
|
1173
|
+
try {
|
|
1174
|
+
await dialog.dismiss();
|
|
1175
|
+
} catch {
|
|
1176
|
+
// dialog may already be closed
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1081
1179
|
|
|
1082
1180
|
const pageClosePromise = new Promise(resolve => {
|
|
1083
1181
|
page.on('close', () => {
|
|
@@ -1109,6 +1207,7 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1109
1207
|
log('Scan: success');
|
|
1110
1208
|
pagesDict[pageId].isScanning = false;
|
|
1111
1209
|
|
|
1210
|
+
if (page.isClosed()) return;
|
|
1112
1211
|
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
1113
1212
|
|
|
1114
1213
|
if (allowed) {
|
|
@@ -1150,10 +1249,10 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1150
1249
|
|
|
1151
1250
|
if (!inputValue?.confirmed) {
|
|
1152
1251
|
await page.evaluate(() => {
|
|
1153
|
-
const
|
|
1154
|
-
if (
|
|
1155
|
-
|
|
1156
|
-
|
|
1252
|
+
const endScanBtn = document.getElementById('oobeeBtnEndScan') as HTMLButtonElement | null;
|
|
1253
|
+
if (endScanBtn) {
|
|
1254
|
+
endScanBtn.disabled = false;
|
|
1255
|
+
endScanBtn.textContent = 'Stop';
|
|
1157
1256
|
}
|
|
1158
1257
|
});
|
|
1159
1258
|
return;
|
|
@@ -1182,6 +1281,7 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1182
1281
|
};
|
|
1183
1282
|
|
|
1184
1283
|
page.on('domcontentloaded', async () => {
|
|
1284
|
+
if (page.isClosed()) return;
|
|
1185
1285
|
try {
|
|
1186
1286
|
const allowed = isOverlayAllowed(page.url(), processPageParams.entryUrl);
|
|
1187
1287
|
|
|
@@ -1218,19 +1318,24 @@ export const initNewPage = async (page, pageClosePromises, processPageParams, pa
|
|
|
1218
1318
|
}
|
|
1219
1319
|
});
|
|
1220
1320
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1321
|
+
try {
|
|
1322
|
+
if (page.isClosed()) return page;
|
|
1323
|
+
await page.exposeFunction('handleOnScanClick', handleOnScanClick);
|
|
1324
|
+
await page.exposeFunction('handleOnStopClick', handleOnStopClick);
|
|
1223
1325
|
|
|
1224
|
-
|
|
1326
|
+
type UpdateMenuPosFunction = (newPos: any) => void;
|
|
1225
1327
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1328
|
+
// Define the updateMenuPos function
|
|
1329
|
+
const updateMenuPos: UpdateMenuPosFunction = newPos => {
|
|
1330
|
+
const prevPos = menuPos;
|
|
1331
|
+
if (prevPos !== newPos) {
|
|
1332
|
+
menuPos = newPos;
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
await page.exposeFunction('updateMenuPos', updateMenuPos);
|
|
1336
|
+
} catch (e) {
|
|
1337
|
+
log(`Error exposing functions on page: ${e}`);
|
|
1338
|
+
}
|
|
1234
1339
|
|
|
1235
1340
|
return page;
|
|
1236
1341
|
};
|
|
@@ -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)) {
|