Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# IDE
.vscode
.idea

# Logs
logs
*.log
Expand Down Expand Up @@ -29,6 +33,7 @@ build/Release
# Dependency directories
node_modules
jspm_packages
package-lock.json

# Optional npm cache directory
.npm
Expand Down
11 changes: 11 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always"
}

1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 24.10.0
88 changes: 68 additions & 20 deletions options.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<title>redwood</title>
<style>
body { padding: 10px; }
label { display: block; margin-bottom: 5px; }
label > span { display: inline-block; font-weight: bold; min-width: 120px; padding-right: 10px; text-align: right; }
label > input { width: 210px; }
footer { margin-top: 20px; text-align: center; }
button { cursor: pointer; }
</style>
<script src="options.js"></script>
<title>redwood</title>
<style>
body {
padding: 10px;
}

label {
display: block;
margin-bottom: 5px;
}

label > span {
display: inline-block;
font-weight: bold;
min-width: 120px;
padding-right: 10px;
}

label > input[type='text'] {
width: 400px;
}

footer {
margin-top: 20px;
text-align: center;
}

button {
cursor: pointer;
}
</style>
<script src="options.js"></script>
</head>
<body>
<main>
<label><span>Match URL prefix</span><input name="match"></label>
<label><span>Replace URL prefix</span><input name="replace"></label>
<label><span>Webpack Dev Server?</span><input name="wds"></label>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In retrospect, it's ridiculous that we left this as a text input for as long as we did

<label><span>Web Server Port (if using wds)</span><input name="webServerPort"></label>
<label><span>Compressed?</span><input name="compressed"></label>
<footer>
<button name="save">Save</button>
</footer>
</main>
<main>
<div>
<span>MongoCDN Presets:</span>
<input type="button" name="preset" value="Development"/>
<input type="button" name="preset" value="QA"/>
<input type="button" name="preset" value="Production"/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the initial idea for the plugin when Justin Moses made it way back in... 2017 or whatever... was that it'd be more general-use, but since Eric moved it into mongodb-labs... having presets is a fantastic idea!

Copy link
Author

@nima-taheri-mongodb nima-taheri-mongodb Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking a bit more about your comment Jeremy, what do you think of simplifying config even further? Like:

  • removing the configurable source base-url and only having the presets.
  • removing the compressed and wds options and only letting users specify their webpack-dev-server local assets url.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real question to me, the more I think about this PR, is whether we want to continue supporting Redwood at all, or if we just want to maintain config that can be imported into that tool you demonstrated at Web Chapter (which I've already forgotten the name of). It seems like that tool is already a clear massive step up for the more "generic" approach, and might also be a simpler DX in general, since it'd avoid the kind of hacky installation process we have with this tool right now.

Copy link
Author

@nima-taheri-mongodb nima-taheri-mongodb Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately there are not many browser extensions that do rule-based http(s) interception, I found these two in my search:

  • https://github.com/vvmgev/Inssman It's a bit janky, export/import has small issue, http-response interception has small issue, a maintainer removed regex matching support for no good reason recently.. that is what I found out in a day there should be more.
  • https://github.com/requestly/requestly Although open-source it's commercial and only allows defining 4 rules or so.

Me personally I'm pretty happy with using https://www.mitmproxy.org/ and writing interception rules. I've developed one for myself, that redirects assets to webpack-dev-server, overrides feature-flags, forces delay to test loading shimmers. It's easier to test/develop/maintain in general than a browser extension.

</div>
<br/>
<label for="match">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do <label> elements actually need a for attribute when they're wrapping the input?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, removed it

<span>Match URL prefix</span>
<input type="text" name="match" id="match">
</label>
<br/>
<label for="replace">
<span>Replace URL prefix</span>
<input type="text" name="replace" id="replace">
</label>
<br/>
<label for="wds">
<input type="checkbox" name="wds" id="wds" value="true">
<span>Webpack Dev Server?</span>
</label>
<br/>
<label for="webServerPort">
<span>Web Server Port (if using Webpack Dev Server)</span>
<input type="text" name="webServerPort" id="webServerPort">
</label>
<br/>
<label for="compressed">
<input type="checkbox" name="compressed" id="compressed" value="true">
<span>Compressed?</span>
</label>
<footer>
<button type="button" name="save">Save</button>
</footer>
</main>
</body>
</html>
94 changes: 69 additions & 25 deletions options.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,72 @@
setInputs = (items) => {
document.querySelector('input[name=match]').value = items.match;
document.querySelector('input[name=replace]').value = items.replace;
document.querySelector('input[name=wds]').checked = items.wds;
document.querySelector('input[name=webServerPort]').value = items.webServerPort;
document.querySelector('input[name=compressed]').checked = items.compressed;
};

setPreset = (event) => {
const presets = {
Development: {
match: 'https://assets-dev.mongodb-cdn.com/mms',
replace: 'http://localhost:8081',
wds: true,
webServerPort: 8081,
compressed: false,
},
QA: {
match: 'https://assets-qa.mongodb-cdn.com/mms',
replace: 'http://localhost:8081',
wds: true,
webServerPort: 8081,
compressed: false,
},
Production: {
match: 'https://assets.mongodb-cdn.com/mms',
replace: 'http://localhost:8081',
wds: true,
webServerPort: 8081,
compressed: false,
},
};

setInputs(presets[event.target.value]);
};

document.addEventListener('DOMContentLoaded', () => {
// Use default value color = 'red' and likesColor = true.
chrome.storage.sync.get({
match: 'https://example.com',
replace: 'http://127.0.0.1:80',
wds: true,
webServerPort: 8080,
compressed: false
}, items => {
document.querySelector('input[name=match]').value = items.match
document.querySelector('input[name=replace]').value = items.replace
document.querySelector('input[name=wds]').value = items.wds
document.querySelector('input[name=webServerPort]').value = items.webServerPort
document.querySelector('input[name=compressed]').value = items.compressed
})
// Use default value color = 'red' and likesColor = true.
chrome.storage.sync.get(
{
match: 'https://example.com',
replace: 'http://127.0.0.1:80',
wds: true,
webServerPort: 8080,
compressed: false,
},
(items) => {
setInputs(items);
}
);

const getInput = (name) => document.querySelector(`input[name=${name}]`).value;
const getCheckbox = (name) => document.querySelector(`input[name=${name}]`).checked;

const getInput = name => document.querySelector(`input[name=${name}]`).value
document.querySelectorAll('input[name=preset]').forEach((button) => {
button.addEventListener('click', setPreset);
});

document.querySelector('button[name=save]').addEventListener('click', () => {
chrome.storage.sync.set({
match: getInput('match'),
replace: getInput('replace'),
wds: getInput('wds'),
webServerPort: getInput('webServerPort'),
compressed: getInput('compressed')
}, chrome.runtime.reload)
})
})
document.querySelector('button[name=save]').addEventListener('click', () => {
chrome.storage.sync.set(
{
match: getInput('match'),
replace: getInput('replace'),
wds: getCheckbox('wds'),
webServerPort: getInput('webServerPort'),
compressed: getCheckbox('compressed'),
},
chrome.runtime.reload
);
window.close();
});
});
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "redwood",
"version": "1.0.0",
"description": "Redirect network requests for assets to test production services with local changes.",
"private": true,
"type": "module",
"scripts": {
"test": "node --test",
"test:watch": "node --test --watch"
},
"devDependencies": {
"@types/chrome": "^0.0.270",
"@types/node": "^24.8.1",
"prettier": "^3.6.2"
}
}

Loading