0

I am writing a Google Chrome extension. It will monitor what is happening on a web page, communicate with a database-backed server, etc. I'm wondering if anyone knows good design patterns, given how asynchronous JavaScript is becoming. Here are the challenges:

  • Before I can do anything, I need to read from chrome.storage. I can't even connect to a server before I've read settings to make that connection
  • Before I can interact with the page, I would like to get data from the server
  • On the other hand, I need to know what the page is doing the whole time; this is a place where I can't loose a bit.

In a synchronous programming environment, I would:

  1. Try to connect to the server
  2. If successful, finish loading the page, and use the data from the server to do back-and-forth with the page
  3. If unsuccessful, read the last state from chrome.storage, and use that to interact with the page, and save interactions to chrome.storage for sync when the server is back.

With async, it seems like there's a million dependencies, orders in which operations might happen, etc. I can design a complex state machine which queues up events from the page when the extension starts, while reading server settings, then connecting to the server, than modifying the page, but it seems super-complex, and like there's some elegant design pattern I'm missing.

The second piece is that even building a simply but robust event queue in chrome.storage seems complex, since there aren't atomic operations, which leads to race conditions. It doesn't even list an API for listing what keys are stored, so it seems easy to lose things if I store things under object-specific keys depending on race conditions. Again, I can build an FSM that avoids race conditions, but it seems super-complex.

Any suggestions on good articles, blog posts, or similar things I should look at?

The really critical thing -- actually -- is just to make sure I robustly capture every event of a certain type on the page, whenever it happens (even if it happens as the page is loading, if there's an outage communicating with my server, if the user randomly closes the browser, if two events come in within 1ms of each other, etc.).

  • It's unclear why you need to **constantly** read/save the state in chrome.storage. I think you can prepare the data just once long time before the page even starts loading by using chrome.webNavigation.onBeforeNavigation or chrome.webRequest.onSendHeaders, then send the data via messaging to the content script and then it can do everything in the usual synchronous fashion. Anyway with the generic description present so far I don't see why async should be a factor. I'd say you're overthinking it, for example generally there's no race conditions in JS since it's single-threaded. – wOxxOm Feb 07 '20 at 04:41
  • Example of a race condition in single-threaded: (1) I read a list of events containing saved events from chrome.storage to send to server. When that finishes, I send the events to the server. When that finishes, I flush the list. (2) When a new event comes in, I want to enqueue it: Read a list from chrome.storage. When that's done, add the event to the list. When that's done, write the list back. Depending on ordering of (1) and (2) async actions, I can get lots of forms of data corruption. – stack_anonymous Feb 09 '20 at 02:39
  • Ah, but that sounds simple ([example](https://stackoverflow.com/a/38906083)) if I understand the problem correctly. Anyway my point was there's likely no need for all that since there should be no need to constantly update the storage while working on the page. – wOxxOm Feb 09 '20 at 04:17
  • That example doesn't look right. JavaScript is single-threaded, but it is asynchronous. As I understand, in that example, I call the `get`. The browser does whatever magic it does. During that time, a dozen events can come in. At that point, the browser calls the callback. I can be guaranteed nothing else will happen during the callback itself, but there can be many events between the `get` and when the browser finishes retrieving the data, and calls the callback with the `set`. If I wanted to guarantee the example worked correctly, I'd need some kind of mutex. – stack_anonymous Feb 10 '20 at 13:31
  • You're right about initializing doing this during onBeforeNavigation. I don't think that solves it, though. I can start a read event in there, start to initialize the server connection, etc. but I can't block on when the callback gets called. In other words, the function will return before my callbacks get called. I also want a persistent queue to make sure all the data gets back to the server. Browser crash, computers run out of power, networks go down, etc., and AJAX events sometimes don't succeed. I'm finding the storage API to be really difficult for that. – stack_anonymous Feb 10 '20 at 13:39
  • That example will be just fine in the use case it was intended for as all it does is maintaining one `queue` object. You also can use a similar approach but I would suggest using messaging so the background script will write the data into a much more advanced and suiting IndexedDB for example. – wOxxOm Feb 10 '20 at 14:11
  • Why is it fine? Is there a guarantee that the callback will happen synchronously? It seems like if I have two events call updateStorage() at almost the exact same time, what happens is implementation-dependent. For example, event 1 calls get(), event 2 calls get(), the browser collects 'commands' from storage twice. It calls both callbacks. The first callback appends the new commands, and the second removes them. – stack_anonymous Feb 11 '20 at 15:15
  • Uhm, you could be right actually as I didn't test my example but my point was it's not that hard to maintain a coherent state that's synchronized to the storage: I mean you don't need to read from the storage like that example does so you'll be perfectly fine and the code will be really simple. – wOxxOm Feb 11 '20 at 15:21
  • Okay. IndexedDB looks like exactly what I want. Thank you! That has transactions and everything I need. I did not know that existed. – stack_anonymous Feb 11 '20 at 15:26

0 Answers0