webext-redux icon indicating copy to clipboard operation
webext-redux copied to clipboard

Manifest V3

Open mikecann opened this issue 4 years ago • 42 comments

Am I correct in thinking that this library will no longer work in Manifest v3 with persistant background pages going away?

Edit:

https://developer.chrome.com/extensions/migrating_to_manifest_v3

Basically background pages are going away being replaced with service workers.

Its not exactly univerally loved as an idea: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-extensions/0Af4aqQcY1Q

mikecann avatar Mar 03 '20 08:03 mikecann

@mikecann Thanks for calling this out. I'll have to look into this.

I have tested using this on a non-persistent background page and it worked fine last time I checked. Mostly because chrome will keep the event page running if it is doing something (like connecting via a chrome port, which this package does on every injected page). Additionally, it saves it's state frequently, so it can handle reloads fairly well.

That said, I haven't explicitly tested this with the new v3 spec. We will need to do that. I'm pretty confident we can get it working without too much issue.

tshaddix avatar Mar 05 '20 17:03 tshaddix

@tshaddix ye I think service workers will randomly disconnect.. Not sure what happens with ports.. Its really annoying TBH

mikecann avatar Mar 05 '20 23:03 mikecann

Alright I'm still working on testing this here: https://github.com/tshaddix/webext-redux-examples/tree/manifest-v3

As of now, it is not currently working. That said, I can't figure out if it's actually an issue with something in webext-redux or another issue with current Canary build. The current developer workflow for service worker based extensions is terrible IMHO.

I've read through quite a few write ups about the new v3 changes and I'm not seeing any reason this library should not work fine with it. The only major thing that I see changing is the recommended frequency of redux store saves to chrome storage.

I'll keep trying to debug this example extension...

tshaddix avatar Mar 09 '20 22:03 tshaddix

Ye, theres not that much love for the service worker background page at the moment.. Thanks for doing some research on this, let us know if you manage to get to the bottom of it. I think theres quite a few extensions relying on this lib :)

mikecann avatar Mar 09 '20 23:03 mikecann

You got it! I'll keep messing with it.

tshaddix avatar Mar 13 '20 14:03 tshaddix

@tshaddix I checked out your branch linked above and tested it in Chrome 84.0.4147.21 dev. Clicks are registered and the counter updates across tabs. Is there something in particular that was broken when you were testing things or has the latest Chrome build fixed things?

christospappas avatar May 29 '20 12:05 christospappas

@christospappas That's great to hear! It sounds like the latest Chrome build fixed things then. I tried the latest build about a month ago and was still running into issues.

I'll double check to make sure my local copy of the branch is good to go and then I guess we'll just continue to keep an eye on it as the Chrome dev builds progress.

Thank you for your help!

tshaddix avatar May 29 '20 14:05 tshaddix

I havent checked the code but does this account for random service worker disconnects?

mikecann avatar May 30 '20 00:05 mikecann

So this package does not offer persistence at this point (maybe it should?), but you can set up a listener on store state changes to persist the store in memory, and then re-instate the store on extension load:

chrome.storage.local.get([
  'state'
], ({state}) => {
  // initiate redux store with saved state or new
  store = Store(state || {});

  // setup webext-redux
  wrapStore(store, {portName: PORT_NAME});

  /**
   * Save the current store state to local storage
   */
  const saveState = () => {
    if (!store) {
      return;
    }

    const state = store.getState();

    chrome.storage.local.set({
      state
    });
  };

  // On new state, persist to local storage (throttle function from lodash)
  const throttledSave = throttle(saveState, 1000, {trailing: true, leading: true});
  store.subscribe(throttledSave);
});

tshaddix avatar Jun 02 '20 15:06 tshaddix

