From b4e0a053cbdd2fc497ba0ebdc720569356bd2c26 Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Mon, 21 Nov 2022 11:32:18 +0100 Subject: [PATCH] Add routing changing actions and RoutePropagator (#110) --- package.json | 5 +++ src/app-bridge/actions.ts | 18 +++++++++- src/app-bridge/next/index.ts | 1 + src/app-bridge/next/route-propagator.tsx | 43 ++++++++++++++++++++++++ tsup.config.ts | 1 + 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/app-bridge/next/index.ts create mode 100644 src/app-bridge/next/route-propagator.tsx diff --git a/package.json b/package.json index aac66b9..28df870 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,11 @@ "import": "./app-bridge/index.mjs", "require": "./app-bridge/index.js" }, + "./app-bridge/next": { + "types": "./app-bridge/next/index.d.ts", + "import": "./app-bridge/next/index.mjs", + "require": "./app-bridge/next/index.js" + }, "./util": { "types": "./util/index.d.ts", "import": "./util/index.mjs", diff --git a/src/app-bridge/actions.ts b/src/app-bridge/actions.ts index 3360507..a427d4f 100644 --- a/src/app-bridge/actions.ts +++ b/src/app-bridge/actions.ts @@ -6,6 +6,7 @@ import { Values } from "./helpers"; export const ActionType = { redirect: "redirect", notification: "notification", + updateRouting: "updateRouting", } as const; export type ActionType = Values; @@ -74,9 +75,24 @@ function createNotificationAction(payload: NotificationPayload): NotificationAct }); } -export type Actions = RedirectAction | NotificationAction; +export type UpdateRoutingPayload = { + newRoute: string; + strategy: "replace" | "push"; +}; + +export type UpdateRouting = ActionWithId<"updateRouting", UpdateRoutingPayload>; + +function createUpdateRoutingAction(payload: UpdateRoutingPayload): UpdateRouting { + return withActionId({ + type: "updateRouting", + payload, + }); +} + +export type Actions = RedirectAction | NotificationAction | UpdateRouting; export const actions = { Redirect: createRedirectAction, Notification: createNotificationAction, + UpdateRouting: createUpdateRoutingAction, }; diff --git a/src/app-bridge/next/index.ts b/src/app-bridge/next/index.ts new file mode 100644 index 0000000..0da4f5b --- /dev/null +++ b/src/app-bridge/next/index.ts @@ -0,0 +1 @@ +export * from "./route-propagator"; diff --git a/src/app-bridge/next/route-propagator.tsx b/src/app-bridge/next/route-propagator.tsx new file mode 100644 index 0000000..b9555ff --- /dev/null +++ b/src/app-bridge/next/route-propagator.tsx @@ -0,0 +1,43 @@ +import { useRouter } from "next/router"; +import { useEffect } from "react"; + +import { actions } from "../actions"; +import { useAppBridge } from "../app-bridge-provider"; + +/** + * Synchronizes app inner state (inside iframe) with dashboard routing, so app's route can be restored after refresh + */ +export const useRoutePropagator = () => { + const { appBridge, appBridgeState } = useAppBridge(); + const router = useRouter(); + + useEffect(() => { + if (!appBridgeState?.ready ?? !appBridge) { + return; + } + + router.events.on("routeChangeComplete", (url) => { + appBridge + ?.dispatch( + actions.UpdateRouting({ + newRoute: url, + strategy: "replace", + }) + ) + .catch(() => { + console.error("Error dispatching action"); + }); + }); + }, [appBridgeState, appBridge]); +}; + +/** + * Synchronizes app inner state (inside iframe) with dashboard routing, so app's route can be restored after refresh + * + * Component uses useRoutePropagator(), but it can consume context in the same component where provider was used (e.g. _app.tsx) + */ +export function RoutePropagator() { + useRoutePropagator(); + + return null; +} diff --git a/tsup.config.ts b/tsup.config.ts index 1eb74be..992eec6 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -10,6 +10,7 @@ export default defineConfig({ "src/infer-webhooks.ts", "src/APL/index.ts", "src/app-bridge/index.ts", + "src/app-bridge/next/index.ts", "src/handlers/next/index.ts", "src/middleware/index.ts", "src/settings-manager/index.ts",