Merge pull request #12551 from appsmithorg/feature/expose-post-message
feat: Add a field for the post message api exposure
This commit is contained in:
commit
ad601f32fe
|
|
@ -430,6 +430,7 @@ export const CLEAR_INTERVAL = () => `Clear interval`;
|
|||
export const GET_GEO_LOCATION = () => `Get Geolocation`;
|
||||
export const WATCH_GEO_LOCATION = () => `Watch Geolocation`;
|
||||
export const STOP_WATCH_GEO_LOCATION = () => `Stop watching Geolocation`;
|
||||
export const POST_MESSAGE = () => `Post message to a target window`;
|
||||
|
||||
//js actions
|
||||
export const JS_ACTION_COPY_SUCCESS = (actionName: string, pageName: string) =>
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ export const ActionType = {
|
|||
getGeolocation: "appsmith.geolocation.getCurrentPosition",
|
||||
watchGeolocation: "appsmith.geolocation.watchPosition",
|
||||
stopWatchGeolocation: "appsmith.geolocation.clearWatch",
|
||||
postMessage: "postMessageToTargetWindow",
|
||||
};
|
||||
type ActionType = typeof ActionType[keyof typeof ActionType];
|
||||
|
||||
|
|
@ -355,6 +356,8 @@ export enum FieldType {
|
|||
DELAY_FIELD = "DELAY_FIELD",
|
||||
ID_FIELD = "ID_FIELD",
|
||||
CLEAR_INTERVAL_ID_FIELD = "CLEAR_INTERVAL_ID_FIELD",
|
||||
MESSAGE_FIELD = "MESSAGE_FIELD",
|
||||
TARGET_ORIGIN_FIELD = "TARGET_ORIGIN_FIELD",
|
||||
}
|
||||
|
||||
type FieldConfig = {
|
||||
|
|
@ -622,6 +625,24 @@ const fieldConfigs: FieldConfigs = {
|
|||
},
|
||||
view: ViewTypes.TEXT_VIEW,
|
||||
},
|
||||
[FieldType.MESSAGE_FIELD]: {
|
||||
getter: (value: string) => {
|
||||
return textGetter(value, 0);
|
||||
},
|
||||
setter: (value: string, currentValue: string) => {
|
||||
return textSetter(value, currentValue, 0);
|
||||
},
|
||||
view: ViewTypes.TEXT_VIEW,
|
||||
},
|
||||
[FieldType.TARGET_ORIGIN_FIELD]: {
|
||||
getter: (value: string) => {
|
||||
return textGetter(value, 1);
|
||||
},
|
||||
setter: (value: string, currentValue: string) => {
|
||||
return textSetter(value, currentValue, 1);
|
||||
},
|
||||
view: ViewTypes.TEXT_VIEW,
|
||||
},
|
||||
};
|
||||
|
||||
function renderField(props: {
|
||||
|
|
@ -793,6 +814,8 @@ function renderField(props: {
|
|||
case FieldType.DELAY_FIELD:
|
||||
case FieldType.ID_FIELD:
|
||||
case FieldType.CLEAR_INTERVAL_ID_FIELD:
|
||||
case FieldType.MESSAGE_FIELD:
|
||||
case FieldType.TARGET_ORIGIN_FIELD:
|
||||
let fieldLabel = "";
|
||||
if (fieldType === FieldType.ALERT_TEXT_FIELD) {
|
||||
fieldLabel = "Message";
|
||||
|
|
@ -818,6 +841,10 @@ function renderField(props: {
|
|||
fieldLabel = "Id";
|
||||
} else if (fieldType === FieldType.CLEAR_INTERVAL_ID_FIELD) {
|
||||
fieldLabel = "Id";
|
||||
} else if (fieldType === FieldType.MESSAGE_FIELD) {
|
||||
fieldLabel = "Message";
|
||||
} else if (fieldType === FieldType.TARGET_ORIGIN_FIELD) {
|
||||
fieldLabel = "Target origin";
|
||||
}
|
||||
viewElement = (view as (props: TextViewProps) => JSX.Element)({
|
||||
label: fieldLabel,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import {
|
|||
NAVIGATE_TO,
|
||||
NO_ACTION,
|
||||
OPEN_MODAL,
|
||||
POST_MESSAGE,
|
||||
RESET_WIDGET,
|
||||
SET_INTERVAL,
|
||||
SHOW_MESSAGE,
|
||||
|
|
@ -125,6 +126,10 @@ const baseOptions: { label: string; value: string }[] = [
|
|||
label: createMessage(STOP_WATCH_GEO_LOCATION),
|
||||
value: ActionType.stopWatchGeolocation,
|
||||
},
|
||||
{
|
||||
label: createMessage(POST_MESSAGE),
|
||||
value: ActionType.postMessage,
|
||||
},
|
||||
];
|
||||
|
||||
const getBaseOptions = (featureFlags: FeatureFlags) => {
|
||||
|
|
@ -373,6 +378,18 @@ function getFieldFromValue(
|
|||
field: FieldType.CALLBACK_FUNCTION_FIELD,
|
||||
});
|
||||
}
|
||||
|
||||
if (value.indexOf("postMessageToTargetWindow") !== -1) {
|
||||
fields.push(
|
||||
{
|
||||
field: FieldType.MESSAGE_FIELD,
|
||||
},
|
||||
{
|
||||
field: FieldType.TARGET_ORIGIN_FIELD,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export enum ActionTriggerType {
|
|||
WATCH_CURRENT_LOCATION = "WATCH_CURRENT_LOCATION",
|
||||
STOP_WATCHING_CURRENT_LOCATION = "STOP_WATCHING_CURRENT_LOCATION",
|
||||
CONFIRMATION_MODAL = "CONFIRMATION_MODAL",
|
||||
POST_MESSAGE = "POST_MESSAGE",
|
||||
}
|
||||
|
||||
export const ActionTriggerFunctionNames: Record<ActionTriggerType, string> = {
|
||||
|
|
@ -37,6 +38,7 @@ export const ActionTriggerFunctionNames: Record<ActionTriggerType, string> = {
|
|||
[ActionTriggerType.WATCH_CURRENT_LOCATION]: "watchLocation",
|
||||
[ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION]: "stopWatch",
|
||||
[ActionTriggerType.CONFIRMATION_MODAL]: "ConfirmationModal",
|
||||
[ActionTriggerType.POST_MESSAGE]: "postMessageToTargetWindow",
|
||||
};
|
||||
|
||||
export type RunPluginActionDescription = {
|
||||
|
|
@ -166,6 +168,14 @@ export type ConfirmationModal = {
|
|||
payload?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type PostMessageDescription = {
|
||||
type: ActionTriggerType.POST_MESSAGE;
|
||||
payload: {
|
||||
message: unknown;
|
||||
targetOrigin: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ActionDescription =
|
||||
| RunPluginActionDescription
|
||||
| ClearPluginActionDescription
|
||||
|
|
@ -182,4 +192,5 @@ export type ActionDescription =
|
|||
| GetCurrentLocationDescription
|
||||
| WatchCurrentLocationDescription
|
||||
| StopWatchingCurrentLocationDescription
|
||||
| ConfirmationModal;
|
||||
| ConfirmationModal
|
||||
| PostMessageDescription;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import {
|
|||
import * as log from "loglevel";
|
||||
import { all, call, put, takeEvery, takeLatest } from "redux-saga/effects";
|
||||
import {
|
||||
evaluateArgumentSaga,
|
||||
evaluateAndExecuteDynamicTrigger,
|
||||
evaluateArgumentSaga,
|
||||
evaluateSnippetSaga,
|
||||
setAppVersionOnWorkerSaga,
|
||||
} from "sagas/EvaluationsSaga";
|
||||
|
|
@ -36,12 +36,12 @@ import {
|
|||
logActionExecutionError,
|
||||
TriggerFailureError,
|
||||
UncaughtPromiseError,
|
||||
UserCancelledActionExecutionError,
|
||||
} from "sagas/ActionExecution/errorUtils";
|
||||
import {
|
||||
clearIntervalSaga,
|
||||
setIntervalSaga,
|
||||
} from "sagas/ActionExecution/SetIntervalSaga";
|
||||
import { UserCancelledActionExecutionError } from "sagas/ActionExecution/errorUtils";
|
||||
import {
|
||||
getCurrentLocationSaga,
|
||||
stopWatchCurrentLocation,
|
||||
|
|
@ -49,6 +49,7 @@ import {
|
|||
} from "sagas/ActionExecution/GetCurrentLocationSaga";
|
||||
import { requestModalConfirmationSaga } from "sagas/UtilSagas";
|
||||
import { ModalType } from "reducers/uiReducers/modalActionReducer";
|
||||
import { postMessageSaga } from "./PostMessageSaga";
|
||||
|
||||
export type TriggerMeta = {
|
||||
source?: TriggerSource;
|
||||
|
|
@ -142,6 +143,9 @@ export function* executeActionTriggers(
|
|||
throw new UserCancelledActionExecutionError();
|
||||
}
|
||||
break;
|
||||
case ActionTriggerType.POST_MESSAGE:
|
||||
yield call(postMessageSaga, trigger.payload, triggerMeta);
|
||||
break;
|
||||
default:
|
||||
log.error("Trigger type unknown", trigger);
|
||||
throw Error("Trigger type unknown");
|
||||
|
|
|
|||
60
app/client/src/sagas/ActionExecution/PostMessage.test.ts
Normal file
60
app/client/src/sagas/ActionExecution/PostMessage.test.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { postMessageSaga, executePostMessage } from "./PostMessageSaga";
|
||||
import { spawn } from "redux-saga/effects";
|
||||
import { runSaga } from "redux-saga";
|
||||
|
||||
describe("PostMessageSaga", () => {
|
||||
describe("postMessageSaga function", () => {
|
||||
const generator = postMessageSaga(
|
||||
{
|
||||
message: "hello world",
|
||||
targetOrigin: "https://dev.appsmith.com",
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
it("executes postMessageSaga with the payload and trigger meta", () => {
|
||||
expect(generator.next().value).toStrictEqual(
|
||||
spawn(
|
||||
executePostMessage,
|
||||
{
|
||||
message: "hello world",
|
||||
targetOrigin: "https://dev.appsmith.com",
|
||||
},
|
||||
{},
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("should be done on next iteration", () => {
|
||||
expect(generator.next().done).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("executePostMessage function", () => {
|
||||
it("calls window.parent with message and target origin", () => {
|
||||
const dispatched: any[] = [];
|
||||
|
||||
const postMessage = jest.spyOn(window.parent, "postMessage");
|
||||
|
||||
runSaga(
|
||||
{
|
||||
dispatch: (action) => dispatched.push(action),
|
||||
},
|
||||
executePostMessage,
|
||||
{
|
||||
message: "hello world",
|
||||
targetOrigin: "https://dev.appsmith.com",
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(postMessage).toHaveBeenCalledWith(
|
||||
"hello world",
|
||||
"https://dev.appsmith.com",
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(dispatched).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
39
app/client/src/sagas/ActionExecution/PostMessageSaga.ts
Normal file
39
app/client/src/sagas/ActionExecution/PostMessageSaga.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { spawn } from "redux-saga/effects";
|
||||
import { PostMessageDescription } from "../../entities/DataTree/actionTriggers";
|
||||
import {
|
||||
logActionExecutionError,
|
||||
TriggerFailureError,
|
||||
} from "sagas/ActionExecution/errorUtils";
|
||||
import { TriggerMeta } from "./ActionExecutionSagas";
|
||||
import { isEmpty } from "lodash";
|
||||
|
||||
export function* postMessageSaga(
|
||||
payload: PostMessageDescription["payload"],
|
||||
triggerMeta: TriggerMeta,
|
||||
) {
|
||||
yield spawn(executePostMessage, payload, triggerMeta);
|
||||
}
|
||||
|
||||
export function* executePostMessage(
|
||||
payload: PostMessageDescription["payload"],
|
||||
triggerMeta: TriggerMeta,
|
||||
) {
|
||||
const { message, targetOrigin } = payload;
|
||||
try {
|
||||
if (targetOrigin === "*") {
|
||||
throw new TriggerFailureError(
|
||||
"Please enter a valid url as targetOrigin. Failing to provide a specific target discloses the data you send to any interested malicious site.",
|
||||
);
|
||||
} else if (isEmpty(targetOrigin)) {
|
||||
throw new TriggerFailureError("Please enter a target origin URL.");
|
||||
} else {
|
||||
window.parent.postMessage(message, targetOrigin, undefined);
|
||||
}
|
||||
} catch (error) {
|
||||
logActionExecutionError(
|
||||
(error as Error).message,
|
||||
triggerMeta.source,
|
||||
triggerMeta.triggerPropertyName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -670,6 +670,11 @@ export const GLOBAL_FUNCTIONS = {
|
|||
"!doc": "Stop executing a setInterval with id",
|
||||
"!type": "fn(id: string) -> void",
|
||||
},
|
||||
postMessageToTargetWindow: {
|
||||
"!doc":
|
||||
"Establish cross-origin communication between Window objects/page and iframes",
|
||||
"!type": "fn(message: unknown, targetOrigin: string)",
|
||||
},
|
||||
};
|
||||
|
||||
export const getPropsForJSActionEntity = ({
|
||||
|
|
|
|||
|
|
@ -433,4 +433,162 @@ describe("Add functions", () => {
|
|||
]),
|
||||
);
|
||||
});
|
||||
|
||||
describe("Post message to target window works", () => {
|
||||
const targetOrigin = "https://dev.appsmith.com/";
|
||||
|
||||
it("Post message with first argument (message) as a string", () => {
|
||||
const message = "Hello world!";
|
||||
|
||||
expect(
|
||||
dataTreeWithFunctions.postMessageToTargetWindow(message, targetOrigin),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
payload: {
|
||||
message: "Hello world!",
|
||||
targetOrigin: "https://dev.appsmith.com/",
|
||||
},
|
||||
type: "POST_MESSAGE",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("Post message with first argument (message) as undefined", () => {
|
||||
const message = undefined;
|
||||
|
||||
expect(
|
||||
dataTreeWithFunctions.postMessageToTargetWindow(message, targetOrigin),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
payload: {
|
||||
message: undefined,
|
||||
targetOrigin: "https://dev.appsmith.com/",
|
||||
},
|
||||
type: "POST_MESSAGE",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("Post message with first argument (message) as null", () => {
|
||||
const message = null;
|
||||
|
||||
expect(
|
||||
dataTreeWithFunctions.postMessageToTargetWindow(message, targetOrigin),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
payload: {
|
||||
message: null,
|
||||
targetOrigin: "https://dev.appsmith.com/",
|
||||
},
|
||||
type: "POST_MESSAGE",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("Post message with first argument (message) as a number", () => {
|
||||
const message = 1826;
|
||||
|
||||
expect(
|
||||
dataTreeWithFunctions.postMessageToTargetWindow(message, targetOrigin),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
payload: {
|
||||
message: 1826,
|
||||
targetOrigin: "https://dev.appsmith.com/",
|
||||
},
|
||||
type: "POST_MESSAGE",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("Post message with first argument (message) as a boolean", () => {
|
||||
const message = true;
|
||||
|
||||
expect(
|
||||
dataTreeWithFunctions.postMessageToTargetWindow(message, targetOrigin),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
payload: {
|
||||
message: true,
|
||||
targetOrigin: "https://dev.appsmith.com/",
|
||||
},
|
||||
type: "POST_MESSAGE",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("Post message with first argument (message) as an array", () => {
|
||||
const message = [1, 2, 3, [1, 2, 3, [1, 2, 3]]];
|
||||
|
||||
expect(
|
||||
dataTreeWithFunctions.postMessageToTargetWindow(message, targetOrigin),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
payload: {
|
||||
message: [1, 2, 3, [1, 2, 3, [1, 2, 3]]],
|
||||
targetOrigin: "https://dev.appsmith.com/",
|
||||
},
|
||||
type: "POST_MESSAGE",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("Post message with first argument (message) as an object", () => {
|
||||
const message = {
|
||||
key: 1,
|
||||
status: "active",
|
||||
person: {
|
||||
name: "timothee chalamet",
|
||||
},
|
||||
randomArr: [1, 2, 3],
|
||||
};
|
||||
|
||||
expect(
|
||||
dataTreeWithFunctions.postMessageToTargetWindow(message, targetOrigin),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
payload: {
|
||||
message: {
|
||||
key: 1,
|
||||
status: "active",
|
||||
person: {
|
||||
name: "timothee chalamet",
|
||||
},
|
||||
randomArr: [1, 2, 3],
|
||||
},
|
||||
targetOrigin: "https://dev.appsmith.com/",
|
||||
},
|
||||
type: "POST_MESSAGE",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -261,6 +261,16 @@ const DATA_TREE_FUNCTIONS: Record<
|
|||
};
|
||||
},
|
||||
},
|
||||
postMessageToTargetWindow: function(message: unknown, targetOrigin: string) {
|
||||
return {
|
||||
type: ActionTriggerType.POST_MESSAGE,
|
||||
payload: {
|
||||
message,
|
||||
targetOrigin,
|
||||
},
|
||||
executionType: ExecutionType.TRIGGER,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const enhanceDataTreeWithFunctions = (
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user