Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manifest V3 #297

Merged
merged 18 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 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 @@ -296,8 +298,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 +368,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 +399,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 +428,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 +440,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
70 changes: 38 additions & 32 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,39 @@
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 options An object of form {portName, state}, where `portName` is a required string and defines the name of the port for state transition changes and `state` is the initial state of this store (default `{}`)
*/
constructor(options?: {
portName?: string,
state?: any,
extensionId?: string,
serializer?: Function,
deserializer?: Function,
patchStrategy?: PatchStrategy
portName?: 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 +48,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 +63,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 +76,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;
portName?: 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 +111,7 @@ export function applyMiddleware(
* Function to remove listener added by `Store.subscribe()`.
*/
export interface Unsubscribe {
(): void
(): void;
}

/**
Expand All @@ -122,14 +128,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
Loading