You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
69 lines
6.7 KiB
Markdown
69 lines
6.7 KiB
Markdown
+++
|
|
title = "the third can"
|
|
|
|
[extra]
|
|
image = "/ttc_config.png"
|
|
image_desc = "a screenshot of The Third Can's configuration page, v0.2.0"
|
|
+++
|
|
|
|
i made a browser extension with [theki](https://theki.club) and contributions from [sandvich](https://sandvich.xyz). it adds lots of useful features to [TwoCans](https://twocansandstring.com).
|
|
|
|
i figure that in addition to sharing how you can use the extension for yourself, i could talk a bit here about how it works internally. but first, the obligatory:
|
|
|
|
## links
|
|
- [chrome web store](https://chrome.google.com/webstore/detail/the-third-can/leimkcdimeemfabpjbjhiccaolpdbjng)
|
|
- [mozilla add-ons](https://addons.mozilla.org/en-US/firefox/addon/the-third-can/)
|
|
- [github](https://github.com/aprzn123/TheThirdCan/#readme)
|
|
|
|
i'd list the features here, but they're already listed right there on the readme so i'm not going to repeat myself (as much)
|
|
|
|
you can also run the development build (instructions on the github readme). it's less stable and doesn't persist across browser restarts (at least on firefox), but it gets new features sooner than the standard methods. not recommended unless you want to help test new features.
|
|
|
|
## ok, rambling time
|
|
now that we've gotten the big stuff out of the way, i can just talk about whatever here. expect comments about how thethirdcan works, planned features, etc. do not expect incredibly high quality writing. will probably be updated periodically as i have Thoughts.
|
|
|
|
### script injection
|
|
so here's the deal: some features of thethirdcan, such as the skip button confirmation prompt, require access to objects in the javascript runtime of the webpage (notably the `TC` object that has a lot of functions for answering, skipping, etc). for security reasons, the extension code cannot interact directly with this namespace, so we need a way to be able to write code that runs within the same namespace.
|
|
|
|
at the beginning, this was simple. i could just write a script in a separate file, and use the extension to add a new `<script>` tag to the webpage with that file as the source. and to be honest, it's still effectively that simple. but it's such a common pattern that we needed a centralized function for consistency.
|
|
|
|
### configuration
|
|
we had another issue around the same time. the extension has a little popup to allow users to enable or disable any feature they want, and while they all saved their values in the same object, beyond that it was pretty much the wild west. you had to handle all the saving and loading mechanics yourself, check the config values all over the place, and so on.
|
|
|
|
on the side of the config popup, we rewrote it to be a bit more structured. we keep a list of enum options and boolean options, and pretty much everything else is handled automagically. (i say this as if it's complicated, it's really just iterating over a bunch of DOM objects from the html). however, this didn't help with the end that consumes the config values.
|
|
|
|
### introducing `third`
|
|
the best javascript object since `slicedBread`!
|
|
|
|
`third` is a big collection of utilities, including some that solve both the script injection problem and the configuration consumption problem, much of which even occurs in the same function! let's take a look at how we built up the Big And Very Important Function, `third.InjectToggleableScripts`:
|
|
|
|
so we began with a simple function, `third.InjectScript`. it takes a path to the script that needs to be injected, and an optional configuration object in case the script element needs to be async, or defer, or anything like that. and we considered when we usually need to inject scripts--it is usually "when this configuration option is true". so instead of having many separate blocks that looked like this, we could do with a single function call that grouped together config options with the scripts that they enable.
|
|
|
|
```js
|
|
if (config.someOption) {
|
|
third.InjectScript("someScript.js");
|
|
}
|
|
```
|
|
so we created a new api, the first iteration of `third.InjectToggleableScripts`, which would be called like this:
|
|
```js
|
|
third.InjectToggleableScripts({
|
|
someOption: "someScript.js",
|
|
someOtherOption: "someOtherScript.js"
|
|
})
|
|
```
|
|
it was a good first attempt, and works well in many situations. however, it doesn't support all the options of `third.InjectScript`, so we updated so that it could take an object instead of just a string. the object would contain a name element with the former string as its value, and then various configuration options.
|
|
|
|
sometimes, though, we need to inject multiple scripts when an option is enabled, and that doesn't fit with this structure. we would need two identical keys, which is simply not an option. so we had to make the configuration object even *more* dynamic: allowing the string/object to be replaced with an array of the same. at this point the api is getting pretty complicated, but it does almost everything we need, and we are almost to the current iteration. but before we get to the current iteration, we need to take a break to talk about `fourth`.
|
|
|
|
### `fourth`
|
|
originaly, we thought `third` was the only utility object we'd need. however, an issue came up in that there's no good way to inject `third` itself into a page to provide utilities for injected scripts--and even if we could, it contains functions that just don't make sense to execute in that context. so we decided to keep up the naming scheme (even if perhaps we shouldn't've) and introduce an *injected* utility object, `fourth`.
|
|
|
|
### injecting `fourth`
|
|
since at this point all scripts were injected with `third.InjectToggleableScripts`, we could take advantage of that to only inject `fourth` when it was potentially relevant. once we filter the scripts to only the ones that need to be injected, we pause for a moment. the list could be empty, and if so, we can return early. otherwise, we move on to inject `fourth` and then the remaining scripts. but here's the thing: depending on what page we're on, some of `fourth`'s utilities can be performed more efficiently than in the general case. so here's what we do to define `fourth`'s api:
|
|
1. inject a simple script defining `const fourth = {};`
|
|
2. inject a script for all the `fourth` fuctions with invariant definitions.
|
|
3. depending on what page we are on, inject a separate function for each function that has a potentially variable definition (ensuring, of course, that the api remains consistent).
|
|
problem is, we don't have any way to tell what page we're on. so that is the final change that gets us to the current incarnation of `third.InjectToggleableScripts`: adding one more argument at the end to indicate what page we're on.
|
|
|
|
at this point, `third`'s injection method has been working really well for us, and (along with improved docs) has made it much faster and less error-prone to build and test new features!
|