Skip to content

Commit

Permalink
refactor: create generic interface for SCMs
Browse files Browse the repository at this point in the history
This prepares the project for adding other SCMs. The changes to Babel were due to an issue with
shimming Promises and Firefox
(mozilla/webextension-polyfill#105 (comment))
  • Loading branch information
NoxHarmonium committed Oct 1, 2019
1 parent 7e622ea commit d2e8cd0
Show file tree
Hide file tree
Showing 17 changed files with 227 additions and 119 deletions.
23 changes: 21 additions & 2 deletions .babelrc
@@ -1,6 +1,25 @@
{
"presets": [
["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 }],
[
"@babel/preset-env",
{
"corejs": 3,
"debug": false,
"exclude": [
"es.promise",
"es.promise.finally"
],
"loose": true,
"spec": true,
"targets": {
"browsers": [
"last 2 Chrome versions",
"last 2 Firefox versions"
]
},
"useBuiltIns": "usage"
}
],
"@babel/preset-react"
],
"plugins": [
Expand Down Expand Up @@ -31,4 +50,4 @@
"@babel/plugin-proposal-do-expressions",
"@babel/plugin-proposal-function-bind"
]
}
}
3 changes: 1 addition & 2 deletions package.json
Expand Up @@ -125,13 +125,12 @@
"engines": {
"node": ">= 10.0.0"
},
"browserslist": "defaults",
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"typeCoverage": {
"atLeast": 99.79
"atLeast": 100
}
}
3 changes: 1 addition & 2 deletions src/app/background.ts
@@ -1,5 +1,4 @@
import { browser, Runtime } from "webextension-polyfill-ts";
import { messages } from "~app/constants";
import { resetTab, toggleDiffOnTab } from "~app/messenging";
import { Message } from "~app/types/messenging";

