Recent coding
Sep. 21st, 2021 04:18 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
I just finished doing a major update to Eardogger.com, my simple little bookmarks-for-webcomics app! šš¼
Whatās changed? Uhhhhh almost exactly nothing.
- If you were getting round-tripped to the āsaved!ā page and back on mobile Safari instead of the nice unobtrusive āok doneā banner, itās fixed! Generate a new bookmarklet to get the benefit.
- The delete buttons now have a (hilarious, IMO, but Iām easily amused) spinner effect while theyāre working, though blink and youāll miss it.
- If you have more than 50 dogears, theyāll get split into multiple pages. No one will ever see this. š¹ But pagination of unlimited data sets is good practice, since it makes you a little harder to DoS.
Yep: pretty much all the work was internal updates with very little visible effect. And boy, there were a lot of them. But hey: Eardogger basically exists in the first place for me to tinker with and learn things. I made it because I wanted it to exist, but I made it the way I did because I wanted to pick up some new skills.
So hereās what I got to monkey with this time:
I converted everything but the frontend JS to Typescript! I hadnāt had much experience with typed languages until recently, and guess what: types are nice. Iād been wanting a better grasp on what TS can and canāt do for me, so this was a good chance to see.
Basically, I knew I was going to need to move a ton of stuff around for token auth and pagination, and itās easy to fumble something or forget to update the expected arguments or return format of a function. Type annotations make it so you donāt have to go searching for everything you need to update when you overhaul a function; your editor and compiler will just tell you where they all are. So, you can make big changes more confidently, in exchange for an up-front cost.
I greatly expanded the test coverage! Previously I had really good tests for the database layer, but Iād had a hard time sorting out what to mock in order to test the rest of the app.
If you havenāt done this sort of thing before: tests are a way to record what you thought was important to remember about some behavior at a certain point in time, so that if the next person working on it breaks something, theyāll automatically get an early warning and a clear explanation of why it mattered. Youād think this would be less important on a single-person project, but it turns out I have only the vaguest sense of why Past Nick did anything.
The deal with mock behaviors is: doing tests that spin up the entire system and put it in some particular state are valuable, but incredibly inconvenient and time-consuming to deal with, so you donāt want too many of them; most of the time you want to fake at least part of the state. The problem is that fake state really wants to drift out of sync with the format of the real state, and once that happens your tests start lying to you.
Iāve dealt with mock drift before, and would prefer not to do it to myself, but maintaining mocks is pure overhead, so you really donāt want it to take much time or attention. Ideally, Iād like to get an automatic warning if my mocks go wonky.
Well, I think I came up with a way to get Typescript to protect me from the worst possibilities! I made a function signature type for each of the DB functions, then assigned each real function and each corresponding mock function to a variable of the appropriate type. Change the real implementation, it tells me itās now the wrong type; update the type, and it tells me the mock is now the wrong type, so I canāt forget to update it.
The combination of better tests and excessively typed DB mock functions caught a ton of almost-bugs when I was refactoring things, so that seems promising for next time I want to add something after not touching this for a year and a half.
I added token-based API authentication! This was something I had vaguely wanted to do at some point, because I was going to need it if I ever made a real browser extension or native app to replace the bookmarklet. But it eventually occurred to me that it could also solve my woes with Safari blocking cookie access for my bookmarklets!
If you havenāt really dealt with it before, the idea with token auth is:
- You generate some random garbage (a ātokenā), you associate it with specific permissions to do things to a specific personās account, you give them the token, and you store some way to recognize the token if they show it to you again.
- If someone or something shows you that token, they can act like theyāre logged in as the owner of the token, without needing to know their username or password.
- But usually, a token can only do a subset of what the actual logged-in user could do. For example, they can almost never change your password or delete your account.
This is a pretty common compromise for using a third-party app with a service without having to give that app a copy of your password. You have to give it some way to show it has permission to mess with your stuff, but it has limits, and you can revoke that permission at any time by deleting the token (without having to change your password).
The deal with Safari blocking my cookies was... my old bookmarklet was making background requests from a bunch of unrelated sites (the stuff youāre bookmarking) to a third-party site (Eardogger), and using a third-party cookie (your login) to associate those requests with a persistent dossier I was building on you (your list of bookmarks). In this case, itās because you were literally just asking me to remember some shit for you... but it also looks exactly like how ad-tech surveillance companies track you around the web and build a creepy profile of everything you do. Safari sure couldnāt tell the difference, so when they turned on tracking protections by default, it disabled the nice version of saving a bookmark and kicked it back to the slow way.
But using an API token instead of a login cookie solves the whole problem! Safari doesnāt know the request has person-specific credentials in it, so it doesnāt see it as surveillance. (Ad-spys canāt really use that technique, for various reasons; itās just not really practical to do it secretly or nonconsensually.) The downside is that now everyone needs to generate a personal bookmarklet instead of just all using the same one, but I think I got that sorted out.
Also, if I ever DO decide to make a browser extension or a native app, Iām now good to go on the authentication side.