Skip to content
This repository has been archived by the owner on Sep 21, 2022. It is now read-only.

Commit

Permalink
working 3 checks
Browse files Browse the repository at this point in the history
  • Loading branch information
nmetulev committed Mar 25, 2022
1 parent 2c39566 commit f11e23f
Show file tree
Hide file tree
Showing 11 changed files with 333 additions and 140 deletions.
61 changes: 61 additions & 0 deletions src/checks/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { ManifestDetectionResult, ServiceWorkerDetectionResult, SiteData } from "../interfaces/validation";
import { getTabId } from "../utils/chromium";

let manifestInfoPromise: Promise<ManifestDetectionResult>;
const messageType = 'manifestMessage';

export function getManifestInfo() : Promise<ManifestDetectionResult> {
if (manifestInfoPromise) {
return manifestInfoPromise;
}

manifestInfoPromise = new Promise(async (resolve, reject) => {
const listener = (message: any, sender: any, sendResponse: any) => {
if (message.type && message.type === messageType) {
// we finally have all the info we need for the service worker
resolve(message.manifestInfo);
chrome.runtime.onMessage.removeListener(listener);
sendResponse(true);
}
}

chrome.runtime.onMessage.addListener(listener)

const tabId = await getTabId();
if (tabId) {
await chrome.scripting.executeScript({
target: {tabId},
func: runContentScriptIsolated,
args: [messageType]
});
} else {
reject("No active tab found");
}
});

return manifestInfoPromise;
}

// running code isolated so we can use messaging back to extension
// should be run first to setup window message listener for messages from other content script
async function runContentScriptIsolated(messageType: string) {

const manifestInfo : ManifestDetectionResult = {
hasManifest: false
}

const link = document.querySelector('link[rel="manifest"]') as HTMLLinkElement;
if (link) {
const manifestUri = link.href;

const manifestResponse = await fetch(manifestUri, { credentials: "include" })
const manifest = await manifestResponse.json();

manifestInfo.hasManifest = true;
manifestInfo.manifestUri = manifestUri;
manifestInfo.manifest = manifest;
}

chrome.runtime.sendMessage({type: messageType, manifestInfo});
return true;
}
76 changes: 76 additions & 0 deletions src/checks/security.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { api } from "../endpoints";
import { TestResult } from "../interfaces/manifest";
import { SecurityDataResults } from "../interfaces/validation";

const default_results = [
{
result: false,
infoString: "Uses HTTPS",
category: "required",
},
{
result: false,
infoString: "Has a valid SSL certificate",
category: "required",
},
{
result: false,
infoString: "No mixed content on page",
category: "required",
},
];

export async function testSecurity(url: string): Promise<Array<TestResult>> {
// We've witnessed this call take a very long time. We're going to time-box it to 20s.
const twentySecondTimeout = new Promise<void>((resolve) =>
setTimeout(() => resolve(), 20000)
);

const encodedUrl = encodeURIComponent(url);
const securityUrl = `${api}/Security?site=${encodedUrl}`;
const fetchResult = fetch(securityUrl);

const fetchResultOrTimeout: void | Response = await Promise.race([
twentySecondTimeout,
fetchResult,
]);

if (!fetchResultOrTimeout) {
console.warn("Security check timed out after 20 seconds.");
return default_results;
}

if (!fetchResultOrTimeout.ok) {
console.warn(
"Security check failed with non-successful status code",
fetchResultOrTimeout.status,
fetchResultOrTimeout.statusText
);
return default_results;
}

// we have made it through, yay!
const results: { data: SecurityDataResults } =
await fetchResultOrTimeout.json();
console.info("Security detection completed successfully", results);

const organizedResults = [
{
result: results.data.isHTTPS,
infoString: "Uses HTTPS",
category: "required",
},
{
result: results.data.valid,
infoString: "Has a valid SSL certificate",
category: "required",
},
{
result: results.data.validProtocol,
infoString: "No mixed content on page",
category: "required",
},
];

return organizedResults;
}
143 changes: 143 additions & 0 deletions src/checks/sw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { ManifestDetectionResult, ServiceWorkerDetectionResult, SiteData } from "../interfaces/validation";
import { getTabId } from "../utils/chromium";

let getServiceWorkerInfoPromise: Promise<SiteData>;
const messageType = 'serviceWorkerMessage';

