tshaddix / webext-redux Goto Github PK
View Code? Open in Web Editor NEWA set of utilities for building Redux applications in Web Extensions.
License: MIT License
A set of utilities for building Redux applications in Web Extensions.
License: MIT License
Hi,
I've setup an action to login a user after the login form was submitted. I've created a matching alias to have this async action of checking credentials happen in the background page. However, I'm using react-router and would like to "redirect" on a successful login. This happens by doing:
import { browserHistory } from 'react-router';
browserHistory.push('<new-path-here>');
As you might expect, having to setup the routing in the popup page (together with the browserHistory) we can't just access the same browserHistory object in the background page. Meaning that one cannot simply redirect a user after a successful login. See code example:
// popup
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { Store } from 'react-chrome-redux';
import { Router, browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import getRoutes from './routes';
const store = new Store({
portName: 'xxx'
});
const unsubscribe = store.subscribe(() => {
unsubscribe();
const history = syncHistoryWithStore(browserHistory, store);
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
{getRoutes(store)}
</Router>
</Provider>,
document.querySelector('#app')
);
});
// background
import firebase from 'firebase';
import { browserHistory } from 'react-router';
// This is an aliased action executing from the background
// with redux-thunk
const loginUser = () => {
return (dispatch) => {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(user => {
dispatch({
type: LOGIN_SUCCESS,
payload: user
});
// Redirect here
// This won't work, because the browserHistory obviously differs
// from the one in the popup page.
browserHistory.push('/dashboard');
});
};
};
I hope this is kind of clear? My question however is: is there a way this issue is supported by react-chrome-redux and am I just not seeing it? Or should I use messaging to send a message to my popup page saying: "Hey, all is fine, you can redirect now using the correct browserHistory object"?
Note: don't mind missing imports and such, just assume everything works. I just want to figure out, how I can get back to the browserHistory in a neat way.
Thanks in advance!
Update the README to include Sender information example merged in #41 . We should also bump the version.
Hi,
as title, could I combine applyMiddleware, createStore with the Store from this module?
Thanks.
Hi,
I originally using redux-thunk for async action, for example:
// actions/index.js
function requestData() {
return (dispatch, getState) => {
dispatch({
type: 'REQUEST_DATA',
...
});
superAgent
.get(...)
.end((err, res) => {
if (err) {
dispatch({
type: 'REQUEST_DATA_FAIL',
...
});
} else {
dispatch({
type: 'REQUEST_DATA_SUCCESS',
...
});
}
});
}
How can I change to use alias & dispatchResponder ?
I think I need to wrap superAgent to an alias action for REQUEST_DATA, but I don't know how to modify my action and how to use reduxPromiseResponder when remote api response..
Thanks.
I used this example to setup my extension using react-chrome-redux: https://github.com/tshaddix/react-chrome-redux-examples
Currently in the countReducer, it has:
const initialState = 0;
Everything works fine.
But if I update that to
const initialState = { counter: 0 };
Then update mapdispatchtoProps from
count: state.count
to
count: state.count.counter
I get an error that says can't find property counter of undefined.
If I setup the initial state as values (not objects), everything works fine.
What am I not understanding?
Thanks.
I might be using a bad practice, but I feel like there is a need for making dispatch
and getState
available in alias
.
Reason being if I want to make remote API calls in the background page, and then dispatch an action when the response comes back, it seems I would be out of luck. I am thinking of using alias
being kind of like using a thunk
, where I can have an impure function that also dispatches other actions, and have access to the state.
I see that the store
is already available in the closure. Any reason you didn't make that available?
Hey guys.
So what is the goal of this project? Given that there is Chrome Storage, which can be used to store data there, what is real benefit of using single Redux Store within background Page in comparison to multiple ones inside Views?
It seems to me that this is somewhat overhead. Why don't just treat each View as separate application with its complete structure?
I'm pretty newbie in extension development world, thus I might be missing something obvious in this regard.
First off, let me say I love this library. I am already using it to synchronize a store globally between all tabs and it works great!
However I have some data that is in a per-tab context, that needs to be accessible to the browserAction. I currently have some rudimentary message passing using callbacks where the browserAction does something like the following to "request" the state:
(note that I'm using Mozilla's webextension-polyfill, hence the promise-based browser
functions)
async function requestData() {
const tabQuery = {
active: true,
currentWindow: true
};
const tabs = await browser.tabs.query(tabQuery);
const message = {
verb: 'tabData_get',
body: { }
};
const response = await browser.tabs.sendMessage(tabs[0].id, message)
await this.receiveTabData(body);
}
The content script then responds directly to that message with the data.
I would love to be able to use the same pattern with a synchronized Redux store here. Obviously I don't expect anyone to write this for me, but was hoping for some insight as to whether or not you think this could work, and if I got it working I could submit a PR.
Here's my general thinking for a solution:
chromex.dispatch
to the tab, and chromex.state
to the browserActionImmutable.js does not seem to work with this setup. I am always getting normal objects in my selectors instead of immutable states. More chances are that I am doing something wrong, but just wanted to confirm. Please close this issue if it's compatible.
I edited the wiki page under 'Advanced Usage' to clarify that the payload of the returned object is the data that actually gets resolved by the promiseResponder in the content script.
Not sure if I stated it in the clearest way etc so it might need further clarification, but it wasn't very clear to me why returning just the object I wanted wasn't resolving the promise correctly (it was coming back as undefined)
We've tried to implement this part of the docs (advances usage)
import {alias} from 'react-chrome-redux';
import aliases from '../aliases';
const middleware = [
alias(aliases),
// whatever middleware you want (like redux-thunk)
];
// createStoreWithMiddleware... you know the drill
We've had some trouble with our promise-middleware not firing after using the aliases, and we've found out that the aliases middleware needs to be the first in the middleware chain.
I think this needs to be more clear in the docs.
Do you guys agree?
Thanks for this project & the video presentation you did. I'm new to chrome extensions, so your level of documentation/explanation is great--def. best I've found on a project like this.
But on that note I was wondering: can you talk a bit about your motivation for creating this project vice using one of the small # of similar projects? e.g. crossbuilder w/ 'cross-messaging' ?
Asking b/c as a newbie, it's tough to differentiate & wanna make sure i understand which tools fit which projects.
We had a great discussion going on in #60 regarding first-class support for WebExtensions and other browsers.
@vhmth @AgrimPrasad @paulius005 Let's continue the discussion.
Hello there, I'm trying to pass a function to payload and retrieve it in the middleware. But, it seems like the function that I pass in gets stripped off. I don't see anything in the src that strips functions off the payload. Any ideas how I could solve this? Thanks!
Hi @tshaddix , great work!
I was wondering how to use chrome.runtime.sendMessage or similar methods to make scripts communicate between each other.
Or is chrome.local.storage a better solution?
@tshaddix right now our biggest point of confusion for most folks is the separation of concerns around what an alias and an action is. This introduces a new paradigm where you have to mentally remember that aliases run in one environment and actions in another. I think it largely confuses newbie Chrome extension devs as well.
My proposal is that we figure out a way to ensure that all actions get run/dispatched on the background. I'm not even sure if there's a clean way to ensure this, but I think opening the conversation is worthwhile.
We're testing the tool and after several attempts it looks like the async actions don't work properly.
The best way we found to create a bug report was to publish a very simple example:
https://github.com/ihenvyr/react-chrome-extension
The synchronous action updates the store in the background page, but the asynchronous one doesn't...
If I have 2 different stores running in the background page, a dispatch to one of them will be handled by all stores - the reducers will run, the state will flow out to all connected ports, and proxy stores will fire registered subscribers.
I propose that dispatch messages include a port
property, and when receiving them it will only be processed if request.port === portName
.
I think we're at a point to move towards a "stable" (1.X.Y) release of react-chrome-redux. We are currently "unstable" (0.X.Y) in terms of SemVer. This issue should be used to discuss any remaining public API changes which might be necessary before moving to 1.0.0.
Missed #31
I noticed that for some actions that get dispatched via mapDispatchToProps
, I'm getting the following error:
TypeError: Cannot read property 'payload' of undefined
This error pops up here. I'm opening this in case anyone could possibly think what the issue may be, but I'm investigating.
Note: I'm using redux-thunk
and frequently dispatch functions instead of action values with a type
, but that shouldn't be an issue. I just thought I'd leave that note here in case it may be useful to debug.
An error should probably be thrown for stupid people like myself who are tempted to specify port
instead of portName
in their UI components when creating a store. :-)
What's up Tyler. This issue may be due to my limited understanding exactly how react-redux
should work, but here's what's happening:
This is the proper behavior for our extension. When I do this:
The UI is not removed and in fact a nested component that shouldn't be getting rendered does and its mapStateToProps
function is getting called (which is throwing an error). I made a recording showcasing the bug with our tool:
https://www.opentest.co/share/d07eab70645c11e69680a3b55516ed77
Not sure what could be the cause, but I'm game to look into this today since this is kinda a high priority for us to fix.
Hello, I'm trying to apply redux-thunk to react-chrome-redux but I'm getting these errors when trying to dispatch an 'asynchronous' action:
Error from event.js (background process)
When I step through after the action gets invoked, action is undefined in createStore:
I've been trying to debug this for a while, any help would be nice!
// Code inside React Component to test
componentDidMount() {
const { actions } = this.props;
actions.asyncCount();
}
// actions.js
export function asyncCount () {
return function (dispatch) {
setTimeout(function() {
dispatch({type: "ADD_COUNT"})
}, 1000)
}
}
// event.js
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import {wrapStore} from 'react-chrome-redux';
const store = createStore(rootReducer, applyMiddleware(thunk));
wrapStore(store, {
portName: 'DRAG_NOTE'
});
Please add that to the Limitations section of the README.
For example redux-promise-middleware
and redux-api-middleware v1
will not work because actions they produce are not serializable. redux-api-middleware v2 beta
has String key instead of Symbol making it compatible with react-chrome-redux
.
I'd like us to consider the possible issues that could arise with supporting onMessageExternal
. We currently don't do any sort of check on the port.sender
property, meaning any other extension or website script could send messages straight to the background store.
What are the possible implications of this? Should we include something in the package to run validation on an external connection? Should we even support external messaging?
I feel like the support for external messaging was a bit premature, honestly (my bad). I feel like we could have solved #47 by simply suggesting listeners in the background page that push the messages to store.dispatch
after the sender has been checked.
Maybe we can solve this by adding an option of senderIds
which is an array of allowed external sender ids. We could also have an allowExternal
boolean which sets up external listeners.
Hello there, I tried using this package with Apollo Client but it's not working. Apollo Client was able to fetch results from graphql server but couldn't dispatch actions to update the store with the fetched result. I have been investigating this for a few hours now but still couldn't find the problem. Any ideas on what the problem could be? Thanks!
Edit:
I was able to dispatch actions from content scripts. So, by right, Apollo Client should be able to do the same.
This package originally included react
in the package name because it provided utilities for working with react components.
That was very short lived and this package no longer has anything to do with react
. This was brought up in #11 .
It would be pretty straight-forward to drop react
from the package name:
chrome-redux
chrome-redux
react-chrome-redux
npm package pointing at chrome-redux
for any version over 1.3.1
What do you think @vhmth ?
It seems that the library is completely free from being dependent on react. It can be used with say angular2+redux.
Any reason why it was named so?
My vote is for eslint since that tends to be the winner in most big projects (including internally at Loom). I can take the lead here on coming up with a boilerplate config and then pool for votes on different choices. Once we come to a consensus, I'll es6ify everything and add the linting to the testing pipeline.
I'm using react-chrome-redux to develop a devtools panel extension. It appears that the _sender is not set on actions that are sent from this panel.
Have I missed something?
Hey @tshaddix
Really impressed with this project, as I see some huge potential in the world of 'web-app augmentations'.
I'm trying to make use of react-chrome-redux
through a script I've injected into a webpage from a chrome extension. I'm using https://github.com/KartikTalwar/gmail-chrome-extension-boilerplate/blob/master/main.js
to get ahold of some gmail controls, and in order to do so, you have to inject your .js in to the page's header. Running plain React works great this way, but I also want a common redux store in the background. Attempting a similar workflow to your clicker-key
example, is when I run into a snag:
chrome.runtime.connect() called from a webpage must specify an Extension ID (string) for its first argument
It seems communicating to an extension is possible, and it totally stands to reason that you need to tell chrome which one... I'm just not really sure how to do that. Is this within the scope of the project/Can you point me in the right direction?
I have a minimal project running where there are a couple of reducers with default values running in the background. I use wrapStore
immediately in the background script and immediately set up a proxy store via the Store
constructor in my popup window. I noticed that this leads to an empty object state in one of my container components (when mapeStateToProps
is called on it). If I set a 100ms
timeout before rendering my <PopupApp>
surrounded by <Provider>
with the proxy store set to it, everything works.
Seems like there may be a race condition? Should I be listening for an event so I know the proxy store has established its connection correctly? Seems like onConnect is a good candidate and the proxy store object should emit some sort of event when the connection has been made and the initial state is filled in?
Hi ! Still didn't have time to try this, but since Firefox now support WebExtension could this be used as it is in as a firefox extension ?
Browsers seem to be standardizing their extensions API with WebExtensions
Does react-chrome-redux work with WebExtensions out of the box or will some modifications have to be made for this to work?
Hi
My scenario is that I am tracking a context menu click on selected text. I need to inform only the current tab to perform subsequent action.
I can use https://github.com/tshaddix/react-chrome-redux#4-optional-retrieve-information-about-the-initiator-of-the-action but this broadcasts the tab information to all tabs. Is there some way in react-chrome-redux to compare this or do I need to track the current tab using
chrome.tabs.onUpdated.addListener
Thanks
RV
Not sure if this is an issue, a gotcha, the fact that I'm using the library wrongly or just the way react works (still new to this) - but I've noticed that when sending state from my event page to either the popup or content script that on the first call to mapStateToProps() that the state object is empty so my initial render function often get's "can't access ... of undefined" errors.
I got round the issue by having mapStateToProps return suitable defaults if the expected fields were missing.
Just thought I'd mention it.
AND...
Then I found the same point mentioned in the closed issues (#52) - #52 and the fix mentioned there also worked for me.
Feel free to close this issue if needs be :)
Hi,
have you tried using Routing along with this?
And if so, did you wrote your own Router or used existing one (like react-router, redux-router, reacr-router-redux, etc.).
I'm looking mostly for authorization solution.
Best,
Martin
I have a simple reducer as following:
import * as ActionTypes from '../../shared/ActionTypes'
const initialState = {
authorized : 0
}
export default (state = initialState, action) => {
switch (action.type) {
case ActionTypes.LOGIN:
return {
authorized : 1
}
case ActionTypes.LOGOUT:
return {
authorized : 0
}
default:
return state;
}
};
When I try to access authorized from my popup, it throws an error: authorized is undefined. However, if I changed the initial state to const initialState = 0
, it works.
This does not simply work: <h1>{this.props.auth.authorized}</h1>
.
If I remove this line and try to print this.props.auth.authorized inside render(), I see that it is undefined first, then 0.
I guess the state is not initialized properly?
Full code : https://github.com/onurhb/Test
Hello!
I'm a former middle school teacher who recently got interested in programming, and I'm currently trying to build a chrome extension, using react-redux, as one of my first projects. I stumbled across react-chrome-redux, and it was exactly what I was looking for!
Unfortunately, I've unable to correctly set it up. I watched the live-code demo and read through all to see what I was missing, but I haven't been able to figure it out.
I tried to first just replicate the clicker example from the video, so my UI component and background.js basically looks exactly the same as the demo:
import React from 'react';
import {render} from 'react-dom';
import App from './lib/components/app';
import {Store} from 'react-chrome-redux';
import {Provider} from 'react-redux';
const proxyStore = new Store({
portName: 'example'
});
document.addEventListener('DOMContentLoaded', () => {
render(
<Provider store={proxyStore}><App /></Provider>
, document.getElementById('root'));
});
import React, {Component} from 'react';
import {connect} from 'react-redux';
class App extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
document.addEventListener('click', () => {
this.props.dispatch({
type: 'ADD_COUNT'
});
});
}
render() {
return (
<div>
Click Count: {this.props.count}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
count: state.count
};
};
export default connect(mapStateToProps)(App);
For some reason, however, the UI component does not seem to actually be receiving the correct props, and when I do click, this error message pops up:
This is a portion of my manifest.json:
"version": "1.0",
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
Please let me me know if this wasn't the correct place to ask for this type of help, or if any more specific code needed to be provided.
Any help would be greatly appreciated. Thanks so much!
The only lodash
function used is extend
, which is an alias of assignIn
. We can just install that module by using this dependency: https://www.npmjs.com/package/lodash.assignin
Something that isn't immediately obvious from the README is that the proxy Store()
by default, will initialise the state
as an empty object. This causes issues with react-redux
if you are accessing nested properties in the state.
Trawling through the code, however, https://github.com/tshaddix/react-chrome-redux/blob/b29d0e9b352bea1ac999415a73f0521eb63327f3/src/store/Store.js#L9 shows that there is an option to initialise a default state before this proxy Store receives state information from the wrapped Store. This is practical for small projects, and should be added to the README.
For larger, more complex initial states though, it may be more practical to wait until the proxy Store receives its initial state. See example below:
const unsubscribe = store.subscribe(() => {
unsubscribe();
render(
<Provider store={store}>
<App/>
</Provider>
, document.getElementById('app'));
});
This example ensures that the App is only rendered upon receiving the initial state from the wrapped store. I think this would also be worth adding to the README.
As of now, playing few days with this library I've found it a bit tricky to use.
1. Let's assume I want to inject a button somewhere on the page:
<nav class='menu'>
<a href="?">First</a>
<a href="?">Second</a>
<a href="?">Third</a>
</nav>
<a href="?">Fourth</a>
because of the way React works, it's not possible to simply render my own component inside nav.menu
because it's going to replace everything inside. So what I would have to do is to wrap my component in a new DOM element:
render(<MenuItem />, document.getElementById('item-anchor'))
<nav class='menu'>
<a href="?">First</a>
<a href="?">Second</a>
<a href="?">Third</a>
<span id="item-anchor">
<a href="?">Fourth</a>
</span>
</nav>
2. How to render multiple instances of the same component with some data from an API?
<ul>
<li>
<span>Text</text> // the text of this item should be replaced with some data from ajax
</li>
<li>
<span>Text</text> // the text of this item should be replaced with some data from ajax
</li>
<li>
<span>Text</text> // the text of this item should be replaced with some data from ajax
</li>
</ul>
let's say I create a react component for this:
// TextComponent
...
render () {
return (
<span>{this.props.text}</span>
)
}
how to actually render my above component multiple times and by passing in the text
prop returned from the API?
I have the index.js
file that does
render(<TextComponent text={this.store.response[i]}/>, );
I can't iterate in the component because the items to be rendered are not siblings, nor I can iterate in index.js
and call render()
multiple times and passing in the text from an array.
In my opinion it works OK for apps an popups, because you are in full control of the DOM you create and therefore you can encapsulate everything nicely. However, with injecting content into the page, where you have to inject multiple elements here and there, it makes it too difficult to handle in my opinion, but I hope I'm wrong on this, so please ... any thoughts would be much appreciated.
Love the concept, but I was reading in the Chrome extension docs that persistent background pages are deprecated in favor of event pages ("persistent": false
). Under Best practices when using event pages it says:
- If you need to keep runtime state in memory throughout a browser session, use the storage API or IndexedDB. Since the event page does not stay loaded for long, you can no longer rely on global variables for runtime state.
Just wondering what your thoughts are since I believe react-chrome-redux keeps the state in background. Would you still recommend using react-chrome-redux on a new project right now? Or do you think it would be somewhat easy (or even possible) to change it to a non-persistent event page that kept state in local storage?
Hi All,
Apologies if this is a dumb question as I'm new to Google App/Extension development. I'm trying to create a kiosk application to run on a Chromebit. Can react-chrome-redux be used for apps rather extensions? With my limited knowledge there appears to be some differences between the two. My application currently is a simple React Redux app that uses Redux Thunk to fetch data from a rest api.
If this is possible I'd really appreciate some pointers.
Many thanks in advance.
Hey guys,
thanks for this amazing job of react-chrome-redux.
I have an issue by the way to initiate value to my store. The original way to do that is by the createstore method. Do i have the same possibity by new Store ?
Hi,
So we have a store that's totally non-immutable. We are using parse-server, so our store actually contains many parse object, which are essentially an object with function that cannot be serialize..
As my understanding of this module, it works by sending the store update with json messages. Upon sending these messages I get some errors which (I think) are a result of trying to form a JSON object from these objects which are not meant for that.
My question is basically if anyone has encountered such behaviour, or if you are aware of a workaround.
I cannot afford to change my store to be immutable, as this will require too much unwanted changes with the entire logic of the original web app - which we wanted to reuse its components.
Another option would be to fork this repo and change the messaging logic, but I'm not sure if even then it would be possible.
Would love to hear your insights about this one!
Cheers,
IY
I'm checking this library as architecture option for our chrome extension. Conceptually, your work looks fantastic.
I just wonder if it provides adequate performance?
If I get it right, given an background page and a content script, both communicate using chrome's port.postMessage. What happens is that on each state change, the whole store's state being transferred from background to content script.
Here's excerpt from wrapStore.js that shows the (possible) performance bottleneck:
const sendState = () => {
port.postMessage({
type: STATE_TYPE,
payload: store.getState()
});
};
AFAIK port.postMessage serializes data it sends. So as state grows and as number of small state manipulations is large, the performance hit of serialization should be noticeable.
Another and even bigger issue is that each time state changes, it will be fully re-created in content script, including parts of the state that weren't modified. This will cause re-render of all components. Normally, not modified parts will be kept in same place in memory, so only changed parts of the state will re-render.
So the question is: do you experience any performance issues in extension that uses react-chrome-redux?
If you do, maybe using some diff patching algorithm like jsondiffpatch, and passing over port only the change delta can in theory improve the performance.
What do you think?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.