How did you manage to get the settings to appear on the menu and how did you manage to change the style of the webpage?
Ive been trying to make my own extension but i cant quite figure out how to do these things! I would appreciate it if you could explain how to!
Before I begin, I want to make it clear that this message will get very technical with the internals of how React works. The way that Azalea works is completely different to other extensions - it's a little more advanced, with more similarities to client mods like Vencord or Unbound (for Discord).
Monkey Patching
You should probably already be familiar with the concept of patching objects for this, but if you aren't, I'll explain briefly.
Basically, if you have an object like this:
const foo = {
bar(name) {
return `hello, ${name}!`;
}
}
you can "monkey patch" this object's bar method to add your own functionality. Observe:
const origBar = foo.bar;
foo.bar = function(name) {
console.log("hi bar was called");
return origBar(name);
}
when you call foo.bar("baz") now, it will first call our custom function, then call the original function.
You can extend this quite a lot, and I do this very often in Azalea, as it's an easy way to patch React components.
React exfiltration
At a high level, Sparx uses a JavaScript framework called React which allows you to create reactive UIs declaratively. If you don't know what that means, basically it lets you embed JavaScript into HTML directly, such as:
function MyButton({ text }) {
return <button>{text}</button>;
}
aswell as creating reactive behavior (elements update their state reactively / react to state changes):
function Counter() {
const [count, setCount] = React.useState(0);
return <>
{/* When setCount is called `count` here will update automatically */}
<h1>The count is {count}</h1>
<button onClick={() => setCount(prev => prev + 1)}>Increment</button>
<button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
</>;
}
To understand how Azalea works, you should already be very familiar with how React works. If you are familiar already, then great! If not, then you should go and learn it, because the rest of this message will make no sense otherwise.
Essentially, for React to function on the page, Sparx needs to load a big script called a "bundle" which basically just gives you an object that holds utility methods and internal state of React - things such as hooks: useState, useEffect and functions: createElement.
If we can find a way to retrieve this bundle when it's loaded by Sparx (which is quite early), then we can call the createElement function ourselves and render elements on the page.
That's what this does. It allows us to "exfiltrate" React when the Object that we're looking for is created. Great! Now we can render elements on the page. But this is only the 1st step (of maybe 3).
Patching React elements
Next, we have to find a way to inject our own buttons into the page. To do this, I have this patch. Essentially, elements that are rendered by React have an internal state in the tree rendered to the page - these are called fibers. You can examine this yourself on a React-based page.
For example, say you have this:
<p id="foo">Hello, world!</p>
Assuming this is rendered by React, you can yield this element in the DOM by doing document.querySelector("#foo"). Now, if you call Object.keys() on the returned object, you will see that there are keys which start with __reactFiber$ or __reactInternalInstance$ or __reactContainer$. With some magic, we can use these fibers to traverse React tree elements and get a specific one's "type" (the function which renders it to the screen). Then, we can apply a monkey patch to it, allowing us to modify the props passed into it and add extra things. In the case of menu buttons, I do this:
const dropdownNode = await lazyDefine(
() => document.querySelector('[class*="_DropdownMenuContent_"][role="menu"]'),
undefined, Infinity
);
const Dropdown = findReact(dropdownNode);
which, at a high level, waits for the menu dropdown node to exist in the DOM, then finds the internal React fiber of that node.
Now, we can monkey patch the node's type (the function which renders it). For this, I use a little library called spitroast which makes it easier to patch things, giving you utilities such as unpatch methods:
const unpatch = patcher.before('render', Dropdown.type, (args) => {
const buttons = findInReactTree(
args[0],
(x) => Array.isArray(x.children) && x.className.includes('_DropdownMenuContent_'),
);
if (!buttons) return;
const menuItems = Object.values(items)
.filter((item) => item.Item)
.map((item) => new item.Item()) satisfies MenuItem[];
menuItems.forEach((item) => {
for (const button of buttons.children) {
if (button?.props?.text === item.text) return;
}
const index = buttons.children.findIndex(x => findInReactTree(x, y => y[1] === 'Sign out'));
buttons.children.splice(
index === -1 ? 1 : index,
0,
<components.DropdownButton
text={item.text}
leading={item.leading}
onClick={item.callback}
/>,
);
});
});
Essentially, React trees are rendered via objects that have props. Now those props may have children, which is either directly an element, or an array of elements, if multiple children.
With this knowledge, we can use this little function:
const buttons = findInReactTree(
args[0],
(x) => Array.isArray(x.children) && x.className.includes('_DropdownMenuContent_'),
);
which traverses a tree specifically looking for the keys props and children until it finds a node which matches the predicate in the second argument. Now we have the array of buttons which would be rendered by the page! We can use our knowledge from earlier to render our own buttons here now. The code in my patch is a little more complicated because I specifically made it render above the "Sign out" button, but you can essentially just modify this array as you need! Now, when the page renders the dropdown's menu, it will first run our patch, modify the array of elements, and then render it.
Registering custom routes
Now for the final piece of the puzzle - pushing our own navigation to the page. This one is slightly more complicated, but again, at a high level, Sparx uses a library called React Navigation as an abstraction for pushing pages to the screen. This was an educated guess by me, as I wasn't entirely sure that's what they were using, but through some reverse engineering it turns out I was right.
The way this works is that it has a "context" which is a thing which is shared by multiple components in the React tree, potentially at different places, as long as they are all under the context's provider. With this knowledge, I patch every call to the useContext hook which allows me to filter for when the navigation context is created. At this point, I can register my own routes with the _internalSetRoutes method defined on the router.
Now our routes are defined, but how do we go to them? You can't really just navigate to /azalea/whatever manually (or by manually setting window.location.href or whatever), but you can do it programmatically via the navigation object provided by React Navigation. When patching the navigation router context thing, that object also has a navigation property on it, which gives us access to methods such as push to push pages to the screen. Therefore, if we save this variable for use later, azalea.navigation = res;, we can simply call azalea.navigation.push(path) to push our own pages to the navigation at any time.
That's what the Azalea menu buttons all do. They just navigate to the path predefined earlier when the route was registered, but programmatically.
This is basically all you need to make custom pages work! It's definitely far more complicated than a basic chrome extension, but that also means it's more robust.
Theming
Now for your second point, how the theming works. This one is actually much simpler. When the extension is loaded, a css file is loaded which overwrites various colors defined by Sparx with our own CSS variables. To modify them later, we can simply set these variables on the body element, and all colors will be overwritten accordingly. You should read theming.ts for more information about how you actually set CSS variables through JavaScript (or Google it).
Finally, I have a big json file, and with some bundling magic this is just embedded into the final JS bundle as predefined themes that you can toggle through.
I'm sorry for such a long explanation but Azalea is quite complicated. I could definitely go more in depth, but I tried to keep it quite brief. I hope this helps!!!
To understand how Azalea works, you should already be very familiar with how React works. If you are familiar already, then great! If not, then you should go and learn it, because the rest of this message will make no sense otherwise.
Do you know of a place TO learn it?
To understand how Azalea works, you should already be very familiar with how React works. If you are familiar already, then great! If not, then you should go and learn it, because the rest of this message will make no sense otherwise.
Do you know of a place TO learn it?
You can watch videos on YouTube. There are plenty by Web Dev Simplified which I like, although you won't really be tought reverse engineering of React there, just normal React. You should look at his older-ish videos because that's where all of the core contents of React are explained (and not libraries that complement it). He helped me understand concepts easier because his way of explaining is really good.
I personally mostly learnt React through reverse engineering experience though (I used to make plugins for a Discord mobile client mod called Enmity) but I understand you may not be at a position where you can do the same.
To understand how Azalea works, you should already be very familiar with how React works. If you are familiar already, then great! If not, then you should go and learn it, because the rest of this message will make no sense otherwise.
Do you know of a place TO learn it?
You can watch videos on YouTube. There are plenty by Web Dev Simplified which I like, although you won't really be tought reverse engineering of React there, just normal React. You should look at his older-ish videos because that's where all of the core contents of React are explained (and not libraries that complement it). He helped me understand concepts easier because his way of explaining is really good.
I personally mostly learnt React through reverse engineering experience though (I used to make plugins for a Discord mobile client mod called Enmity) but I understand you may not be at a position where you can do the same.
Ok thank you I'll try that, however that may take a while so would you like me to tell you all the ideas I was wanting to implement? (As a suggestion for Azalea, you don't have to do all or even any!)
To understand how Azalea works, you should already be very familiar with how React works. If you are familiar already, then great! If not, then you should go and learn it, because the rest of this message will make no sense otherwise.
Do you know of a place TO learn it?
You can watch videos on YouTube. There are plenty by Web Dev Simplified which I like, although you won't really be tought reverse engineering of React there, just normal React. You should look at his older-ish videos because that's where all of the core contents of React are explained (and not libraries that complement it). He helped me understand concepts easier because his way of explaining is really good. I personally mostly learnt React through reverse engineering experience though (I used to make plugins for a Discord mobile client mod called Enmity) but I understand you may not be at a position where you can do the same.
Ok thank you I'll try that, however that may take a while so would you like me to tell you all the ideas I was wanting to implement? (As a suggestion for Azalea, you don't have to do all or even any!)
Yeah sure! I don't really have that much time to implement new features into Azalea anymore but I'm definitely willing to listen to them.
To understand how Azalea works, you should already be very familiar with how React works. If you are familiar already, then great! If not, then you should go and learn it, because the rest of this message will make no sense otherwise.
Do you know of a place TO learn it?
You can watch videos on YouTube. There are plenty by Web Dev Simplified which I like, although you won't really be tought reverse engineering of React there, just normal React. You should look at his older-ish videos because that's where all of the core contents of React are explained (and not libraries that complement it). He helped me understand concepts easier because his way of explaining is really good. I personally mostly learnt React through reverse engineering experience though (I used to make plugins for a Discord mobile client mod called Enmity) but I understand you may not be at a position where you can do the same.
Ok thank you I'll try that, however that may take a while so would you like me to tell you all the ideas I was wanting to implement? (As a suggestion for Azalea, you don't have to do all or even any!)
Yeah sure! I don't really have that much time to implement new features into Azalea anymore but I'm definitely willing to listen to them.
Here they are:
Timer - optional to show how long you have been doing it for Notes - be able to write notes with text as you work it out Calculator - settings for on off or detect whether it is allowed (could use Desmos Embed) % to fraction - when you view the sparx maths homework on the home page instead of showing a percentage it shows a fraction of how many you’ve answered out of total When extension icon clicked take to website Export import settings - in case for whatever reason you want to send them to someone else Highlight correct bookwork Rewards - something to use XP for such as an avatar Write on images with a ‘pen’ when in the enlarged mode
I have removed all ones your extension already has and explained them a bit more. I made sure to TRY and only include things possible with a browser extension.
To understand how Azalea works, you should already be very familiar with how React works. If you are familiar already, then great! If not, then you should go and learn it, because the rest of this message will make no sense otherwise.
Do you know of a place TO learn it?
You can watch videos on YouTube. There are plenty by Web Dev Simplified which I like, although you won't really be tought reverse engineering of React there, just normal React. You should look at his older-ish videos because that's where all of the core contents of React are explained (and not libraries that complement it). He helped me understand concepts easier because his way of explaining is really good. I personally mostly learnt React through reverse engineering experience though (I used to make plugins for a Discord mobile client mod called Enmity) but I understand you may not be at a position where you can do the same.
Ok thank you I'll try that, however that may take a while so would you like me to tell you all the ideas I was wanting to implement? (As a suggestion for Azalea, you don't have to do all or even any!)
Yeah sure! I don't really have that much time to implement new features into Azalea anymore but I'm definitely willing to listen to them.
Here they are:
Timer - optional to show how long you have been doing it for Notes - be able to write notes with text as you work it out Calculator - settings for on off or detect whether it is allowed (could use Desmos Embed) % to fraction - when you view the sparx maths homework on the home page instead of showing a percentage it shows a fraction of how many you’ve answered out of total When extension icon clicked take to website Export import settings - in case for whatever reason you want to send them to someone else Highlight correct bookwork Rewards - something to use XP for such as an avatar Write on images with a ‘pen’ when in the enlarged mode
I have removed all ones your extension already has and explained them a bit more. I made sure to TRY and only include things possible with a browser extension.
Azalea actually already highlights the correct bookwork option, aswell as allowing you to export and import your saved bookwork codes. That's one of the core reasons for Azalea existing at all, but the other ones seem interesting.