hi can you please tell me, where should we place this code to load the state from chrome.local.storage? I have put it just in background page (I tried this, but it get's executed only once after the extension is reloaded) but I guess we should put it inside an "on extension load" listener? What's exactly that? Thank you

Edit: to make it clear why I am asking is because I have an issue that when I switch to other tab, my redux store get's overwritten with new rootReducer for the new tab.

For example here is a state when tab with id 1371 is active:

{
1371isInjected: false,
1371isPanelOpen: true
}

after switching to tab id 2334:

{
2334isInjected: false,
2334isPanelOpen: true
}

data about tab 1371 is gone. Should I load previous state every time a tab is switched from chrome.local.storage and merge it with current state? Or is there better solution, am I doing something wrong?

EDIT2: so I found out that this disappearing state is happening because of dynamic keys due to tab id. so I am rewriting my store to look like this:

{
    tabs: {
        2334: {
            isInjected: false,
            isPanelOpen: false
        },
        1371: {
            isInjected: true,
            isPanelOpen: true
        }
    }
}

now my store persists when switching tabs.

Alino avatar Jul 06 '20 00:07 Alino

Just a heads up that I've managed to implement this (it took a bit of fiddling though). For anyone struggling I made use of the https://github.com/KELiON/redux-async-initial-state library as redux doesn't play nice out of the box with the promises that are returned from browser.storage.local.get('state'). The usage of the library in my case is something like:

const enhancers = [
  asyncInitialState.middleware(getInitialState),
  ...other enhancers e.g. saga
];

However, in my travels I have concluded that it's worth adding

browser.runtime.onSuspend.addListener(() => {
  saveState();
});

for non-persistent background scripts (which as I understand it is the way the future is looking in v3 as this thread discusses) to ensure the latest state is persisted before the background processes is suspended

anotherstarburst avatar Jul 08 '20 05:07 anotherstarburst

@mikecann - thanks for opening this issue. I'm just starting to look in anger at rewriting my previous Chrome extension to be compliant with the new MV3 spec and I had exactly the same question as I used the previous version of this library to provide a react / redux framework in my popup page and it worked a treat and I was hoping to use the latest version as well.

FWIW - I'm happy to help out with testing or implementing any fixes to make sure that everything still works in a MV3 world 👍

Techdojo avatar Nov 20 '20 15:11 Techdojo

For those who still awaiting this library to work in Manifest V3: that will never happen, at least as long as it uses its current architecture. This architecture, let's call it client-server, assumes that the state of extension is permanently stored in a background script (server) and sending to other components (popup etc) by request. The trouble is that this approach is incompatible with event-based model used in Manifest V3. In this model background scripts do not live/run permanently and may be unloaded at any moment (that can't be known beforehand). So with this library, sooner or later, the state of extension will be lost - it's inevitable unless you save the state in chrome.storage every time it changes and load it from there every time it's needed.

Here the question arises: if we have to regularly save the state in chrome.storage anyway, then why do we need such intermediate as Webext Redux at all? Let's just store the state immediately in chrome.storage!

However, chrome.storage does not work in Redux way. So if you accustomed to thinking in Redux, you would like to somehow make chrome.storage compatible with it. That's why I wrote Reduxed Chrome Storage library. This is the only way to get Redux working in Manifest V3. And at the same time it is a unified way to use Redux in all modern browsers' extensions (as storage API has full cross-browser support).

hindmost avatar Mar 30 '21 22:03 hindmost

this library to work in Manifest V3: that will never happen

@hindmost I disagree.

you save the state in chrome.storage every time it changes and load it from there every time it's needed.

Right, this is the solution. Service Workers are not immediately discarded. Doing so while they're being used would be counterproductive. One should assume that the SW will be open or will be re-opened when receiving a message.

why do we need such intermediate as Webext Redux at all? Let's just store the state immediately in chrome.storage!

chrome.storage does not work in Redux way

You answered yourself.

I don't see why this can't work. I do think that a background-less implementation could be faster, but you'll have to compare a possibly-in-memory call of a background implementation to a likely-not-in-memory implementation of raw storage.get calls. I don't know if the browser always reads from disk in this case.

fregante avatar Apr 24 '21 09:04 fregante

For some reason I haven't receive notification about the above comment. That's why my so late response.

@fregante I don't see why you started talking about implementation performance here (did I ever mention performance?). The issue with this library is not that it's slower or faster than something else. The issue is that it can't guarantee the state to be always up to date without regular and manual backup in chrome.storage. Since Webext Redux doesn't deal with chrome.storage at all, user has to do this backup himself/herself in each place (component) where Webext Redux is used.

