Skip to content

Commit

Permalink
fix dropping first message by queueing messages before store is wrapped
Browse files Browse the repository at this point in the history
wrapStore is no longer directly exported, it's constructed by createWrapStore. createWrapStore must be called synchronously when the service worker starts, allowing any message events to be queued and processed later after wrapStore is called.

This avoids dropping dispatches from extension contexts that wake the service worker with a message.
  • Loading branch information
SidneyNemzer committed Jun 12, 2024
1 parent 2ff04a0 commit bf485ad
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 155 deletions.
69 changes: 38 additions & 31 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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;
Expand All @@ -9,32 +9,32 @@ export class Store<S = any, A extends redux.Action = redux.AnyAction> {
* @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 `''`)
*/
constructor(options?: {
portName?: string,
state?: any,
extensionId?: string,
serializer?: Function,
deserializer?: Function,
patchStrategy?: PatchStrategy
portName?: string;
state?: any;
extensionId?: string;
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 +49,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 +64,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 +77,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 +112,7 @@ export function applyMiddleware(
* Function to remove listener added by `Store.subscribe()`.
*/
export interface Unsubscribe {
(): void
(): void;
}

/**
Expand All @@ -122,14 +129,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;
};
10 changes: 5 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Store from './store/Store';
import applyMiddleware from './store/applyMiddleware';
import wrapStore from './wrap-store/wrapStore';
import alias from './alias/alias';
import Store from "./store/Store";
import applyMiddleware from "./store/applyMiddleware";
import createWrapStore from "./wrap-store/wrapStore";
import alias from "./alias/alias";

export {Store, applyMiddleware, wrapStore, alias};
export { Store, applyMiddleware, createWrapStore, alias };
15 changes: 15 additions & 0 deletions src/listener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const createDeferredListener = () => {
let resolve = () => {};
const fnPromise = new Promise((resolve_) => (resolve = resolve_));

const listener = (message, sender, sendResponse) => {
fnPromise.then((fn) => {
fn(message, sender, sendResponse);
});

// Allow response to be async
return true;
};

return { setListener: resolve, listener };
};
Loading

0 comments on commit bf485ad

Please sign in to comment.