export function getSwInfo() : Promise<SiteData> {
if (getServiceWorkerInfoPromise) {
return getServiceWorkerInfoPromise;
}

getServiceWorkerInfoPromise = new Promise(async (resolve, reject) => {
const listener = (message: any, sender: any, sendResponse: any) => {
if (message.type && message.type === messageType) {
// we finally have all the info we need for the service worker
resolve(message.serviceWorkerRegistration);
chrome.runtime.onMessage.removeListener(listener);
sendResponse(true);
}
}

chrome.runtime.onMessage.addListener(listener)

const tabId = await getTabId();
if (tabId) {
await chrome.scripting.executeScript({
target: {tabId},
func: runContentScriptIsolated,
args: [messageType]
});

await chrome.scripting.executeScript({
target: {tabId},
func: runContentScriptOnPage,
world: "MAIN",
args: [messageType]
});
} else {
reject("No active tab found");
}
});

return getServiceWorkerInfoPromise;
}


// running code on page allowing us to get the service worker registration
async function runContentScriptOnPage(messageType: string) {

const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(resolve, 10000);
})


const result = await Promise.any([timeoutPromise, navigator.serviceWorker.ready]);

const swInfo : ServiceWorkerDetectionResult = {
hasSW: false
}

if (result) {
const registration = result as ServiceWorkerRegistration;
swInfo.hasSW = true;
swInfo.url = registration.active?.scriptURL;
swInfo.scope = registration.scope;
swInfo.isHtmlInCache = false;

async function fetchSwCode(uri: string) {
const response = await fetch(uri, { credentials: "include" });
const text = await response.text();
return text;
}

async function isHtmlInCache() {
const cacheNames = await caches.keys();
for (let cacheName of cacheNames) {
const cache = await caches.open(cacheName);
const cacheKeys = await cache.keys();
for (let key of cacheKeys) {
const cachedObject = await cache.match(key);
if (cachedObject && cachedObject.headers) {
const contentType = cachedObject.headers.get('Content-Type');
if (contentType && contentType.startsWith('text/html')) {
return true;
}
}
}
}
return false;
}

function hasSwEventSubscription(eventName: string, eventMethodName: string, testString: string) {
const addEventListenerRegEx = `(\\n\\s*|.)addEventListener\\(['|\\"]${eventName}['|\\"]`;
const methodnameRegEx = `(\\n\\s*|.)${eventName}\\s*=`;

return (new RegExp(`(${addEventListenerRegEx}|${methodnameRegEx})`)).test(testString);
}

const results = await Promise.all([fetchSwCode(registration.active?.scriptURL!), isHtmlInCache()]);
swInfo.rawSW = results[0];
swInfo.isHtmlInCache = results[1];

const pushSubscription = await registration.pushManager.getSubscription();
swInfo.hasPushRegistration = pushSubscription !== null || hasSwEventSubscription("push", "onpush", swInfo.rawSW);

try {
const periodicSyncTags : [] = await (registration as any).periodicSync.getTags();
swInfo.hasPeriodicBackgroundSync = periodicSyncTags.length > 0 || hasSwEventSubscription("periodicsync", "onperiodicsync", swInfo.rawSW);
} catch {
swInfo.hasPeriodicBackgroundSync = hasSwEventSubscription("periodicsync", "onperiodicsync", swInfo.rawSW);
}

try {
const syncTags : [] = await (registration as any).sync.getTags();
swInfo.hasBackgroundSync = syncTags.length > 0 || hasSwEventSubscription("sync", "onsync", swInfo.rawSW);
} catch {
swInfo.hasBackgroundSync = hasSwEventSubscription("sync", "onsync", swInfo.rawSW);
}

}

window.postMessage({type: messageType, serviceWorkerRegistration: swInfo}, "*");
}

// running code isolated so we can use messaging back to extension
// should be run first to setup window message listener for messages from other content script
function runContentScriptIsolated(messageType: string) {
const callback = (event: MessageEvent) => {
console.log('got message there', event.data);
if (event.source !== window) {
return;
}

if (event.data.type && event.data.type === messageType) {
window.removeEventListener("message", callback);
console.log('here', event.data.serviceWorkerRegistration)
chrome.runtime.sendMessage<ServiceWorkerDetectionResult>(event.data);
}
};

window.addEventListener("message", callback)
return true;
}
8 changes: 4 additions & 4 deletions src/components/manifest-designer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { LitElement, html } from "lit";
import {customElement, state} from 'lit/decorators.js';
import { getSiteInfo } from "../extensionHelpers";
import { Manifest } from "../interfaces/manifest";