Expand Down Expand Up @@ -65,7 +64,7 @@ browser.runtime.onMessage.addListener(
console.log(
`Received message from [${senderTabId}]: [${JSON.stringify(message)}]`
);
if (message.type === messages.Supported && message.value === true) {
if (message.type === "Supported" && message.value === true) {
console.log("Adding tab to enabled set!");
tabEnabledSet.add(senderTabId);
console.log(tabEnabledSet);
Expand Down
6 changes: 0 additions & 6 deletions src/app/constants.ts

This file was deleted.

36 changes: 15 additions & 21 deletions src/app/main.ts
@@ -1,13 +1,8 @@
import { browser, Runtime } from "webextension-polyfill-ts";
import { messages } from "~app/constants";
import { setTabSupportsMulePreview } from "~app/messenging";
import { isDiffMode, stopDiff, toggleDiff } from "~app/modes/diff";
import { isPreviewMode, togglePreview } from "~app/modes/preview";
import {
getBitbucketDiffElement,
getBitbucketFilePreviewElement,
isRunningInBitbucket
} from "~app/scms/bitbucket/ui";
import { stopDiff, toggleDiff } from "~app/modes/diff";
import { togglePreview } from "~app/modes/preview";
import { bitbucketServerScmModule } from "~app/scms/bitbucket-server";
import { Message } from "~app/types/messenging";
import "../scss/extension.scss";

Expand All @@ -28,14 +23,15 @@ browser.runtime.onMessage.addListener(
message
)}]`
);
if (message.type === messages.ToggleDiff) {
if (getBitbucketDiffElement() !== null) {
toggleDiff();
const mode = await bitbucketServerScmModule.determineScmMode();
if (message.type === "ToggleDiff") {
if (mode === "Diff") {
toggleDiff(bitbucketServerScmModule);
}
if (getBitbucketFilePreviewElement() !== null) {
togglePreview();
if (mode === "Preview") {
togglePreview(bitbucketServerScmModule);
}
} else if (message.type === messages.Reset) {
} else if (message.type === "Reset") {
reset();
}
return true; // Enable async
Expand All @@ -49,10 +45,7 @@ const onReady = () => {

const startReadyPolling = () => {
const readyPoller = setInterval(() => {
if (
getBitbucketDiffElement() !== null ||
getBitbucketFilePreviewElement() !== null
) {
if (bitbucketServerScmModule.isReady()) {
console.log("[Mule Preview] Ready!");
clearInterval(readyPoller);
onReady();
Expand All @@ -64,12 +57,13 @@ const startReadyPolling = () => {
}, bitbucketPollPeriod);
};

const reset = () => {
const reset = async () => {
stopDiff();
// Reset button
setTabSupportsMulePreview(false);
await setTabSupportsMulePreview(false);

if (isRunningInBitbucket() && (isDiffMode() || isPreviewMode())) {
const supported = await bitbucketServerScmModule.isSupported();
if (supported) {
console.log(
"[Mule Preview] I'm pretty sure this is the right place but I have to wait for the element to be ready."
);
Expand Down
12 changes: 5 additions & 7 deletions src/app/messenging.ts
@@ -1,25 +1,23 @@
import { browser } from "webextension-polyfill-ts";
import { messages } from "~app/constants";
import { Message } from "~app/types/messenging";

const sendMessageRobust = async (currentTabId: number, message: Message) =>
browser.tabs.sendMessage(currentTabId, message).catch((error: unknown) => {
console.warn(`Could not send message: [${error}]. Ignoring...`);
});

export const setTabSupportsMulePreview = async (supported: boolean) => {
await browser.runtime.sendMessage({
type: messages.Supported,
export const setTabSupportsMulePreview = async (supported: boolean) =>
browser.runtime.sendMessage({
type: "Supported",
value: supported
});
};

export const resetTab = async (tabId: number) =>
sendMessageRobust(tabId, {
type: messages.Reset
type: "Reset"
});

export const toggleDiffOnTab = async (tabId: number) =>
sendMessageRobust(tabId, {
type: messages.ToggleDiff
type: "ToggleDiff"
});
26 changes: 5 additions & 21 deletions src/app/modes/diff.tsx
Expand Up @@ -2,19 +2,9 @@ import { MulePreviewDiffContent } from "@agiledigital/mule-preview";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { browser } from "webextension-polyfill-ts";
import { getFileContentFromDiff } from "~app/scms/bitbucket/fetch";
import { DiffContent } from "~app/scms/bitbucket/types";
import { getBitbucketData } from "~app/scms/bitbucket/ui";
import { ScraperResponse } from "~app/types/scraper";
import { DiffContent, ScmModule } from "~app/types/scms";
import { createContainerElement, getMulePreviewElement } from "~app/ui";

const handleBitbucketData = (bitbucketData: ScraperResponse) => {
if (!bitbucketData.valid) {
throw new Error("Could not fetch Bitbucket data");
}
return getFileContentFromDiff(bitbucketData);
};

const handleFileContent = ({ fileA, fileB }: DiffContent) => {
const element = document.querySelector("body");

Expand All @@ -33,7 +23,7 @@ const handleFileContent = ({ fileA, fileB }: DiffContent) => {
);
};

const startDiff = () => {
const startDiff = async (scmModule: ScmModule) => {
if (getMulePreviewElement() !== null) {
console.log("[Mule Preview] Already loaded. Will not load again.");
return;
Expand All @@ -42,13 +32,7 @@ const startDiff = () => {
console.log(
"[Mule Preview] Bitbucket detected. Will attempt to load overlay."
);
getBitbucketData()
.then(handleBitbucketData)
.then(content => {
if (content !== undefined) {
handleFileContent(content);
}
});
return scmModule.getDiffContent().then(handleFileContent);
};

export const stopDiff = () => {
Expand All @@ -58,11 +42,11 @@ export const stopDiff = () => {
}
};

export const toggleDiff = () => {
export const toggleDiff = (scmModule: ScmModule) => {
const element = getMulePreviewElement();
if (element === null) {
console.log("[Mule Preview] No existing element. Starting diff");
startDiff();
startDiff(scmModule);
} else {
console.log("[Mule Preview] Existing element found. Stopping diff");
stopDiff();
Expand Down
37 changes: 15 additions & 22 deletions src/app/modes/preview.tsx
@@ -1,12 +1,11 @@
import { MulePreviewContent } from "@agiledigital/mule-preview";
import fetch from "cross-fetch";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { browser } from "webextension-polyfill-ts";
import { getFileRawUrlFromContentView } from "~app/scms/bitbucket/ui";
import { ScmModule } from "~app/types/scms";
import { createContainerElement, getMulePreviewElement } from "~app/ui";

const startPreview = () => {
const startPreview = async (scmModule: ScmModule) => {
if (getMulePreviewElement() !== null) {
console.log("[Mule Preview] Already loaded. Will not load again.");
return;
Expand All @@ -21,23 +20,17 @@ const startPreview = () => {
throw new Error("Could not find body element");
}

const url = getFileRawUrlFromContentView();
return fetch(url)
.then(response => response.text())
.then(content => {
const mulePreviewElement = createContainerElement();
element.appendChild(mulePreviewElement);
ReactDOM.render(
<MulePreviewContent
contentString={content}
contentRoot={browser.runtime.getURL("public/")}
/>,
mulePreviewElement
);
})
.catch(err => {
console.error(err);
});
return scmModule.getPreviewContent().then(content => {
const mulePreviewElement = createContainerElement();
element.appendChild(mulePreviewElement);
ReactDOM.render(
<MulePreviewContent
contentString={content}
contentRoot={browser.runtime.getURL("public/")}
/>,
mulePreviewElement
);
});
};

export const stopPreview = () => {
Expand All @@ -47,11 +40,11 @@ export const stopPreview = () => {
}
};

export const togglePreview = () => {
export const togglePreview = (scmModule: ScmModule) => {
const element = getMulePreviewElement();
if (element === null) {
console.log("[Mule Preview] No existing element. Starting preview");
startPreview();
startPreview(scmModule);
} else {
console.log("[Mule Preview] Existing element found. Stopping preview");
stopPreview();
Expand Down
@@ -1,6 +1,11 @@
import fetch from "cross-fetch";
import { Change, ChangesResponse, DiffContent } from "~app/scms/bitbucket/types";
import { ValidScraperResponse } from "~app/types/scraper";
import {
Change,
ChangesResponse,
ScraperResponse,
ValidScraperResponse
} from "~app/scms/bitbucket-server/types";
import { DiffContent } from "~app/types/scms";

/**
* Functions to fetch files from Bitbucket to preview and diff
Expand Down Expand Up @@ -88,3 +93,16 @@ export const getFileContentFromDiff = async ({
}
});
};

export const handleBitbucketData = async (
bitbucketData: ScraperResponse
): Promise<DiffContent> => {
if (!bitbucketData.valid) {
throw new Error("Could not fetch Bitbucket data");
}
const diffContent = await getFileContentFromDiff(bitbucketData);
if (diffContent === undefined) {
throw new Error("Could not fetch Bitbucket diff");
}
return diffContent;
};
50 changes: 50 additions & 0 deletions src/app/scms/bitbucket-server/index.ts
@@ -0,0 +1,50 @@
import {
DiffContent,
PreviewContent,
ScmMode,
ScmModule
} from "~app/types/scms";
import { handleBitbucketData } from "./fetch";
import {
getBitbucketData,
getBitbucketDiffElement,
getBitbucketFilePreviewElement,
getFileRawUrlFromContentView
} from "./ui";

/**
* Supports the self hosted version of Atlassian Bitbucket (Bitbucket Server)
*
* Does not support Bitbucket Cloud, this works very differently under the hood
* and will need to be a separate module.
*
* Tested on Atlassian Bitbucket v6.1.1
* It will probably work on any version of v6 and maybe even earlier versions
* but has not be tested on those versions.
*/
export const bitbucketServerScmModule: ScmModule = {
isSupported: async (): Promise<boolean> => {
const metaTag = document.querySelector("meta[name=application-name]");
return metaTag === null
? false
: metaTag.getAttribute("content") === "Bitbucket";
},
isReady: (): boolean =>
getBitbucketDiffElement() !== null ||
getBitbucketFilePreviewElement() !== null,
determineScmMode: async (): Promise<ScmMode> => {
if (new URL(document.URL).pathname.endsWith("diff")) {
return "Diff";
}
if (new URL(document.URL).pathname.endsWith(".xml")) {
return "Preview";
}
return "None";
},
getDiffContent: async (): Promise<DiffContent> =>
getBitbucketData().then(handleBitbucketData),
getPreviewContent: async (): Promise<PreviewContent> => {
const url = getFileRawUrlFromContentView();
return fetch(url).then(response => response.text());
}
};
11 changes: 6 additions & 5 deletions src/app/scraper.ts → src/app/scms/bitbucket-server/scraper.ts
@@ -1,10 +1,11 @@
import { messages } from "~app/constants";
import {
CommonPageState,
DiffDetails,
DiffPageState,
PullRequestPageState
} from "~app/scms/bitbucket/types";
import { DiffDetails, ScraperResponse } from "~app/types/scraper";
PullRequestPageState,
ScraperResponse
} from "~app/scms/bitbucket-server/types";
import { MessageType } from "~app/types/messenging";

// Note: Code in this file has to be injected into the target
// browser so it should be free of dependencies and small as possible
Expand Down Expand Up @@ -96,7 +97,7 @@ import { DiffDetails, ScraperResponse } from "~app/types/scraper";
const payload = preparePayload(bitbucketPageState);

document.dispatchEvent(
new CustomEvent(messages.BitbucketDataScraped, {
new CustomEvent("BitbucketDataScraped" as MessageType, {
detail: payload
})
);
Expand Down

0 comments on commit d2e8cd0

Please sign in to comment.