lazyd3v blog
I like to drink good wine, it’s one of my favorite drinks. Also, I’m a huge fan of Vivino app. If you don’t know it is an awesome app with a big collection of wine reviews. This collection is that big, so I couldn’t find a wine that wasn’t registered in Vivino.
Also, I live in Sweden. There’s only one retail store allowed to sell alcoholic beverages, it’s called “Systembolaget”. Usually, I go there, start taking a photo of every available wine and after ~20 minutes I choose some. Yeah, I look like an idiot, but always choose a good wine. Recently I thought maybe there’s a way to optimize the process of choosing the right wine?
Systembolaget has a very nice website and webshop. You can look what is available to deliver or can be picked up in the nearest store. But there’s no Vivino score or at least reviews from other buyers.
Vivino has a web version of app as well. There’s no possibility to scan labels via photo, but it’s not needed actually. You can just search for wine by name and find more details. Unfortunately, you can’t filter wines that available in the nearest Systembolaget. Of course, you can copy-paste the name, but it’s even more annoying than taking a photo of every wine in the store.
“Challenge accepted” - I said and started to think about how to integrate Vivino score into Systembolaget website.
It was obvious that I needed to create some chrome extension to achieve that. After some research, I concluded that Vivino doesn’t have any public API and has no alternatives. So I started to think about how can I use their website for my needs.
The answer was simple - just use the search functionality. Make a request, parse HTML using cheerio and extract the info. Quite simple!
But as always - everything is not as simple as it seems. CORS 😡. Due to security reasons, you can’t make cross-origin requests from content scripts. But there’s another way to do it - fetch URLs via background script as advised here
Ok. I can extract information from Vivino by name, how to add this info to Systembolaget website. I looked to the markup, found some places where I can attach Vivino score … and it’s working! wow!
Then I scrolled down and realized they have a lazy-loading mechanism on a wine list. Ok - just add MutationObserver and append Vivino score after the list item is loaded. Unfortunately, it didn’t work. Maybe I’m stupid and missed something or there’s some magic in their app - but MutationObserver’s callback didn’t want to be executed. My theory is because of using React, it recreates DOM nodes and doesn’t attach MutationObservers to a new one. But I’m not sure, I’m not an expert at such low-level things, it’s just my theory.
I came up with a simpler solution - just attaching scores on list items on a scroll. I also applied debounce
to achieve better performance.
Another thing for better perfomace is cache. I used axios-cache-adapter for cache mechanizm itself, and localForage with localforage-webExtensionStorage-driver to store this cache in extension’s local storage.
Here’s the screenshot how it looks like:
Source code is available here