import {
Expand All @@ -13,6 +12,7 @@ import {
} from "@fluentui/web-components";

import "@shoelace-style/shoelace/dist/components/color-picker/color-picker";
import { getManifestInfo } from "../checks/manifest";

provideFluentDesignSystem().register(
fluentButton(),
Expand Down Expand Up @@ -122,9 +122,9 @@ export class ManifestDesigner extends LitElement {
private manifest!: Manifest;

async firstUpdated() {
const siteInfo = await getSiteInfo();
if (siteInfo && siteInfo.manifest && siteInfo.manifest.manifest) {
this.manifest = siteInfo.manifest.manifest;
const manifestInfo = await getManifestInfo();
if (manifestInfo && manifestInfo.manifest) {
this.manifest = manifestInfo.manifest;
}

console.log('manifest', this.manifest);
Expand Down
10 changes: 5 additions & 5 deletions src/components/package-windows.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import { windowsEndpoint } from "../endpoints";
import { getSiteInfo } from "../extensionHelpers";
import { WindowsOptions } from "../interfaces/windowsOptions";

import {
Expand All @@ -11,6 +10,7 @@ import {
fluentTooltip,
fluentAnchor,
} from "@fluentui/web-components";
import { getManifestInfo } from "../checks/manifest";

provideFluentDesignSystem().register(
fluentTextField(),
Expand Down Expand Up @@ -60,11 +60,11 @@ export class PackageWindows extends LitElement {
];

public async firstUpdated() {
const siteInfo = await getSiteInfo();
const manifestInfo = await getManifestInfo();

if (siteInfo && siteInfo.manifest) {
this.currentManiUrl = siteInfo.manifest.manifestUri;
const manifest = siteInfo.manifest.manifest;
if (manifestInfo) {
this.currentManiUrl = manifestInfo.manifestUri;
const manifest = manifestInfo.manifest;

// get current url
let url = await chrome.tabs.query({ active: true, currentWindow: true });
Expand Down
35 changes: 24 additions & 11 deletions src/components/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import "@lottiefiles/lottie-player";
import "@shoelace-style/shoelace/dist/components/progress-ring/progress-ring";
import "@shoelace-style/shoelace/dist/components/badge/badge";

import { getSiteInfo } from "../extensionHelpers";
import { runManifestChecks } from "../utils/manifest";
import { TestResult } from "../interfaces/manifest";
import { testSecurity } from "../checks/security";
import { getSwInfo } from "../checks/sw";
import { getManifestInfo } from "../checks/manifest";

provideFluentDesignSystem().register(
fluentButton(),
Expand Down Expand Up @@ -73,18 +75,29 @@ export class PWAScanner extends LitElement {
this.currentUrl = url[0].url || "";
}

const siteInfo = await getSiteInfo();
console.log("fetching site info");
// const siteInfo = await getSiteInfo();
// console.log(siteInfo);

this.testResults = await runManifestChecks({
manifestUrl: siteInfo.manifest.manifestUri,
initialManifest: siteInfo.manifest.manifest,
siteUrl: this.currentUrl,
isGenerated: false,
isEdited: false,
manifest: siteInfo.manifest.manifest,
});
// this.testResults = await runManifestChecks({
// manifestUrl: siteInfo.manifest.manifestUri!,
// initialManifest: siteInfo.manifest.manifest!,
// siteUrl: this.currentUrl,
// isGenerated: false,
// isEdited: false,
// manifest: siteInfo.manifest.manifest!,
// });

// if (siteInfo.sw.hasSW) {
// console.log('service worker tests', analyzeSW(siteInfo.sw.rawSW!))
// }

// console.log('manifest tests', this.testResults);

console.log("manifest", await getManifestInfo());
console.log("sw", await getSwInfo());
console.log('security', await testSecurity(this.currentUrl));

console.log(this.testResults);
}

render() {
Expand Down
Loading

0 comments on commit f11e23f

Please sign in to comment.