And you used some of my quotes out of context. "chrome.storage does not work in Redux way" isn't the end of paragraph. After these words I suggest a solution of this problem ready to use.

hindmost avatar Jul 03 '21 10:07 hindmost

Hi Foks,

We are exploring migrating to V3 in our extension to align with Chrome extension requirements. I follow most of the comments on this issue which were helpful to give me an overall picture of the situation for this library.

Based on the recent posts between @fregante and @hindmost, I am concerned that the library doesn't have any mechanisms for persists the store if the browser decides to stop the service worker. Have you considered providing a persistence layer against chrome.storage as part of the library?

eduardoacskimlinks avatar Jul 07 '21 15:07 eduardoacskimlinks

@tshaddix looks like this is still an unresolved issue and a pretty major one given that multiple browsers are moving from persistent to event based extensions. I'm doing research on how to deal with this in my own chrome extension, and I'm a bit concerned about continuing to use this library. How dedicated is the webext-redux and goguardian team towards implementing a solution? Is goguardian still using this library in its suite of products?

yiziz avatar Jul 31 '21 20:07 yiziz

@tshaddix everything seems to work as long as the initial service working is active. however, once the service worker is terminated or idle, restarting the service worker does not automatically reconnect the selectors. The service worker loads the correct initial state and redux actions can still be sent, but until I refresh the tab, the selectors do not respect a redux state change.

I was able to address this by re-assigning the store variable with a new Store object and then calling ReactDOM.render into the existing/same container. While this does make the selectors work as expected (so far), I worry that there will be unexpected side effects that I haven't encountered yet.

yiziz avatar Aug 01 '21 08:08 yiziz

@yiziz Interesting - okay. I will dive into this a bit more. Thank you for looking into this.

To answer your previous question:

How dedicated is the webext-redux and goguardian team towards implementing a solution? Is goguardian still using this library in its suite of products?

This package is maintained by contributors from the open source community and myself. GoGuardian does not support this package in any sort of official capacity. Unfortunately, my free time has been extremely sparse over the last couple years, but I still have every intention of supporting this package.

tshaddix avatar Aug 17 '21 14:08 tshaddix

@eduardoacskimlinks Great question - in the past I've always just pointed people towards the demo for persisting the store state, but at this time with the new v3 spec, it's probably best to make it an official functionality of the package. I think this will naturally be required to support v3.

tshaddix avatar Aug 17 '21 14:08 tshaddix

Hey folks, I started the migration to manifest V3. I found that articles suggest bundling service workers with Webpack 5 to use the options target: node for reasons like DOM and XHR requests are unavailable in service workers. However, the moment I use this option, packages like redux, among others, doesn't build properly. Have you experienced this issue? How did you solve it?

eduardoacskimlinks avatar Oct 14 '21 09:10 eduardoacskimlinks

Hey folks, I started the migration to manifest V3. I found that articles suggest bundling service workers with Webpack 5 to use the options target: node for reasons like DOM and XHR requests are unavailable in service workers. However, the moment I use this option, packages like redux, among others, doesn't build properly. Have you experienced this issue? How did you solve it?

Service workers don't have access to DOM (https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/#documents) and XMLHttpRequest is deprecated in service workers you need to use Fetch api (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)

ymn avatar Oct 14 '21 09:10 ymn

Hey folks, I started the migration to manifest V3. I found that articles suggest bundling service workers with Webpack 5 to use the options target: node for reasons like DOM and XHR requests are unavailable in service workers. However, the moment I use this option, packages like redux, among others, doesn't build properly. Have you experienced this issue? How did you solve it?

Service workers don't have access to DOM (https://developer.chrome.com/docs/extensions/mv3/migrating_to_service_workers/#documents), and XMLHttpRequest is deprecated in service workers you need to use Fetch API (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)

