Skip to content

Commit

Permalink
Merge pull request #4377 from easyops-cn/steve/v3-window-post-message
Browse files Browse the repository at this point in the history
Steve/v3-window-post-message
  • Loading branch information
qiaofengxi authored Jul 31, 2024
2 parents a99b2a1 + a2b5474 commit 630a276
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 37 deletions.
54 changes: 47 additions & 7 deletions packages/runtime/src/internal/bindListeners.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const consoleWarn = jest.spyOn(console, "warn");
const consoleError = jest.spyOn(console, "error");
const windowOpen = jest.spyOn(window, "open");
const windowAlert = jest.spyOn(window, "alert");
const windowPostMessage = jest.spyOn(window, "postMessage");
const mockGetHistory = getHistory as jest.Mock;
const mockHandleHttpError = handleHttpError as jest.MockedFunction<
typeof handleHttpError
Expand Down Expand Up @@ -756,6 +757,37 @@ describe("listenerFactory for console.*", () => {
)(event);
expect(windowOpen).toBeCalledWith("/ok", "_blank", "popup=yes");
});

test("window.postMessage without origin", () => {
listenerFactory(
{
action: "window.postMessage",
args: ["<% { channel: 'test-1', detail: EVENT.detail } %>"],
},
runtimeContext
)(event);
expect(windowPostMessage).toBeCalledWith(
{ channel: "test-1", detail: "ok" },
"http://localhost"
);
});

test("window.postMessage with origin", () => {
listenerFactory(
{
action: "window.postMessage",
args: [
"<% { channel: 'test-2', detail: EVENT.detail } %>",
"<% location.origin %>",
],
},
runtimeContext
)(event);
expect(windowPostMessage).toBeCalledWith(
{ channel: "test-2", detail: "ok" },
"http://localhost"
);
});
});

describe("listenerFactory for setting brick properties", () => {
Expand Down Expand Up @@ -1003,24 +1035,32 @@ describe("listenerFactory for calling brick methods", () => {
expect(brick.element.callbackFinally).toBeCalledWith(null);
});

test("Calling undefined method", () => {
consoleError.mockReturnValueOnce();
test("Calling undefined method", async () => {
consoleInfo.mockReturnValueOnce();
const brick = {
element: document.createElement("div"),
};
listenerFactory(
{
target: "_self",
method: "callMe",
callback: {
error: {
action: "console.info",
args: ["<% EVENT.detail %>"],
},
},
},
runtimeContext,
brick
)(event);
expect(consoleError).toBeCalledTimes(1);
expect(consoleError).toBeCalledWith("target has no method:", {
target: brick.element,
method: "callMe",
});

await (global as any).flushPromises();

expect(consoleInfo).toBeCalledTimes(1);
expect(consoleInfo).toBeCalledWith(
new Error("target <div> has no method: callMe")
);
});
});

Expand Down
76 changes: 46 additions & 30 deletions packages/runtime/src/internal/bindListeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
getTplHostElement,
getTplStateStore,
} from "./CustomTemplates/utils.js";
import { handleHttpError, httpErrorToString } from "../handleHttpError.js";
import { handleHttpError } from "../handleHttpError.js";
import { Notification } from "../Notification.js";
import { getFormStateStore } from "./FormRenderer/utils.js";
import { DataStore } from "./data/DataStore.js";
Expand Down Expand Up @@ -162,7 +162,11 @@ export function listenerFactory(
// case "alias.replace":

case "window.open":
handleWindowAction(event, handler.args, runtimeContext);
handleWindowOpenAction(event, handler.args, runtimeContext);
break;

case "window.postMessage":
handleWindowPostMessageAction(event, handler.args, runtimeContext);
break;

case "location.reload":
Expand Down Expand Up @@ -314,27 +318,21 @@ export function listenerFactory(
};
}

async function handleUseProviderAction(
function handleUseProviderAction(
event: Event,
handler: UseProviderEventHandler,
runtimeContext: RuntimeContext,
runtimeBrick?: ElementHolder
) {
try {
const providerBrick = await getProviderBrick(handler.useProvider);
const method = handler.method !== "saveAs" ? "resolve" : "saveAs";
brickCallback(
event,
providerBrick,
handler,
method,
runtimeContext,
runtimeBrick
);
} catch (error) {
// eslint-disable-next-line no-console
console.error(httpErrorToString(error));
}
const method = handler.method !== "saveAs" ? "resolve" : "saveAs";
brickCallback(
event,
handler.useProvider,
handler,
method,
runtimeContext,
runtimeBrick
);
}

function handleCustomAction(
Expand Down Expand Up @@ -427,23 +425,26 @@ function handleCustomAction(

async function brickCallback(
event: Event,
target: HTMLElement,
targetOrProvider: HTMLElement | string,
handler: ExecuteCustomBrickEventHandler | UseProviderEventHandler,
method: string,
runtimeContext: RuntimeContext,
runtimeBrick?: ElementHolder,
options?: ArgsFactoryOptions
): Promise<void> {
if (typeof (target as any)[method] !== "function") {
// eslint-disable-next-line no-console
console.error("target has no method:", {
target,
method: method,
});
return;
}
const isProvider = isUseProviderHandler(handler);

const task = async (): Promise<unknown> => {
const realTarget = isProvider
? await getProviderBrick(targetOrProvider as string)
: (targetOrProvider as HTMLElement);

if (typeof (realTarget as any)[method] !== "function") {
throw new Error(
`target <${realTarget.tagName.toLowerCase()}> has no method: ${method}`
);
}

let computedArgs = argsFactory(
handler.args,
runtimeContext,
Expand All @@ -461,7 +462,7 @@ async function brickCallback(
handler.sse?.stream
);
}
return (target as any)[method](...computedArgs);
return (realTarget as any)[method](...computedArgs);
};

if (!handler.callback) {
Expand All @@ -482,7 +483,7 @@ async function brickCallback(
finally: callbackFactory("finally"),
};

if (isUseProviderHandler(handler)) {
if (isProvider) {
const pollRuntimeContext = {
...runtimeContext,
event,
Expand Down Expand Up @@ -589,7 +590,7 @@ function handleHistoryAction(
);
}

function handleWindowAction(
function handleWindowOpenAction(
event: Event,
args: unknown[] | undefined,
runtimeContext: RuntimeContext
Expand All @@ -602,6 +603,21 @@ function handleWindowAction(
window.open(url, target || "_self", features);
}

function handleWindowPostMessageAction(
event: Event,
args: unknown[] | undefined,
runtimeContext: RuntimeContext
) {
const computedArgs = argsFactory(args, runtimeContext, event) as Parameters<
typeof window.postMessage
>;
if (computedArgs.length === 1) {
// Add default target origin
computedArgs.push(location.origin);
}
window.postMessage(...computedArgs);
}

function batchUpdate(
args: unknown[],
batch: boolean,
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ export interface BuiltinBrickEventHandler {
| "location.reload"
| "location.assign"
| "window.open"
| "window.postMessage"
| "event.preventDefault"
| "event.stopPropagation"
| "console.log"
Expand Down

0 comments on commit 630a276

Please sign in to comment.