Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions css/panel.css
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,37 @@ body.theme-transitioning img {
box-sizing: border-box;
}

.url-mapping-list {
display: flex;
flex-direction: column;
gap: 10px;
}

.url-mapping-row {
display: flex;
align-items: center;
gap: 8px;
}

.url-mapping-row .form-control {
width: auto;
flex: 1;
}

.url-mapping-arrow {
color: var(--text-color);
opacity: 0.6;
}

.url-mapping-remove {
width: 24px;
height: 24px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
}

.form-row {
display: flex;
gap: 10px;
Expand Down Expand Up @@ -1708,11 +1739,13 @@ tr.selected {
}

/* Custom Tooltip */
.icon-btn[data-tooltip] {
.icon-btn[data-tooltip],
.secondary-btn[data-tooltip] {
position: relative;
}

.icon-btn[data-tooltip]:hover::after {
.icon-btn[data-tooltip]:hover::after,
.secondary-btn[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
Expand Down Expand Up @@ -2176,6 +2209,10 @@ pre {
align-self: flex-start;
}

.send-btn-local {
margin-left: 8px;
}

.status-badge {
padding: 4px 10px;
border-radius: 4px;
Expand Down Expand Up @@ -5316,4 +5353,3 @@ pre {
transform: translateX(0);
}
}

152 changes: 152 additions & 0 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { initExtractorUI } from './features/extractors/index.js';
import { setupAIFeatures } from './features/ai/index.js';
import { setupLLMChat } from './features/llm-chat/index.js';
import { handleSendRequest } from './network/handler.js';
import { getUrlMappings, saveUrlMappings } from './network/url-mapping.js';
import { initSearch } from './search/index.js';

// UI Modules
Expand Down Expand Up @@ -119,6 +120,9 @@ document.addEventListener('DOMContentLoaded', () => {
if (elements.sendBtn) {
elements.sendBtn.addEventListener('click', handleSendRequest);
}
if (elements.sendBtnLocal) {
elements.sendBtnLocal.addEventListener('click', () => handleSendRequest({ useUrlMapping: true }));
}

// Remove Duplicates Toggle
if (elements.removeDuplicatesBtn) {
Expand Down Expand Up @@ -216,6 +220,154 @@ document.addEventListener('DOMContentLoaded', () => {
});
}

// URL Mapping
const urlMappingBtn = document.getElementById('url-mapping-btn');
const urlMappingModal = document.getElementById('url-mapping-modal');
const urlMappingList = document.getElementById('url-mapping-list');
const addUrlMappingBtn = document.getElementById('add-url-mapping-btn');
const saveUrlMappingBtn = document.getElementById('save-url-mapping-btn');
const urlMappingBulkInput = document.getElementById('url-mapping-bulk-input');
const applyUrlMappingBulkBtn = document.getElementById('apply-url-mapping-bulk-btn');

function createUrlMappingRow(mapping = { from: '', to: '' }) {
const row = document.createElement('div');
row.className = 'url-mapping-row';
row.innerHTML = `
<input type="text" class="form-control url-mapping-from" placeholder="From (prefix)">
<span class="url-mapping-arrow">→</span>
<input type="text" class="form-control url-mapping-to" placeholder="To (prefix)">
<button type="button" class="icon-btn url-mapping-remove" title="Remove">×</button>
`;

const fromInput = row.querySelector('.url-mapping-from');
const toInput = row.querySelector('.url-mapping-to');
const removeBtn = row.querySelector('.url-mapping-remove');

if (fromInput) fromInput.value = mapping.from || '';
if (toInput) toInput.value = mapping.to || '';

if (removeBtn) {
removeBtn.addEventListener('click', () => {
row.remove();
});
}

return row;
}

function loadUrlMappingsIntoModal() {
if (!urlMappingList) return;
urlMappingList.innerHTML = '';
const mappings = getUrlMappings();
if (mappings.length === 0) {
urlMappingList.appendChild(createUrlMappingRow());
return;
}
mappings.forEach(mapping => {
urlMappingList.appendChild(createUrlMappingRow(mapping));
});
}

if (urlMappingBtn && urlMappingModal) {
urlMappingBtn.addEventListener('click', () => {
loadUrlMappingsIntoModal();
if (urlMappingBulkInput) urlMappingBulkInput.value = '';
urlMappingModal.style.display = 'block';
});
}
Comment thread
tengfeisky marked this conversation as resolved.

if (urlMappingModal) {
const urlMappingCloseBtn = urlMappingModal.querySelector('.close-modal');
if (urlMappingCloseBtn) {
urlMappingCloseBtn.addEventListener('click', () => {
urlMappingModal.style.display = 'none';
});
}
}

if (addUrlMappingBtn) {
addUrlMappingBtn.addEventListener('click', () => {
if (urlMappingList) {
urlMappingList.appendChild(createUrlMappingRow());
}
});
}

if (applyUrlMappingBulkBtn) {
applyUrlMappingBulkBtn.addEventListener('click', () => {
if (!urlMappingList || !urlMappingBulkInput) return;
const text = urlMappingBulkInput.value || '';
const entries = parseBulkOverrides(text);
if (entries.length === 0) {
alert('No valid overrides found.');
return;
}

const rowMap = new Map();
urlMappingList.querySelectorAll('.url-mapping-row').forEach(row => {
const fromInput = row.querySelector('.url-mapping-from');
if (fromInput && fromInput.value.trim()) {
rowMap.set(fromInput.value.trim(), row);
}
});

entries.forEach(({ from, to }) => {
const existingRow = rowMap.get(from);
if (existingRow) {
const toInput = existingRow.querySelector('.url-mapping-to');
if (toInput) toInput.value = to;
} else {
const newRow = createUrlMappingRow({ from, to });
urlMappingList.appendChild(newRow);
rowMap.set(from, newRow);
}
});

urlMappingBulkInput.value = '';
});
}

if (saveUrlMappingBtn && urlMappingModal) {
saveUrlMappingBtn.addEventListener('click', () => {
if (!urlMappingList) return;
const rows = Array.from(urlMappingList.querySelectorAll('.url-mapping-row'));
const mappings = rows.map(row => {
const fromInput = row.querySelector('.url-mapping-from');
const toInput = row.querySelector('.url-mapping-to');
return {
from: fromInput ? fromInput.value.trim() : '',
to: toInput ? toInput.value.trim() : ''
};
});

if (rows.length === 0 || mappings.some(mapping => !mapping.from || !mapping.to)) {
alert('Both fields are required for each route override.');
return;
}

saveUrlMappings(mappings);
alert('Route overrides saved.');
urlMappingModal.style.display = 'none';
});
}

function parseBulkOverrides(text) {
return text
.split('\n')
.map(line => line.trim())
.filter(Boolean)
.map(line => {
const separator = '=>';
const separatorIndex = line.indexOf(separator);
if (separatorIndex === -1) return null;
const from = line.slice(0, separatorIndex).trim();
const to = line.slice(separatorIndex + separator.length).trim();
if (!from || !to) return null;
return { from, to };
})
.filter(Boolean);
}

// History Navigation
// Undo/Redo buttons
if (elements.undoBtn) {
Expand Down
23 changes: 19 additions & 4 deletions js/network/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { elements } from '../ui/main-ui.js';
import { events, EVENT_NAMES } from '../core/events.js';
import { parseRequest } from './capture.js';
import { sendRequest } from './request-sender.js';
import { applyUrlMappings, getUrlMappings } from './url-mapping.js';
import { formatRawResponse, getStatusClass } from './response-parser.js';
import { formatBytes } from '../core/utils/format.js';
import { renderDiff } from '../core/utils/misc.js';
Expand All @@ -12,7 +13,7 @@ import { generateHexView } from '../ui/hex-view.js'
import { generateJsonView } from '../ui/json-view.js'
import { saveEditorState } from '../ui/request-editor.js';

export async function handleSendRequest() {
export async function handleSendRequest({ useUrlMapping = false } = {}) {
const rawContent = elements.rawRequestInput.innerText;
const useHttps = elements.useHttpsCheckbox.checked;

Expand All @@ -30,13 +31,27 @@ export async function handleSendRequest() {

try {
const { url, options, method, filteredHeaders, bodyText } = parseRequest(rawContent, useHttps);
let targetUrl = url;

if (useUrlMapping) {
const mappings = getUrlMappings();
if (mappings.length === 0) {
alert('No route overrides configured. Add one in Route Override.');
return;
}
targetUrl = applyUrlMappings(url, mappings);
if (targetUrl === url) {
alert('No route override matched this request.');
return;
}
}

elements.resStatus.textContent = 'Sending...';
elements.resStatus.className = 'status-badge';

console.log('Sending request to:', url);
console.log('Sending request to:', targetUrl);

const result = await sendRequest(url, options);
const result = await sendRequest(targetUrl, options);

elements.resTime.textContent = `${result.duration}ms`;
elements.resSize.textContent = formatBytes(result.size);
Expand Down Expand Up @@ -115,7 +130,7 @@ export async function handleSendRequest() {
origins: ['<all_urls>']
}, (granted) => {
if (granted) {
handleSendRequest();
handleSendRequest({ useUrlMapping });
} else {
elements.rawResponseDisplay.innerHTML += '<p style="color: var(--error-color); margin-top: 10px;">Permission denied.</p>';
}
Expand Down
40 changes: 40 additions & 0 deletions js/network/url-mapping.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const STORAGE_KEY = 'rep_url_mappings';


function normalizeMappings(mappings) {
if (!Array.isArray(mappings)) return [];
return mappings
.map(mapping => ({
from: typeof mapping?.from === 'string' ? mapping.from.trim() : '',
to: typeof mapping?.to === 'string' ? mapping.to.trim() : ''
}))
.filter(mapping => mapping.from && mapping.to);
}

export function getUrlMappings() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === null) return [];

try {
const parsed = JSON.parse(stored);
return normalizeMappings(parsed);
} catch (err) {
localStorage.removeItem(STORAGE_KEY);
return [];
}
}

export function saveUrlMappings(mappings) {
const normalized = normalizeMappings(mappings);
localStorage.setItem(STORAGE_KEY, JSON.stringify(normalized));
}

export function applyUrlMappings(url, mappings = getUrlMappings()) {
for (const mapping of mappings) {
if (!mapping.from || !mapping.to) continue;
if (url.startsWith(mapping.from)) {
return mapping.to + url.slice(mapping.from.length);
}
}
return url;
}
1 change: 1 addition & 0 deletions js/ui/main-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function initUI() {
elements.rawRequestInput = document.getElementById('raw-request-input');
elements.useHttpsCheckbox = document.getElementById('use-https');
elements.sendBtn = document.getElementById('send-btn');
elements.sendBtnLocal = document.getElementById('send-btn-local');
elements.rawResponseDisplay = document.getElementById('raw-response-display');
elements.rawResponseText = document.getElementById('raw-response-text');
elements.hexResponseDisplay = document.getElementById('res-hex-display');
Expand Down
Loading