Wednesday, July 13, 2022

Inactivity timer for Chrome extensions

My Quiz Cards browser extensions are interactive flash cards, giving users a way to practice their Spanish, German, US Capitals, and World Capitals, by simply clicking an icon on their browser.


Screenshot of Quiz Cards popup asking a spanish word

One of the Quiz Cards features is updating the browser icon with a little number to indicate how many days have passed since the last time you answered a card. 

When I upgraded the extension to manifest v3, I found I also needed to update that feature.

How it worked in manifest v2: whenever a user answered a question, the extension stored a timestamp in localStorage. The background script used setInterval to call a function every so often to see how many days passed since that timestamp, and if more than 0, it updated the badge with the number of days.

When using manifest v3, background pages are actually service workers. Using setInterval will no longer work reliably, since the browser stops and starts service workers when not in use. Instead, Chrome recommends using their alarms API instead. They also suggest using their storage API instead of localStorage.

So, in the flash card pop up, I run this code when a user answers a card:

chrome.storage.local.set({'last-asked': (new Date()).getTime())

chrome.action.setBadgeText({text: ''});


That stores the latest timestamp in storage and clears out any number that might have been previously set on the badge.


In the background service worker, I set an alarm to call a function every 60 minutes. That function retrieves the timestamp from storage, compares it to the current time, and updates the badge number if relevant.


async function sync() {

    const result = await chrome.storage.local.get(['last-asked']);

    const lastAsked = result[key];

    if (lastAsked) {

      const rightNow = (new Date()).getTime();

      const timeDiff = (rightNow - lastAsked);

      const DAY_MS = 86400000;

      if (timeDiff > DAY_MS) {

         chrome.action.setBadgeBackgroundColor({color:[0, 0, 0, 255]});

         const numDays = Math.floor(timeDiff/DAY_MS);

         chrome.action.setBadgeText({text: numDays + ''});

      }

   }

}


// Once an hour, check if it's been too long

sync();

chrome.alarms.create('check-inactivity', {periodInMinutes: 60});

chrome.alarms.onAlarm.addListener(sync);


And that's it! I figure this may be a common use case for the alarms API, so I'm hoping this post helps anyone looking to implement a similar feature.


No comments: