Skip to content

Commit

Permalink
Merge pull request #297 from tshaddix/mv3
Browse files Browse the repository at this point in the history
Manifest V3
  • Loading branch information
SidneyNemzer authored Jul 10, 2024
2 parents 091f3bc + f3f75bb commit 11fc660
Show file tree
Hide file tree
Showing 13 changed files with 4,754 additions and 2,647 deletions.
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,18 @@ store.ready().then(() => {
```js
// background.js

import {wrapStore} from 'webext-redux';
import {createWrapStore} from 'webext-redux';

const store; // a normal Redux store

const wrapStore = createWrapStore()
wrapStore(store);
```

That's it! The dispatches called from UI component will find their way to the background page no problem. The new state from your background page will make sure to find its way back to the UI components.


> [!NOTE]
> `createWrapStore()` sets up listeners for browser messaging. In MV3, it must be registered synchronously when the service worker starts. This ensures `dispatch()` calls that wake the service worker are received. Messages are queued internally until `wrapStore()` is called, and the events can be dispatched to the store.

### 3. Optional: Apply any redux middleware to your *Proxy Store* with `applyMiddleware()`
Expand Down Expand Up @@ -110,7 +112,7 @@ Sometimes you'll want to make sure the logic of your action creators happen in t
// background.js

import { applyMiddleware, createStore } from 'redux';
import { alias, wrapStore } from 'webext-redux';
import { alias } from 'webext-redux';

const aliases = {
// this key is the name of the action to proxy, the value is the action
Expand Down Expand Up @@ -258,10 +260,6 @@ On the other hand, this piece of code is safe:

If you spot any more surprises that are worth watching out for, make sure to let us know!

## Security

`webext-redux` supports `onMessageExternal` which is fired when a message is sent from another extension, app, or website. By default, if `externally_connectable` is not declared in your extension's manifest, all extensions or apps will be able to send messages to your extension, but no websites will be able to. You can follow [this](https://developer.chrome.com/extensions/manifest/externally_connectable) to address your needs appropriately.

## Custom Serialization

You may wish to implement custom serialization and deserialization logic for communication between the background store and your proxy store(s). Web Extension's message passing (which is used to implement this library) automatically serializes messages when they are sent and deserializes them when they are received. In the case that you have non-JSON-ifiable information in your Redux state, like a circular reference or a `Date` object, you will lose information between the background store and the proxy store(s). To manage this, both `wrapStore` and `Store` accept `serializer` and `deserializer` options. These should be functions that take a single parameter, the payload of a message, and return a serialized and deserialized form, respectively. The `serializer` function will be called every time a message is sent, and the `deserializer` function will be called every time a message is received. Note that, in addition to state updates, action creators being passed from your content script(s) to your background page will be serialized and deserialized as well.
Expand Down Expand Up @@ -296,8 +294,9 @@ As you can see, Web Extension's message passing has caused your date to disappea
```js
// background.js

import {wrapStore} from 'webext-redux';
import {createWrapStore} from 'webext-redux';

const wrapStore = createWrapStore();
const store; // a normal Redux store

wrapStore(store, {
Expand Down Expand Up @@ -365,9 +364,10 @@ If any of the individual keys under `state.items` is updated, `state.items` will
```js
// background.js

import {wrapStore} from 'webext-redux';
import {createWrapStore} from 'webext-redux';
import deepDiff from 'webext-redux/lib/strategies/deepDiff/diff';

const wrapStore = createWrapStore();
const store; // a normal Redux store

wrapStore(store, {
Expand Down Expand Up @@ -395,9 +395,10 @@ Note that the deep diffing strategy currently diffs arrays shallowly, and patche
```js
// background.js

import {wrapStore} from 'webext-redux';
import {createWrapStore} from 'webext-redux';
import makeDiff from 'webext-redux/lib/strategies/deepDiff/makeDiff';

const wrapStore = createWrapStore();
const store; // a normal Redux store

const shouldContinue = (oldState, newState, context) => {
Expand All @@ -423,7 +424,7 @@ A `shouldContinue` function of the form `(oldObj, newObj, context) => context.le

### Custom `diffStrategy` and `patchStrategy` functions

You can also provide your own diffing and patching strategies, using the `diffStrategy` parameter in `wrapStore` and the `patchStrategy` parameter in `Store`, repsectively. A diffing strategy should be a function that takes two arguments - the old state and the new state - and returns a patch, which can be of any form. A patch strategy is a function that takes two arguments - the old state and a patch - and returns the new state.
You can also provide your own diffing and patching strategies, using the `diffStrategy` parameter in `wrapStore` and the `patchStrategy` parameter in `Store`, respectively. A diffing strategy should be a function that takes two arguments - the old state and the new state - and returns a patch, which can be of any form. A patch strategy is a function that takes two arguments - the old state and a patch - and returns the new state.
When using a custom diffing and patching strategy, you are responsible for making sure that they function as expected; that is, that `patchStrategy(oldState, diffStrategy(oldState, newState))` is equal to `newState`.

Aside from being able to fine-tune `webext-redux`'s performance, custom diffing and patching strategies allow you to use `webext-redux` with Redux stores whose states are not vanilla Javascript objects. For example, you could implement diffing and patching strategies - along with corresponding custom serialization and deserialization functions - that allow you to handle [Immutable.js](https://github.com/facebook/immutable-js) collections.
Expand All @@ -435,7 +436,7 @@ Aside from being able to fine-tune `webext-redux`'s performance, custom diffing
* [Advanced Usage](https://github.com/tshaddix/webext-redux/wiki/Advanced-Usage)
* [API](https://github.com/tshaddix/webext-redux/wiki/API)
* [Store](https://github.com/tshaddix/webext-redux/wiki/Store)
* [wrapStore](https://github.com/tshaddix/webext-redux/wiki/wrapStore)
* [createWrapStore](https://github.com/tshaddix/webext-redux/wiki/createWrapStore)
* [alias](https://github.com/tshaddix/webext-redux/wiki/alias)

## Who's using this?
Expand Down
82 changes: 49 additions & 33 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
import * as redux from 'redux';
import * as redux from "redux";

export type DiffStrategy = (oldObj: any, newObj: any) => any;
export type PatchStrategy = (oldObj: any, patch: any) => any;

export class Store<S = any, A extends redux.Action = redux.AnyAction> {
/**
/**
* Creates a new Proxy store
* @param options An object of form {portName, state, extensionId}, where `portName` is a required string and defines the name of the port for state transition changes, `state` is the initial state of this store (default `{}`) `extensionId` is the extension id as defined by chrome when extension is loaded (default `''`)
* @param {object} options
* @param {string} options.channelName The name of the channel for this store.
* @param {object} options.state The initial state of the store (default
* `{}`).
* @param {function} options.serializer A function to serialize outgoing
* messages (default is passthrough).
* @param {function} options.deserializer A function to deserialize incoming
* messages (default is passthrough).
* @param {function} options.patchStrategy A function to patch the state with
* incoming messages. Use one of the included patching strategies or a custom
* patching function. (default is shallow diff).
*/
constructor(options?: {
portName?: string,
state?: any,
extensionId?: string,
serializer?: Function,
deserializer?: Function,
patchStrategy?: PatchStrategy
channelName?: string;
state?: any;
serializer?: Function;
deserializer?: Function;
patchStrategy?: PatchStrategy;
});

/**
* Returns a promise that resolves when the store is ready.
* @return promise A promise that resolves when the store has established a connection with the background page.
*/
*/
ready(): Promise<void>;

/**
* Returns a promise that resolves when the store is ready.
* @param callback An callback that will fire when the store is ready.
* @return promise A promise that resolves when the store has established a connection with the background page.
*/
*/
ready<S>(cb: () => S): Promise<S>;

/**
* Subscribes a listener function for all state changes
* @param listener A listener function to be called when store state changes
* @return An unsubscribe function which can be called to remove the listener from state updates
*/
* Subscribes a listener function for all state changes
* @param listener A listener function to be called when store state changes
* @return An unsubscribe function which can be called to remove the listener from state updates
*/
subscribe(listener: () => void): () => void;

/**
Expand All @@ -49,7 +58,6 @@ export class Store<S = any, A extends redux.Action = redux.AnyAction> {
*/
patchState(difference: Array<any>): void;


/**
* Stub function to stay consistent with Redux Store API. No-op.
* @param nextReducer The reducer for the store to use instead.
Expand All @@ -65,7 +73,7 @@ export class Store<S = any, A extends redux.Action = redux.AnyAction> {
/**
* Dispatch an action to the background using messaging passing
* @param data The action data to dispatch
*
*
* Note: Although the return type is specified as the action, react-chrome-redux will
* wrap the result in a responsePromise that will resolve/reject based on the
* action response from the background page
Expand All @@ -78,22 +86,30 @@ export class Store<S = any, A extends redux.Action = redux.AnyAction> {
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
[Symbol.observable](): Observable<S>
[Symbol.observable](): Observable<S>;
}

export function wrapStore<S, A extends redux.Action = redux.AnyAction>(
type WrapStore<S, A extends redux.Action = redux.AnyAction> = (
store: redux.Store<S, A>,
configuration?: {
portName?: string,
dispatchResponder?(dispatchResult: any, send: (response: any) => void): void,
serializer?: Function,
deserializer?: Function,
diffStrategy?: DiffStrategy
},
): void;
channelName?: string;
dispatchResponder?(
dispatchResult: any,
send: (response: any) => void
): void;
serializer?: Function;
deserializer?: Function;
diffStrategy?: DiffStrategy;
}
) => void;

export function createWrapStore<
S,
A extends redux.Action = redux.AnyAction
>(): WrapStore<S, A>;

export function alias(aliases: {
[key: string]: (action: any) => any
[key: string]: (action: any) => any;
}): redux.Middleware;

export function applyMiddleware(
Expand All @@ -105,7 +121,7 @@ export function applyMiddleware(
* Function to remove listener added by `Store.subscribe()`.
*/
export interface Unsubscribe {
(): void
(): void;
}

/**
Expand All @@ -122,14 +138,14 @@ export type Observable<T> = {
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: (observer: Observer<T>) => { unsubscribe: Unsubscribe }
[Symbol.observable](): Observable<T>
}
subscribe: (observer: Observer<T>) => { unsubscribe: Unsubscribe };
[Symbol.observable](): Observable<T>;
};

/**
* An Observer is used to receive data from an Observable, and is supplied as
* an argument to subscribe.
*/
export type Observer<T> = {
next?(value: T): void
}
next?(value: T): void;
};
Loading

0 comments on commit 11fc660

Please sign in to comment.