roadrunnertwice: Dialogue: "Craigslist is killing mothra." (Craigslist is killing Mothra (C&G))
[personal profile] roadrunnertwice

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.

This account has disabled anonymous posting.
(will be screened if not validated)
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org