Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/meteor/app/integrations/server/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type IntegrationThis = GenericRouteExecutionContext & {
request: Request & {
integration: IIncomingIntegration;
};
user: IUser & { username: RequiredField<IUser, 'username'> };
user: RequiredField<IUser, 'username'>;
};

async function createIntegration(options: IntegrationOptions, user: IUser): Promise<IOutgoingIntegration | undefined> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class RocketChatIntegrationHandler {
channel: tmpRoom.t === 'd' ? `@${tmpRoom._id}` : `#${tmpRoom._id}`,
};

return processWebhookMessage(message, user as IUser & { username: RequiredField<IUser, 'username'> }, defaultValues);
return processWebhookMessage(message, user as RequiredField<IUser, 'username'>, defaultValues);
}

eventNameArgumentsToObject(...args: unknown[]) {
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/app/lib/server/functions/processWebhookMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ const buildMessage = (messageObj: Payload, defaultValues: DefaultValues) => {

export function processWebhookMessage(
messageObj: Payload & { separateResponse: true },
user: IUser & { username: RequiredField<IUser, 'username'> },
user: RequiredField<IUser, 'username'>,
defaultValues?: DefaultValues,
): Promise<WebhookResponseItem[]>;

export function processWebhookMessage(
messageObj: Payload & { separateResponse?: false | undefined },
user: IUser & { username: RequiredField<IUser, 'username'> },
user: RequiredField<IUser, 'username'>,
defaultValues?: DefaultValues,
): Promise<WebhookSuccessItem[]>;

Expand All @@ -148,7 +148,7 @@ export async function processWebhookMessage(
*/
separateResponse?: boolean;
},
user: IUser & { username: RequiredField<IUser, 'username'> },
user: RequiredField<IUser, 'username'>,
defaultValues: DefaultValues = { channel: '', alias: '', avatar: '', emoji: '' },
) {
const rooms: ({ channel: string } & ({ room: IRoom } | { room: IRoom | null; error?: any }))[] = [];
Expand Down
142 changes: 75 additions & 67 deletions apps/meteor/ee/server/apps/communication/uikit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AppEvents, type IAppServerOrchestrator } from '@rocket.chat/apps';
import type { UiKitCoreAppPayload } from '@rocket.chat/core-services';
import { UiKitCoreApp } from '@rocket.chat/core-services';
import type { OperationParams, UrlParams } from '@rocket.chat/rest-typings';
import type { OperationParams, OperationResult, UrlParams } from '@rocket.chat/rest-typings';
import bodyParser from 'body-parser';
import cors from 'cors';
import type { Request, Response } from 'express';
Expand Down Expand Up @@ -94,7 +93,7 @@ apiServer.use('/api/apps/ui.interaction/', bodyParser.json(), cors(corsOptions),

type UiKitUserInteractionRequest = Request<
UrlParams<'/apps/ui.interaction/:id'>,
any,
OperationResult<'POST', '/apps/ui.interaction/:id'> | { error: unknown },
OperationParams<'POST', '/apps/ui.interaction/:id'> & {
visitor?: {
id: string;
Expand All @@ -111,81 +110,87 @@ type UiKitUserInteractionRequest = Request<
}
>;

const getCoreAppPayload = (req: UiKitUserInteractionRequest): UiKitCoreAppPayload => {
router.post('/:id', async (req: UiKitUserInteractionRequest, res, next) => {
const { id: appId } = req.params;

if (req.body.type === 'blockAction') {
const { user } = req;
const { type, actionId, triggerId, payload, container, visitor } = req.body;
const message = 'mid' in req.body ? req.body.mid : undefined;
const room = 'rid' in req.body ? req.body.rid : undefined;

return {
appId,
type,
actionId,
triggerId,
container,
message,
payload,
user,
visitor,
room,
};
const isCoreApp = await UiKitCoreApp.isRegistered(appId);
if (!isCoreApp) {
return next();
}

if (req.body.type === 'viewClosed') {
try {
const { user } = req;
const {
type,
payload: { view, isCleared },
triggerId,
} = req.body;

return {
appId,
triggerId,
type,
user,
payload: {
view,
isCleared,
},
};
}

if (req.body.type === 'viewSubmit') {
const { user } = req;
const { type, actionId, triggerId, payload } = req.body;

return {
appId,
type,
actionId,
triggerId,
payload,
user,
};
}
switch (req.body.type) {
case 'blockAction': {
const { type, actionId, triggerId, payload, container, visitor } = req.body;
const message = 'mid' in req.body ? req.body.mid : undefined;
const room = 'rid' in req.body ? req.body.rid : undefined;

throw new Error('Type not supported');
};
const result = await UiKitCoreApp.blockAction({
appId,
type,
actionId,
triggerId,
container,
message,
payload,
user,
visitor,
room,
});

router.post('/:id', async (req: UiKitUserInteractionRequest, res, next) => {
const { id: appId } = req.params;
// Using ?? to always send something in the response, even if the app had no result.
res.send(result ?? {});

const isCoreApp = await UiKitCoreApp.isRegistered(appId);
if (!isCoreApp) {
return next();
}
return;
}

try {
const payload = getCoreAppPayload(req);
case 'viewSubmit': {
const { type, actionId, triggerId, payload } = req.body;

const result = await UiKitCoreApp.viewSubmit({
appId,
type,
actionId,
triggerId,
payload,
user,
});

// Using ?? to always send something in the response, even if the app had no result.
res.send(result ?? {});

return;
}

case 'viewClosed': {
const {
type,
payload: { view, isCleared },
triggerId,
} = req.body;

const result = await UiKitCoreApp.viewClosed({
appId,
triggerId,
type,
user,
payload: {
view,
isCleared,
},
});

// Using ?? to always send something in the response, even if the app had no result.
res.send(result ?? {});

const result = await UiKitCoreApp[payload.type](payload);
return;
}

// Using ?? to always send something in the response, even if the app had no result.
res.send(result ?? {});
default:
throw new Error('Type not supported');
}
} catch (e) {
const error = e instanceof Error ? e.message : e;
res.status(500).send({ error });
Expand All @@ -201,7 +206,10 @@ export class AppUIKitInteractionApi {
router.post('/:id', this.routeHandler);
}

private routeHandler = async (req: UiKitUserInteractionRequest, res: Response): Promise<void> => {
private routeHandler = async (
req: UiKitUserInteractionRequest,
res: Response<OperationResult<'POST', '/apps/ui.interaction/:id'> | { error: unknown }>,
): Promise<void> => {
const { orch } = this;
const { id: appId } = req.params;

Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/server/modules/core-apps/banner.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Banner } from '@rocket.chat/core-services';
import type { IUiKitCoreApp, UiKitCoreAppPayload } from '@rocket.chat/core-services';
import type { IUiKitCoreApp, UiKitCoreAppViewClosedPayload } from '@rocket.chat/core-services';
import type * as UiKit from '@rocket.chat/ui-kit';

export class BannerModule implements IUiKitCoreApp {
appId = 'banner-core';

// when banner view is closed we need to dismiss that banner for that user
async viewClosed(payload: UiKitCoreAppPayload): Promise<UiKit.ServerInteraction> {
async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction> {
const {
payload: { view: { viewId: bannerId } = {} },
user: { _id: userId } = {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Banner } from '@rocket.chat/core-services';
import type { IUiKitCoreApp, UiKitCoreAppPayload } from '@rocket.chat/core-services';
import type {
IUiKitCoreApp,
UiKitCoreAppBlockActionPayload,
UiKitCoreAppViewClosedPayload,
UiKitCoreAppViewSubmitPayload,
} from '@rocket.chat/core-services';
import type { Cloud, IUser } from '@rocket.chat/core-typings';
import { Banners } from '@rocket.chat/models';
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
Expand All @@ -19,7 +24,7 @@ type CloudAnnouncementInteractant =
user: Pick<IUser, '_id' | 'username' | 'name'>;
}
| {
visitor: Pick<Required<UiKitCoreAppPayload>['visitor'], 'id' | 'username' | 'name' | 'department' | 'phone'>;
visitor: Pick<NonNullable<UiKitCoreAppBlockActionPayload['visitor']>, 'id' | 'username' | 'name' | 'department' | 'phone'>;
};

type CloudAnnouncementInteractionRequest = UiKit.UserInteraction & CloudAnnouncementInteractant;
Expand All @@ -35,15 +40,15 @@ export class CloudAnnouncementsModule implements IUiKitCoreApp {
return settings.get('Cloud_Url');
}

blockAction(payload: UiKitCoreAppPayload): Promise<UiKit.ServerInteraction | void> {
blockAction(payload: UiKitCoreAppBlockActionPayload): Promise<UiKit.ServerInteraction | undefined> {
return this.handlePayload(payload);
}

viewSubmit(payload: UiKitCoreAppPayload): Promise<UiKit.ServerInteraction | void> {
viewSubmit(payload: UiKitCoreAppViewSubmitPayload): Promise<UiKit.ServerInteraction | undefined> {
return this.handlePayload(payload);
}

async viewClosed(payload: UiKitCoreAppPayload): Promise<UiKit.ServerInteraction> {
async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction | undefined> {
const {
payload: { view: { viewId, id } = {} },
user: { _id: userId } = {},
Expand Down Expand Up @@ -86,7 +91,9 @@ export class CloudAnnouncementsModule implements IUiKitCoreApp {
};
}

protected async handlePayload(payload: UiKitCoreAppPayload): Promise<UiKit.ServerInteraction | void> {
protected async handlePayload(
payload: UiKitCoreAppBlockActionPayload | UiKitCoreAppViewSubmitPayload | UiKitCoreAppViewClosedPayload,
): Promise<UiKit.ServerInteraction | undefined> {
const interactant = this.getInteractant(payload);
const interaction = this.getInteraction(payload);

Expand All @@ -104,10 +111,13 @@ export class CloudAnnouncementsModule implements IUiKitCoreApp {
return serverInteraction;
} catch (err) {
SystemLogger.error({ err });
return undefined;
}
}

protected getInteractant(payload: UiKitCoreAppPayload): CloudAnnouncementInteractant {
protected getInteractant(
payload: UiKitCoreAppBlockActionPayload | UiKitCoreAppViewSubmitPayload | UiKitCoreAppViewClosedPayload,
): CloudAnnouncementInteractant {
if (payload.user) {
return {
user: {
Expand All @@ -118,7 +128,7 @@ export class CloudAnnouncementsModule implements IUiKitCoreApp {
};
}

if (payload.visitor) {
if ('visitor' in payload && payload.visitor) {
return {
visitor: {
id: payload.visitor.id,
Expand All @@ -136,7 +146,9 @@ export class CloudAnnouncementsModule implements IUiKitCoreApp {
/**
* Transform the payload received from the Core App back to the format the UI sends from the client
*/
protected getInteraction(payload: UiKitCoreAppPayload): UiKit.UserInteraction {
protected getInteraction(
payload: UiKitCoreAppBlockActionPayload | UiKitCoreAppViewSubmitPayload | UiKitCoreAppViewClosedPayload,
): UiKit.UserInteraction {
if (payload.type === 'blockAction' && payload.container?.type === 'message') {
const {
actionId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { UiKitCoreAppPayload } from '@rocket.chat/core-services';
import type { UiKitCoreAppViewClosedPayload } from '@rocket.chat/core-services';
import type * as UiKit from '@rocket.chat/ui-kit';

import { CloudAnnouncementsModule } from './cloudAnnouncements.module';

export class CloudSubscriptionCommunication extends CloudAnnouncementsModule {
override appId = 'cloud-communication-core';

override async viewClosed(payload: UiKitCoreAppPayload): Promise<UiKit.ServerInteraction> {
override async viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction> {
const {
payload: { view: { viewId } = {} },
user: { _id: userId } = {},
Expand Down
Loading
Loading