492 lines
13 KiB
JavaScript
492 lines
13 KiB
JavaScript
const modifierContainer = document.getElementById("modifierContainer");
|
|
const modalContainer = document.getElementById("modalContainer");
|
|
const modalBody = document.getElementById("modal-body");
|
|
const modalContent = document.getElementById("modal-content");
|
|
const modalSubmitButton = document.getElementById("modal-submit");
|
|
const modalClose = document.getElementById("modal-close");
|
|
|
|
let hasFetched = false;
|
|
let payload = {
|
|
requestmodifications: [],
|
|
responsemodifications: [],
|
|
};
|
|
let ninjaData = [];
|
|
|
|
initialize();
|
|
|
|
// Rerun handleThemeChange() so style is applied to Ninja Keys
|
|
handleThemeChange();
|
|
|
|
// Add event listener to the iframe so it closes dropdown when clicked
|
|
closeDropdownOnClickWithinIframe();
|
|
|
|
async function initialize() {
|
|
if (!hasFetched) {
|
|
try {
|
|
await fetchPayload();
|
|
hasFetched = true;
|
|
} catch (error) {
|
|
console.error("Fetch error:", error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function closeDropdownOnClickWithinIframe() {
|
|
const iframe = document.getElementById("resultIframe");
|
|
iframe.contentWindow.document.addEventListener(
|
|
"click",
|
|
() => {
|
|
if (
|
|
!document.getElementById("dropdown_panel").classList.contains("hidden")
|
|
) {
|
|
toggleDropdown();
|
|
}
|
|
},
|
|
true
|
|
);
|
|
}
|
|
|
|
async function fetchPayload() {
|
|
try {
|
|
const response = await fetch("/api/modifiers");
|
|
const data = await response.json();
|
|
|
|
Object.entries(data.result.requestmodifiers ?? []).forEach(([_, value]) => {
|
|
addModifierToNinjaData(
|
|
value.name,
|
|
value.description,
|
|
value.params,
|
|
"requestmodifications"
|
|
);
|
|
});
|
|
|
|
Object.entries(data.result.responsemodifiers ?? []).forEach(
|
|
([_, value]) => {
|
|
addModifierToNinjaData(
|
|
value.name,
|
|
value.description,
|
|
value.params,
|
|
"responsemodifications"
|
|
);
|
|
}
|
|
);
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error("Fetch error:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function submitForm() {
|
|
if (!document.getElementById("inputForm").checkValidity()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch("/playground/" + inputField.value, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Request failed");
|
|
}
|
|
|
|
const result = await response.text();
|
|
updateResultIframe(result);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
function updateResultIframe(result) {
|
|
const resultIframe = parent.document.getElementById("resultIframe");
|
|
resultIframe.contentDocument.open();
|
|
resultIframe.contentDocument.write(result);
|
|
closeDropdownOnClickWithinIframe();
|
|
resultIframe.contentDocument.close();
|
|
}
|
|
|
|
document.getElementById("inputForm").addEventListener("submit", function (e) {
|
|
e.preventDefault();
|
|
submitForm();
|
|
});
|
|
|
|
if (navigator.userAgent.includes("Mac")) {
|
|
document.getElementById("ninjaKey").textContent = "⌘";
|
|
} else {
|
|
document.getElementById("ninjaKey").textContent = "Ctrl";
|
|
}
|
|
|
|
function downloadYaml() {
|
|
function jsonToYaml(payload) {
|
|
const jsonObject = {
|
|
rules: [
|
|
{
|
|
domains: [hostname],
|
|
responsemodifications: [],
|
|
requestmodifications: [],
|
|
...payload,
|
|
},
|
|
],
|
|
};
|
|
return jsyaml.dump(jsonObject);
|
|
}
|
|
|
|
if (!document.getElementById("inputForm").checkValidity()) {
|
|
alert("Please enter a valid URL.");
|
|
return;
|
|
}
|
|
const hostname = new URL(inputField.value).hostname;
|
|
const ruleHostname = hostname.replace(/^www\./, "").replace(/\./g, "-");
|
|
const yamlString = jsonToYaml(payload);
|
|
const blob = new Blob([yamlString], { type: "text/yaml;charset=utf-8" });
|
|
const href = URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = href;
|
|
link.download = `${ruleHostname}.yaml`;
|
|
link.click();
|
|
URL.revokeObjectURL(href);
|
|
}
|
|
|
|
function getValues(type, id, description, params) {
|
|
const focusTrap = trap(modalBody);
|
|
let values = [];
|
|
let existingValues = [];
|
|
const inputs = [];
|
|
const inputEventListeners = [];
|
|
|
|
function closeModal() {
|
|
focusTrap.destroy();
|
|
modalBody.removeEventListener("keydown", handleKeyboardEvents);
|
|
modalContainer.removeEventListener("click", handleClickOutside);
|
|
modalSubmitButton.removeEventListener("click", closeModal);
|
|
modalClose.removeEventListener("click", closeModal);
|
|
inputEventListeners.forEach((listener, index) => {
|
|
if (listener !== undefined && inputs[index] !== undefined)
|
|
inputs[index].removeEventListener("input", listener);
|
|
});
|
|
modalContent.classList.remove("relative", "h-[220px]");
|
|
inputEventListeners.length = 0;
|
|
inputs.length = 0;
|
|
modalContainer.classList.add("hidden");
|
|
modalContent.innerHTML = "";
|
|
}
|
|
|
|
function handleClickOutside(e) {
|
|
if (modalBody !== null && !modalBody.contains(e.target)) {
|
|
closeModal();
|
|
}
|
|
}
|
|
|
|
function handleKeyboardEvents(e) {
|
|
if (e.key === "Escape") {
|
|
closeModal();
|
|
}
|
|
if (e.key === "Enter") {
|
|
if (e.target.tagName.toLowerCase() === "textarea") {
|
|
return;
|
|
} else {
|
|
modalSubmitButton.click();
|
|
}
|
|
}
|
|
}
|
|
|
|
document.getElementById("modal-title").textContent = id;
|
|
document.getElementById("modal-description").textContent = description;
|
|
|
|
existingValues =
|
|
payload[type].find(
|
|
(modifier) => modifier.name === id && modifier.params !== undefined
|
|
)?.params ?? [];
|
|
|
|
params.map((param, i) => {
|
|
function textareaEventListener(e) {
|
|
const codeElement = document.querySelector("code");
|
|
let text = e.target.value;
|
|
|
|
if (text[text.length - 1] == "\n") {
|
|
text += " ";
|
|
}
|
|
|
|
codeElement.innerHTML = text
|
|
.replace(new RegExp("&", "g"), "&")
|
|
.replace(new RegExp("<", "g"), "<");
|
|
|
|
Prism.highlightElement(codeElement);
|
|
values[i] = text;
|
|
syncScroll(e.target);
|
|
}
|
|
|
|
function textareaKeyEventListener(e) {
|
|
if (e.key === "Tab" && !e.shiftKey) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
let text = e.target.value;
|
|
const start = e.target.selectionStart;
|
|
const end = e.target.selectionEnd;
|
|
e.target.value = text.substring(0, start) + "\t" + text.substring(end);
|
|
e.target.setSelectionRange(start + 1, start + 1);
|
|
e.target.dispatchEvent(new Event("input"));
|
|
}
|
|
syncScroll(e.target);
|
|
}
|
|
|
|
function syncScroll(el) {
|
|
const codeElement = document.querySelector("code");
|
|
codeElement.scrollTop = el.scrollTop;
|
|
codeElement.scrollLeft = el.scrollLeft;
|
|
}
|
|
|
|
function inputEventListener(e) {
|
|
if (e.key !== "Enter") {
|
|
values[i] = e.target.value;
|
|
}
|
|
}
|
|
|
|
const label = document.createElement("label");
|
|
label.textContent = param.name;
|
|
label.setAttribute("for", `input-${i}`);
|
|
let input;
|
|
if (param.name === "js") {
|
|
input = document.createElement("textarea");
|
|
input.type = "textarea";
|
|
input.setAttribute("spellcheck", "false");
|
|
input.placeholder = "Enter your JavaScript injection code ...";
|
|
input.classList.add(
|
|
"h-[200px]",
|
|
"w-full",
|
|
"font-mono",
|
|
"whitespace-nowrap",
|
|
"font-semibold",
|
|
"absolute",
|
|
"text-base",
|
|
"leading-6",
|
|
"rounded-md",
|
|
"ring-1",
|
|
"ring-slate-900/10",
|
|
"shadow-sm",
|
|
"z-10",
|
|
"p-4",
|
|
"m-0",
|
|
"my-2",
|
|
"bg-transparent",
|
|
"dark:bg-transparent",
|
|
"text-transparent",
|
|
"overflow-auto",
|
|
"resize-none",
|
|
"caret-white",
|
|
"hover:ring-slate-300",
|
|
"hyphens-none"
|
|
);
|
|
input.style.tabSize = "4";
|
|
} else {
|
|
input = document.createElement("input");
|
|
input.type = "text";
|
|
input.classList.add(
|
|
"w-full",
|
|
"text-sm",
|
|
"leading-6",
|
|
"text-slate-400",
|
|
"rounded-md",
|
|
"ring-1",
|
|
"ring-slate-900/10",
|
|
"shadow-sm",
|
|
"py-1.5",
|
|
"pl-2",
|
|
"pr-3",
|
|
"mt-0",
|
|
"hover:ring-slate-300",
|
|
"dark:bg-slate-800",
|
|
"dark:highlight-white/5",
|
|
"overflow-auto"
|
|
);
|
|
}
|
|
input.id = `input-${i}`;
|
|
input.value = existingValues[i] ?? "";
|
|
modalContent.appendChild(label);
|
|
modalContent.appendChild(input);
|
|
if (input.type === "textarea") {
|
|
label.classList.add("sr-only", "hidden");
|
|
preElement = document.createElement("pre");
|
|
codeElement = document.createElement("code");
|
|
preElement.setAttribute("aria-hidden", "true");
|
|
preElement.classList.add(
|
|
"bg-[#2d2d2d]",
|
|
"dark:bg-[#2d2d2d]",
|
|
"h-[200px]",
|
|
"w-full",
|
|
"rounded-md",
|
|
"ring-1",
|
|
"ring-slate-900/10",
|
|
"shadow-sm",
|
|
"p-0",
|
|
"m-0",
|
|
"my-2",
|
|
"font-mono",
|
|
"text-base",
|
|
"leading-6",
|
|
"overflow-auto",
|
|
"whitespace-nowrap",
|
|
"font-semibold",
|
|
"absolute",
|
|
"z-0",
|
|
"hyphens-none"
|
|
);
|
|
modalContent.classList.add("relative", "h-[220px]");
|
|
preElement.setAttribute("tabindex", "-1");
|
|
codeElement.classList.add(
|
|
"language-javascript",
|
|
"absolute",
|
|
"w-full",
|
|
"font-mono",
|
|
"text-base",
|
|
"leading-6",
|
|
"z-0",
|
|
"p-4",
|
|
"-mx-4",
|
|
"-my-4",
|
|
"h-full",
|
|
"whitespace-nowrap",
|
|
"overflow-auto",
|
|
"hyphens-none"
|
|
);
|
|
preElement.appendChild(codeElement);
|
|
modalContent.appendChild(preElement);
|
|
codeElement.innerHTML = input.value
|
|
.replace(new RegExp("&", "g"), "&")
|
|
.replace(new RegExp("<", "g"), "<");
|
|
Prism.highlightElement(codeElement);
|
|
input.addEventListener("input", textareaEventListener);
|
|
input.addEventListener("keydown", textareaKeyEventListener);
|
|
input.addEventListener("scroll", () => syncScroll(input));
|
|
inputEventListeners.push(
|
|
textareaEventListener,
|
|
textareaKeyEventListener,
|
|
syncScroll
|
|
);
|
|
} else {
|
|
input.addEventListener("input", inputEventListener);
|
|
inputEventListeners.push(inputEventListener);
|
|
}
|
|
inputs.push(input);
|
|
});
|
|
|
|
modalContainer.classList.remove("hidden");
|
|
document.getElementById("input-0").focus();
|
|
|
|
return new Promise((resolve) => {
|
|
modalBody.addEventListener("keydown", handleKeyboardEvents);
|
|
modalContainer.addEventListener("click", handleClickOutside);
|
|
modalClose.addEventListener("click", () => {
|
|
closeModal();
|
|
});
|
|
modalSubmitButton.addEventListener("click", (e) => {
|
|
inputs.forEach((input, i) => {
|
|
values[i] = input.value;
|
|
});
|
|
resolve(values);
|
|
closeModal();
|
|
});
|
|
});
|
|
}
|
|
|
|
function toggleModifier(type, id, params = []) {
|
|
function pillClickHandler(pill) {
|
|
toggleModifier(pill.getAttribute("type"), pill.id);
|
|
pill.removeEventListener("click", () => pillClickHandler(pill));
|
|
pill.remove();
|
|
}
|
|
|
|
function createPill(type, id) {
|
|
const pill = document.createElement("span");
|
|
pill.classList.add(
|
|
"inline-flex",
|
|
"items-center",
|
|
"rounded-md",
|
|
"bg-slate-100",
|
|
"dark:bg-slate-800",
|
|
"px-2",
|
|
"py-1",
|
|
"h-4",
|
|
"text-xs",
|
|
"font-medium",
|
|
"border",
|
|
"border-slate-400",
|
|
"dark:border-slate-700",
|
|
"cursor-pointer"
|
|
);
|
|
pill.id = id;
|
|
pill.setAttribute("type", type);
|
|
pill.textContent = id;
|
|
modifierContainer.appendChild(pill);
|
|
pill.addEventListener("click", () => pillClickHandler(pill));
|
|
}
|
|
|
|
if (
|
|
params === undefined &&
|
|
payload[type].some((modifier) => modifier.name === id)
|
|
) {
|
|
payload[type] = payload[type].filter((modifier) => modifier.name !== id);
|
|
const existingPill = document.getElementById(id);
|
|
if (existingPill !== null) {
|
|
existingPill.removeEventListener("click", () => pillClickHandler(pill));
|
|
existingPill.remove();
|
|
}
|
|
} else {
|
|
const existingModifier = payload[type].find(
|
|
(modifier) => modifier.name === id
|
|
);
|
|
if (existingModifier) {
|
|
existingModifier.params = params;
|
|
} else {
|
|
payload[type].push({ name: id, params: params });
|
|
}
|
|
const existingPill = document.getElementById(id);
|
|
if (existingPill === null) {
|
|
createPill(type, id);
|
|
}
|
|
}
|
|
|
|
submitForm();
|
|
}
|
|
|
|
function addModifierToNinjaData(id, description, params, type) {
|
|
const section =
|
|
type === "requestmodifications"
|
|
? "Request Modifiers"
|
|
: "Response Modifiers";
|
|
const modifier = {
|
|
id: id,
|
|
title: id,
|
|
section: section,
|
|
|
|
handler: () => {
|
|
if (Object.keys(params).length === 0) {
|
|
toggleModifier(type, id);
|
|
} else {
|
|
if (params[0].name === "_") {
|
|
toggleModifier(type, id, (params = [""]));
|
|
} else {
|
|
getValues(type, id, description, params).then((values) => {
|
|
if (Object.keys(values).length === 0) return;
|
|
toggleModifier(type, id, values);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
ninjaData.push(modifier);
|
|
}
|
|
|
|
const ninja = document.querySelector("ninja-keys");
|
|
ninja.data = ninjaData;
|
|
document.getElementById("btnNinja").addEventListener("click", () => {
|
|
ninja.open();
|
|
});
|