Hi Ymn, I appreciate your response; Perhaps, my discussion topic was unclear in my first message. I am aware of the situation with service workers regarding DOM and XHR requests ("Deprecated" is an understatement since it's no longer available)

My question is regarding the bundling process using Webpack 5 and the target: node option, which is recommended when building a service worker. This is causing current problems to import libraries like Redux on the JavaScript setup, so I am wondering if any of you during the migration experienced this issue and how you solved it

eduardoacskimlinks avatar Oct 14 '21 11:10 eduardoacskimlinks

Any update on this?

sghsri avatar Feb 09 '22 19:02 sghsri

Any update on this?

switching to https://github.com/hindmost/reduxed-chrome-storage worked out for me

nopol10 avatar Feb 10 '22 00:02 nopol10

Unfortunately, the amount of free time I have available continues to dwindle. Realistically, I won't be able to make any major modifications to this package in the near future. It's not something I'm thrilled about, but I just don't have enough time in the day anymore.

That said, I'm more than happy to review PRs if someone else attempts to migrate this package to V3. Alternatively, as @nopol10 pointed out, @hindmost has built a great package with a focus on event-driven extensions. I'd highly recommend considering it as an alternative.

tshaddix avatar Feb 10 '22 02:02 tshaddix

Any update on this?

switching to https://github.com/hindmost/reduxed-chrome-storage worked out for me

Do you have an example for using reduxed-chrome-storage? I am uncertain this solution will work properly as it's not handling the different changes for the background script for example, what happens when the extension goes idle and goes back? as well the management of the store section is a bit obscured so I am curious if you have use the library mention in a commercial application

eduardoacskimlinks avatar Apr 26 '22 16:04 eduardoacskimlinks

switching to https://github.com/hindmost/reduxed-chrome-storage worked out for me

Do you have an example for using reduxed-chrome-storage? I am uncertain this solution will work properly as it's not handling the different changes for the background script for example, what happens when the extension goes idle and goes back? as well the management of the store section is a bit obscured so I am curious if you have use the library mention in a commercial application

with reduxed-chrome-storage, dispatches from content scripts happen directly in the content scripts and the state is stored in chrome storage so even if the background service worker script is not active, it is ok.

see if this helps: https://github.com/nopol10/nekocap/blob/389303d33af11d0408b04c3a74e7353645146767/src/common/store/store.tsx

https://github.com/nopol10/nekocap/blob/7245452d8aff6cffe4e2d3d06947522d6a799eb3/src/extension/background/index.tsx

nopol10 avatar Apr 28 '22 11:04 nopol10

switching to https://github.com/hindmost/reduxed-chrome-storage worked out for me

Do you have an example for using reduxed-chrome-storage? I am uncertain this solution will work properly as it's not handling the different changes for the background script for example, what happens when the extension goes idle and goes back? as well the management of the store section is a bit obscured so I am curious if you have use the library mention in a commercial application

with reduxed-chrome-storage, dispatches from content scripts happen directly in the content scripts and the state is stored in chrome storage so even if the background service worker script is not active, it is ok.

see if this helps: https://github.com/nopol10/nekocap/blob/389303d33af11d0408b04c3a74e7353645146767/src/common/store/store.tsx

https://github.com/nopol10/nekocap/blob/7245452d8aff6cffe4e2d3d06947522d6a799eb3/src/extension/background/index.tsx

@nopol10 Have you faced the issue where the view stops receiving state updates to re-render after coming back from idle? And how did you solve it using reduxed-chrome-storage?

Flow illustrative:

Before going idle:

  • content script Action --> Background Action --> store (state updated) --> View (new state sent back to all content scripts) After going iddle:
  • content script action --> Background Action --> store (state updated) --x View (no state pass back to the any content script)

eduardoacskimlinks avatar Apr 29 '22 09:04 eduardoacskimlinks

From reduxed-chrome-storage's medium post (https://levelup.gitconnected.com/using-redux-in-event-driven-chrome-extensions-problem-solution-30eed1207a42):

image

Actions do not go through the background script any more. Content script has its own store, background service worker has its own store and all of them access the same state that is synchronized in chrome.storage so you shouldn't encounter the issue you mentioned.

nopol10 avatar Apr 29 '22 09:04 nopol10