Add action that informs Dashboard that AppBridge is ready (#129)
* Add action that informs Dashboard that AppBridge is ready * Allow AppBridge to disable automated notifyReady event * Remove unused strategy field from RoutePropagator * Apply suggestions from code review Co-authored-by: Dawid <tarasiukdawid@gmail.com> Co-authored-by: Dawid <tarasiukdawid@gmail.com>
This commit is contained in:
parent
b39081ed9a
commit
95eac760ca
5 changed files with 98 additions and 27 deletions
|
@ -19,6 +19,7 @@ type AppBridgeOptions = {
|
|||
targetDomain?: string;
|
||||
saleorApiUrl?: string;
|
||||
initialLocale?: LocaleCode;
|
||||
autoNotifyReady?: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -154,10 +155,12 @@ handleRedirect();
|
|||
### Available actions
|
||||
|
||||
| Action | Arguments | Description |
|
||||
| :------------- | :--------------------------------------------------------------- | :---------- |
|
||||
| :-------------- | :--------------------------------------------------------------- | :--------------------------------------- |
|
||||
| `Redirect` | `to` (string) - relative (inside Dashboard) or absolute URL path | |
|
||||
| | `newContext` (boolean) - should open in a new browsing context | |
|
||||
| `Notification` | `status` (`info` / `success` / `warning` / `error` / undefined) | |
|
||||
| | `title` (string / undefined) - title of the notification | |
|
||||
| | `text` (string / undefined) - content of the notification | |
|
||||
| | `apiMessage` (string / undefined) - error log from api | |
|
||||
| `NotifyReady` | | Inform Dashboard that AppBridge is ready |
|
||||
| `UpdateRouting` | `newRoute` - current path of App to be set in URL | |
|
||||
|
|
|
@ -4,9 +4,22 @@ import { Values } from "./helpers";
|
|||
|
||||
// Using constants over Enums, more info: https://fettblog.eu/tidy-typescript-avoid-enums/
|
||||
export const ActionType = {
|
||||
/**
|
||||
* Ask Dashboard to redirect - either internal or external route
|
||||
*/
|
||||
redirect: "redirect",
|
||||
/**
|
||||
* Ask Dashboard to send a notification toast
|
||||
*/
|
||||
notification: "notification",
|
||||
/**
|
||||
* Ask Dashboard to update deep URL to preserve app route after refresh
|
||||
*/
|
||||
updateRouting: "updateRouting",
|
||||
/**
|
||||
* Inform Dashboard that AppBridge is ready
|
||||
*/
|
||||
notifyReady: "notifyReady",
|
||||
} as const;
|
||||
|
||||
export type ActionType = Values<typeof ActionType>;
|
||||
|
@ -77,7 +90,6 @@ function createNotificationAction(payload: NotificationPayload): NotificationAct
|
|||
|
||||
export type UpdateRoutingPayload = {
|
||||
newRoute: string;
|
||||
strategy: "replace" | "push";
|
||||
};
|
||||
|
||||
export type UpdateRouting = ActionWithId<"updateRouting", UpdateRoutingPayload>;
|
||||
|
@ -89,10 +101,20 @@ function createUpdateRoutingAction(payload: UpdateRoutingPayload): UpdateRouting
|
|||
});
|
||||
}
|
||||
|
||||
export type Actions = RedirectAction | NotificationAction | UpdateRouting;
|
||||
export type NotifyReady = ActionWithId<"notifyReady", {}>;
|
||||
|
||||
function createNotifyReadyAction(): NotifyReady {
|
||||
return withActionId({
|
||||
type: "notifyReady",
|
||||
payload: {},
|
||||
});
|
||||
}
|
||||
|
||||
export type Actions = RedirectAction | NotificationAction | UpdateRouting | NotifyReady;
|
||||
|
||||
export const actions = {
|
||||
Redirect: createRedirectAction,
|
||||
Notification: createNotificationAction,
|
||||
UpdateRouting: createUpdateRoutingAction,
|
||||
NotifyReady: createNotifyReadyAction,
|
||||
};
|
||||
|
|
|
@ -3,7 +3,14 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
|||
|
||||
import { LocaleCode } from "../locales";
|
||||
// eslint-disable-next-line
|
||||
import { actions, AppBridge, DispatchResponseEvent, HandshakeEvent, ThemeEvent } from ".";
|
||||
import {
|
||||
actions,
|
||||
ActionType,
|
||||
AppBridge,
|
||||
DispatchResponseEvent,
|
||||
HandshakeEvent,
|
||||
ThemeEvent,
|
||||
} from ".";
|
||||
|
||||
// mock document.referrer
|
||||
const origin = "http://example.com";
|
||||
|
@ -41,11 +48,37 @@ const delay = (timeout: number) =>
|
|||
setTimeout(res, timeout);
|
||||
});
|
||||
|
||||
const mockDashboardActionResponse = (actionType: ActionType, actionID: string) => {
|
||||
function onMessage(event: MessageEvent) {
|
||||
if (event.data.type === actionType) {
|
||||
fireEvent(
|
||||
window,
|
||||
new MessageEvent("message", {
|
||||
data: {
|
||||
type: "response",
|
||||
payload: { ok: true, actionId: actionID },
|
||||
} as DispatchResponseEvent,
|
||||
origin,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", onMessage);
|
||||
|
||||
return function cleanup() {
|
||||
window.removeEventListener("message", onMessage);
|
||||
};
|
||||
};
|
||||
|
||||
describe("AppBridge", () => {
|
||||
let appBridge = new AppBridge();
|
||||
|
||||
beforeEach(() => {
|
||||
appBridge = new AppBridge();
|
||||
vi.spyOn(console, "error").mockImplementation(() => {
|
||||
// noop
|
||||
});
|
||||
});
|
||||
|
||||
it("correctly sets the default domain, if not set in constructor", () => {
|
||||
|
@ -152,20 +185,7 @@ describe("AppBridge", () => {
|
|||
const target = "/test";
|
||||
const action = actions.Redirect({ to: target });
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data.type === action.type) {
|
||||
fireEvent(
|
||||
window,
|
||||
new MessageEvent("message", {
|
||||
data: {
|
||||
type: "response",
|
||||
payload: { ok: true, actionId: action.payload.actionId },
|
||||
} as DispatchResponseEvent,
|
||||
origin,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
mockDashboardActionResponse(action.type, action.payload.actionId);
|
||||
|
||||
return expect(appBridge.dispatch(action)).resolves.toBeUndefined();
|
||||
});
|
||||
|
@ -232,4 +252,14 @@ describe("AppBridge", () => {
|
|||
|
||||
window.location.href = currentLocationHref;
|
||||
});
|
||||
|
||||
it("dispatches 'notifyReady' action when created", (done) => {
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data.type === ActionType.notifyReady) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
appBridge = new AppBridge();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import debugPkg from "debug";
|
||||
|
||||
import { LocaleCode } from "../locales";
|
||||
import { Actions } from "./actions";
|
||||
import { Actions, actions } from "./actions";
|
||||
import { AppBridgeState, AppBridgeStateContainer } from "./app-bridge-state";
|
||||
import { AppIframeParams } from "./app-iframe-params";
|
||||
import { SSR } from "./constants";
|
||||
|
@ -71,6 +71,11 @@ export type AppBridgeOptions = {
|
|||
targetDomain?: string;
|
||||
saleorApiUrl?: string;
|
||||
initialLocale?: LocaleCode;
|
||||
/**
|
||||
* Should automatically emit Actions.NotifyReady.
|
||||
* If app loading time is longer, this can be disabled and sent manually.
|
||||
*/
|
||||
autoNotifyReady?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -93,6 +98,7 @@ const getDefaultOptions = (): AppBridgeOptions => ({
|
|||
targetDomain: getDomainFromUrl(),
|
||||
saleorApiUrl: getSaleorApiUrlFromUrl(),
|
||||
initialLocale: getLocaleFromUrl() ?? "en",
|
||||
autoNotifyReady: true,
|
||||
});
|
||||
|
||||
export class AppBridge {
|
||||
|
@ -145,6 +151,10 @@ export class AppBridge {
|
|||
|
||||
this.setInitialState();
|
||||
this.listenOnMessages();
|
||||
|
||||
if (this.combinedOptions.autoNotifyReady) {
|
||||
this.sendNotifyReadyAction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -256,6 +266,13 @@ export class AppBridge {
|
|||
return this.state.getState();
|
||||
}
|
||||
|
||||
sendNotifyReadyAction() {
|
||||
this.dispatch(actions.NotifyReady()).catch((e) => {
|
||||
console.error("notifyReady action failed");
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
private setInitialState() {
|
||||
debug("setInitialState() called");
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ export const useRoutePropagator = () => {
|
|||
?.dispatch(
|
||||
actions.UpdateRouting({
|
||||
newRoute: url,
|
||||
strategy: "replace",
|
||||
})
|
||||
)
|
||||
.catch(() => {
|
||||
|
|
Loading…
Reference in a new issue