Files
upstream-mattermost/webapp/channels/src/actions/apps.ts
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

272 lines
10 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {IntlShape} from 'react-intl';
import type {AnyAction} from 'redux';
import type {AppCallResponse, AppForm, AppCallRequest, AppContext, AppBinding} from '@mattermost/types/apps';
import type {CommandArgs} from '@mattermost/types/integrations';
import type {Post} from '@mattermost/types/posts';
import {Client4} from 'mattermost-redux/client';
import {AppCallResponseTypes} from 'mattermost-redux/constants/apps';
import {cleanForm} from 'mattermost-redux/utils/apps';
import {openModal} from 'actions/views/modals';
import AppsForm from 'components/apps_form';
import {createCallRequest, makeCallErrorResponse} from 'utils/apps';
import {getHistory} from 'utils/browser_history';
import {ModalIdentifiers} from 'utils/constants';
import {getSiteURL, shouldOpenInNewTab} from 'utils/url';
import type {DoAppCallResult} from 'types/apps';
import type {ActionFuncAsync, ThunkActionFunc} from 'types/store';
import {sendEphemeralPost} from './global_actions';
export function handleBindingClick<Res=unknown>(binding: AppBinding, context: AppContext, intl: any): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
return async (dispatch) => {
// Fetch form
let form = binding.form;
if (form?.source) {
const callRequest = createCallRequest(form.source, context);
const res = await dispatch(doAppFetchForm<Res>(callRequest, intl));
if (res.error) {
return res;
}
form = res.data!.form;
}
// Open form
if (form) {
// This should come properly formed, but using preventive checks
if (!form?.submit) {
const errMsg = intl.formatMessage({
id: 'apps.error.malformed_binding',
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
});
return {error: makeCallErrorResponse(errMsg)};
}
const res: AppCallResponse<Res> = {
type: AppCallResponseTypes.FORM,
form,
};
return {data: res};
}
// Submit binding
// This should come properly formed, but using preventive checks
if (!binding.submit) {
const errMsg = intl.formatMessage({
id: 'apps.error.malformed_binding',
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
});
return {error: makeCallErrorResponse(errMsg)};
}
const callRequest = createCallRequest(
binding.submit,
context,
);
const res = await dispatch(doAppSubmit<Res>(callRequest, intl));
return res;
};
}
export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: IntlShape): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
return async () => {
try {
const call: AppCallRequest = {
...inCall,
context: {
...inCall.context,
track_as_submit: true,
},
};
const res = await Client4.executeAppCall(call, true) as AppCallResponse<Res>;
const responseType = res.type || AppCallResponseTypes.OK;
switch (responseType) {
case AppCallResponseTypes.OK:
return {data: res};
case AppCallResponseTypes.ERROR:
return {error: res};
case AppCallResponseTypes.FORM:
if (!res.form?.submit) {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.form.no_form',
defaultMessage: 'Response type is `form`, but no form was included in response.',
});
return {error: makeCallErrorResponse(errMsg)};
}
cleanForm(res.form);
return {data: res};
case AppCallResponseTypes.NAVIGATE: {
if (!res.navigate_to_url) {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.navigate.no_url',
defaultMessage: 'Response type is `navigate`, but no url was included in response.',
});
return {error: makeCallErrorResponse(errMsg)};
}
if (shouldOpenInNewTab(res.navigate_to_url, getSiteURL())) {
window.open(res.navigate_to_url);
return {data: res};
}
const navigateURL = res.navigate_to_url.startsWith(getSiteURL()) ? res.navigate_to_url.slice(getSiteURL().length) : res.navigate_to_url;
getHistory().push(navigateURL);
return {data: res};
}
default: {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.unknown_type',
defaultMessage: 'App response type not supported. Response type: {type}.',
}, {type: responseType});
return {error: makeCallErrorResponse(errMsg)};
}
}
} catch (error: any) {
const errMsg = error.message || intl.formatMessage({
id: 'apps.error.responses.unexpected_error',
defaultMessage: 'Received an unexpected error.',
});
return {error: makeCallErrorResponse(errMsg)};
}
};
}
export function doAppFetchForm<Res=unknown>(call: AppCallRequest, intl: any): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
return async () => {
try {
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
const responseType = res.type || AppCallResponseTypes.OK;
switch (responseType) {
case AppCallResponseTypes.ERROR:
return {error: res};
case AppCallResponseTypes.FORM:
if (!res.form?.submit) {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.form.no_form',
defaultMessage: 'Response type is `form`, but no form was included in response.',
});
return {error: makeCallErrorResponse(errMsg)};
}
cleanForm(res.form);
return {data: res};
default: {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.unknown_type',
defaultMessage: 'App response type not supported. Response type: {type}.',
}, {type: responseType});
return {error: makeCallErrorResponse(errMsg)};
}
}
} catch (error: any) {
const errMsg = error.message || intl.formatMessage({
id: 'apps.error.responses.unexpected_error',
defaultMessage: 'Received an unexpected error.',
});
return {error: makeCallErrorResponse(errMsg)};
}
};
}
export function doAppLookup<Res=unknown>(call: AppCallRequest, intl: any): ThunkActionFunc<Promise<DoAppCallResult<Res>>> {
return async () => {
try {
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
const responseType = res.type || AppCallResponseTypes.OK;
switch (responseType) {
case AppCallResponseTypes.OK:
return {data: res};
case AppCallResponseTypes.ERROR:
return {error: res};
default: {
const errMsg = intl.formatMessage({
id: 'apps.error.responses.unknown_type',
defaultMessage: 'App response type not supported. Response type: {type}.',
}, {type: responseType});
return {error: makeCallErrorResponse(errMsg)};
}
}
} catch (error: any) {
const errMsg = error.message || intl.formatMessage({
id: 'apps.error.responses.unexpected_error',
defaultMessage: 'Received an unexpected error.',
});
return {error: makeCallErrorResponse(errMsg)};
}
};
}
export function makeFetchBindings(location: string): (channelId: string, teamId: string) => ActionFuncAsync<AppBinding[]> {
return (channelId: string, teamId: string): ActionFuncAsync<AppBinding[]> => {
return async () => {
try {
const allBindings = await Client4.getAppsBindings(channelId, teamId);
const headerBindings = allBindings.filter((b) => b.location === location);
const bindings = headerBindings.reduce((accum: AppBinding[], current: AppBinding) => accum.concat(current.bindings || []), []);
return {data: bindings};
} catch {
return {data: []};
}
};
};
}
export function openAppsModal(form: AppForm, context: AppContext): AnyAction {
return openModal({
modalId: ModalIdentifiers.APPS_MODAL,
dialogType: AppsForm,
dialogProps: {
form,
appContext: context,
},
});
}
export function postEphemeralCallResponseForPost(response: AppCallResponse, message: string, post: Post) {
return sendEphemeralPost(
message,
post.channel_id,
post.root_id || post.id,
response.app_metadata?.bot_user_id,
);
}
export function postEphemeralCallResponseForChannel(response: AppCallResponse, message: string, channelID: string) {
return sendEphemeralPost(
message,
channelID,
'',
response.app_metadata?.bot_user_id,
);
}
export function postEphemeralCallResponseForContext(response: AppCallResponse, message: string, context: AppContext) {
return sendEphemeralPost(
message,
context.channel_id,
context.root_id || context.post_id,
response.app_metadata?.bot_user_id,
);
}
export function postEphemeralCallResponseForCommandArgs(response: AppCallResponse, message: string, args: CommandArgs) {
return sendEphemeralPost(
message,
args.channel_id,
args.root_id,
response.app_metadata?.bot_user_id,
);
}