;
+
+ findOneById(_id: T['_id'], options?: FindOptions
): Promise
;
+
+ async findOneById(_id: T['_id'], _options?: any): Promise {
+ return null;
+ }
+
+ findOne(query?: Filter | T['_id'], options?: undefined): Promise;
+
+ findOne(query: Filter | T['_id'], options: FindOptions): Promise
;
+
+ async findOne
(_query: Filter | T['_id'], _options?: any): Promise | WithId | null> {
+ return null;
+ }
+
+ find(query?: Filter): FindCursor>;
+
+ find(query: Filter, options: FindOptions): FindCursor
;
+
+ find
(
+ _query: Filter | undefined,
+ _options?: FindOptions,
+ ): FindCursor> | FindCursor> {
+ return undefined as any;
+ }
+
+ findPaginated(query: Filter, options?: FindOptions): FindPaginated>>;
+
+ findPaginated(_query: Filter, _options?: any): FindPaginated>> {
+ return {
+ cursor: undefined as any,
+ totalCount: Promise.resolve(0),
+ };
+ }
+
+ async update(
+ filter: Filter,
+ update: UpdateFilter | Partial,
+ options?: UpdateOptions & { multi?: true },
+ ): Promise {
+ return this.updateOne(filter, update, options);
+ }
+
+ async updateOne(_filter: Filter, _update: UpdateFilter | Partial, _options?: UpdateOptions): Promise {
+ return {
+ acknowledged: true,
+ matchedCount: 0,
+ modifiedCount: 0,
+ upsertedCount: 0,
+ upsertedId: '' as any,
+ };
+ }
+
+ async updateMany(filter: Filter, update: UpdateFilter | Partial, options?: UpdateOptions): Promise {
+ return this.updateOne(filter, update, options);
+ }
+
+ async insertMany(_docs: InsertionModel[], _options?: BulkWriteOptions): Promise> {
+ return {
+ acknowledged: true,
+ insertedCount: 0,
+ insertedIds: {},
+ };
+ }
+
+ async insertOne(_doc: InsertionModel, _options?: InsertOneOptions): Promise> {
+ return {
+ acknowledged: true,
+ insertedId: '' as any,
+ };
+ }
+
+ async removeById(_id: T['_id']): Promise {
+ return {
+ acknowledged: true,
+ deletedCount: 0,
+ };
+ }
+
+ async deleteOne(filter: Filter, options?: DeleteOptions & { bypassDocumentValidation?: boolean }): Promise {
+ return this.deleteMany(filter, options);
+ }
+
+ async deleteMany(_filter: Filter, _options?: DeleteOptions): Promise {
+ return {
+ acknowledged: true,
+ deletedCount: 0,
+ };
+ }
+
+ // Trash
+ trashFind(
+ _query: Filter,
+ _options?: FindOptions,
+ ): FindCursor> | undefined {
+ return undefined as any;
+ }
+
+ trashFindOneById(_id: TDeleted['_id']): Promise;
+
+ trashFindOneById(_id: TDeleted['_id'], options: FindOptions
): Promise
;
+
+ async trashFindOneById
(
+ _id: TDeleted['_id'],
+ _options?: FindOptions
,
+ ): Promise | TDeleted> | null> {
+ return null;
+ }
+
+ trashFindDeletedAfter(deletedAt: Date): FindCursor>;
+
+ trashFindDeletedAfter(
+ _deletedAt: Date,
+ _query?: Filter,
+ _options?: FindOptions,
+ ): FindCursor> {
+ return undefined as any;
+ }
+
+ trashFindPaginatedDeletedAfter(
+ _deletedAt: Date,
+ _query?: Filter,
+ _options?: FindOptions,
+ ): FindPaginated>> {
+ return {
+ cursor: undefined as any,
+ totalCount: Promise.resolve(0),
+ };
+ }
+
+ watch(_pipeline?: object[]): ChangeStream {
+ return undefined as any;
+ }
+
+ async countDocuments(): Promise {
+ return 0;
+ }
+
+ async estimatedDocumentCount(): Promise {
+ return 0;
+ }
+}
diff --git a/apps/meteor/server/models/dummy/ReadReceipts.ts b/apps/meteor/server/models/dummy/ReadReceipts.ts
new file mode 100644
index 000000000000..90a5cbdf900f
--- /dev/null
+++ b/apps/meteor/server/models/dummy/ReadReceipts.ts
@@ -0,0 +1,58 @@
+import type { IUser, IMessage, ReadReceipt } from '@rocket.chat/core-typings';
+import type { IReadReceiptsModel } from '@rocket.chat/model-typings';
+import type { FindCursor, DeleteResult, Filter, UpdateResult, Document } from 'mongodb';
+
+import { BaseDummy } from './BaseDummy';
+
+export class ReadReceiptsDummy extends BaseDummy implements IReadReceiptsModel {
+ constructor() {
+ super('read_receipts');
+ }
+
+ findByMessageId(_messageId: string): FindCursor {
+ return this.find({});
+ }
+
+ removeByUserId(_userId: string): Promise {
+ return this.deleteMany({});
+ }
+
+ removeByRoomId(_roomId: string): Promise {
+ return this.deleteMany({});
+ }
+
+ removeByRoomIds(_roomIds: string[]): Promise {
+ return this.deleteMany({});
+ }
+
+ removeByMessageId(_messageId: string): Promise {
+ return this.deleteMany({});
+ }
+
+ removeByMessageIds(_messageIds: string[]): Promise {
+ return this.deleteMany({});
+ }
+
+ removeOTRReceiptsUntilDate(_roomId: string, _until: Date): Promise {
+ return this.deleteMany({});
+ }
+
+ async removeByIdPinnedTimestampLimitAndUsers(
+ _roomId: string,
+ _ignorePinned: boolean,
+ _ignoreDiscussion: boolean,
+ _ts: Filter['ts'],
+ _users: IUser['_id'][],
+ _ignoreThreads: boolean,
+ ): Promise {
+ return this.deleteMany({});
+ }
+
+ setPinnedByMessageId(_messageId: string, _pinned = true): Promise {
+ return this.updateMany({}, {});
+ }
+
+ setAsThreadById(_messageId: string): Promise {
+ return this.updateMany({}, {});
+ }
+}
diff --git a/apps/meteor/server/models/raw/LivechatCustomField.ts b/apps/meteor/server/models/raw/LivechatCustomField.ts
index 46bc32c52177..71228f55069d 100644
--- a/apps/meteor/server/models/raw/LivechatCustomField.ts
+++ b/apps/meteor/server/models/raw/LivechatCustomField.ts
@@ -1,6 +1,6 @@
import type { ILivechatCustomField, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { ILivechatCustomFieldModel } from '@rocket.chat/model-typings';
-import type { Db, Collection, IndexDescription, FindOptions, FindCursor } from 'mongodb';
+import type { Db, Collection, IndexDescription, FindOptions, FindCursor, Document } from 'mongodb';
import { BaseRaw } from './BaseRaw';
@@ -73,4 +73,12 @@ export class LivechatCustomFieldRaw extends BaseRaw implem
return record;
}
+
+ findByIdsAndScope(
+ ids: ILivechatCustomField['_id'][],
+ scope: ILivechatCustomField['scope'],
+ options?: FindOptions,
+ ): FindCursor {
+ return this.find({ _id: { $in: ids }, scope }, options);
+ }
}
diff --git a/apps/meteor/server/models/startup.ts b/apps/meteor/server/models/startup.ts
index 14b26e0f188f..3d6dc6066689 100644
--- a/apps/meteor/server/models/startup.ts
+++ b/apps/meteor/server/models/startup.ts
@@ -68,3 +68,4 @@ import './Imports';
import './AppsTokens';
import './CronHistory';
import './Migrations';
+import './ReadReceipts';
diff --git a/apps/meteor/server/services/apps-engine/service.ts b/apps/meteor/server/services/apps-engine/service.ts
index e72ce3cbce0a..41a53cf5bbb6 100644
--- a/apps/meteor/server/services/apps-engine/service.ts
+++ b/apps/meteor/server/services/apps-engine/service.ts
@@ -1,3 +1,4 @@
+import { Apps, AppEvents } from '@rocket.chat/apps';
import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus';
import type { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
@@ -6,7 +7,6 @@ import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage';
import type { IAppsEngineService } from '@rocket.chat/core-services';
import { ServiceClassInternal } from '@rocket.chat/core-services';
-import { Apps, AppEvents } from '../../../ee/server/apps/orchestrator';
import { SystemLogger } from '../../lib/logger/system';
export class AppsEngineService extends ServiceClassInternal implements IAppsEngineService {
@@ -16,7 +16,7 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi
super();
this.onEvent('presence.status', async ({ user, previousStatus }): Promise => {
- await Apps.triggerEvent(AppEvents.IPostUserStatusChanged, {
+ await Apps.self?.triggerEvent(AppEvents.IPostUserStatusChanged, {
user,
currentStatus: user.status,
previousStatus,
@@ -24,68 +24,72 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi
});
this.onEvent('apps.added', async (appId: string): Promise => {
- Apps.getRocketChatLogger().debug(`"apps.added" event received for app "${appId}"`);
+ Apps.self?.getRocketChatLogger().debug(`"apps.added" event received for app "${appId}"`);
// if the app already exists in this instance, don't load it again
- const app = Apps.getManager()?.getOneById(appId);
+ const app = Apps.self?.getManager()?.getOneById(appId);
if (app) {
- Apps.getRocketChatLogger().info(`"apps.added" event received for app "${appId}", but it already exists in this instance`);
+ Apps.self?.getRocketChatLogger().info(`"apps.added" event received for app "${appId}", but it already exists in this instance`);
return;
}
- await Apps.getManager()?.addLocal(appId);
+ await Apps.self?.getManager()?.addLocal(appId);
});
this.onEvent('apps.removed', async (appId: string): Promise => {
- Apps.getRocketChatLogger().debug(`"apps.removed" event received for app "${appId}"`);
- const app = Apps.getManager()?.getOneById(appId);
+ Apps.self?.getRocketChatLogger().debug(`"apps.removed" event received for app "${appId}"`);
+ const app = Apps.self?.getManager()?.getOneById(appId);
if (!app) {
- Apps.getRocketChatLogger().info(`"apps.removed" event received for app "${appId}", but it couldn't be found in this instance`);
+ Apps.self
+ ?.getRocketChatLogger()
+ .info(`"apps.removed" event received for app "${appId}", but it couldn't be found in this instance`);
return;
}
- await Apps.getManager()?.removeLocal(appId);
+ await Apps.self?.getManager()?.removeLocal(appId);
});
this.onEvent('apps.updated', async (appId: string): Promise => {
- Apps.getRocketChatLogger().debug(`"apps.updated" event received for app "${appId}"`);
- const storageItem = await Apps.getStorage()?.retrieveOne(appId);
+ Apps.self?.getRocketChatLogger().debug(`"apps.updated" event received for app "${appId}"`);
+ const storageItem = await Apps.self?.getStorage()?.retrieveOne(appId);
if (!storageItem) {
- Apps.getRocketChatLogger().info(`"apps.updated" event received for app "${appId}", but it couldn't be found in the storage`);
+ Apps.self?.getRocketChatLogger().info(`"apps.updated" event received for app "${appId}", but it couldn't be found in the storage`);
return;
}
- const appPackage = await Apps.getAppSourceStorage()?.fetch(storageItem);
+ const appPackage = await Apps.self?.getAppSourceStorage()?.fetch(storageItem);
if (!appPackage) {
return;
}
- await Apps.getManager()?.updateLocal(storageItem, appPackage);
+ await Apps.self?.getManager()?.updateLocal(storageItem, appPackage);
});
this.onEvent('apps.statusUpdate', async (appId: string, status: AppStatus): Promise => {
- Apps.getRocketChatLogger().debug(`"apps.statusUpdate" event received for app "${appId}" with status "${status}"`);
- const app = Apps.getManager()?.getOneById(appId);
+ Apps.self?.getRocketChatLogger().debug(`"apps.statusUpdate" event received for app "${appId}" with status "${status}"`);
+ const app = Apps.self?.getManager()?.getOneById(appId);
if (!app) {
- Apps.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but it couldn't be found in this instance`);
+ Apps.self
+ ?.getRocketChatLogger()
+ .info(`"apps.statusUpdate" event received for app "${appId}", but it couldn't be found in this instance`);
return;
}
if (app.getStatus() === status) {
- Apps.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but the status is the same`);
+ Apps.self?.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but the status is the same`);
return;
}
if (AppStatusUtils.isEnabled(status)) {
- await Apps.getManager()?.enable(appId).catch(SystemLogger.error);
+ await Apps.self?.getManager()?.enable(appId).catch(SystemLogger.error);
} else if (AppStatusUtils.isDisabled(status)) {
- await Apps.getManager()?.disable(appId, status, true).catch(SystemLogger.error);
+ await Apps.self?.getManager()?.disable(appId, status, true).catch(SystemLogger.error);
}
});
this.onEvent('apps.settingUpdated', async (appId: string, setting): Promise => {
- Apps.getRocketChatLogger().debug(`"apps.settingUpdated" event received for app "${appId}"`, { setting });
- const app = Apps.getManager()?.getOneById(appId);
+ Apps.self?.getRocketChatLogger().debug(`"apps.settingUpdated" event received for app "${appId}"`, { setting });
+ const app = Apps.self?.getManager()?.getOneById(appId);
const oldSetting = app?.getStorageItem().settings[setting.id].value;
// avoid updating the setting if the value is the same,
@@ -94,30 +98,32 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi
// so we need to convert it to JSON stringified to compare it
if (JSON.stringify(oldSetting) === JSON.stringify(setting.value)) {
- Apps.getRocketChatLogger().info(
- `"apps.settingUpdated" event received for setting ${setting.id} of app "${appId}", but the setting value is the same`,
- );
+ Apps.self
+ ?.getRocketChatLogger()
+ .info(`"apps.settingUpdated" event received for setting ${setting.id} of app "${appId}", but the setting value is the same`);
return;
}
- await Apps.getManager()
+ await Apps.self
+ ?.getManager()
?.getSettingsManager()
.updateAppSetting(appId, setting as any);
});
}
isInitialized(): boolean {
- return Apps.isInitialized();
+ return Boolean(Apps.self?.isInitialized());
}
async getApps(query: IGetAppsFilter): Promise {
- return Apps.getManager()
+ return Apps.self
+ ?.getManager()
?.get(query)
.map((app) => app.getApp().getInfo());
}
async getAppStorageItemById(appId: string): Promise {
- const app = Apps.getManager()?.getOneById(appId);
+ const app = Apps.self?.getManager()?.getOneById(appId);
if (!app) {
return;
diff --git a/apps/meteor/server/services/omnichannel/queue.ts b/apps/meteor/server/services/omnichannel/queue.ts
index 8a902b75da3c..642575c06db5 100644
--- a/apps/meteor/server/services/omnichannel/queue.ts
+++ b/apps/meteor/server/services/omnichannel/queue.ts
@@ -1,4 +1,5 @@
-import type { InquiryWithAgentInfo, IOmnichannelQueue } from '@rocket.chat/core-typings';
+import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
+import { type InquiryWithAgentInfo, type IOmnichannelQueue } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import { LivechatInquiry, LivechatRooms } from '@rocket.chat/models';
@@ -98,9 +99,11 @@ export class OmnichannelQueue implements IOmnichannelQueue {
// Note: this removes the "one-shot" behavior of queue, allowing it to take a conversation again in the future
// And sorting them by _updatedAt: -1 will make it so that the oldest inquiries are taken first
// preventing us from playing with the same inquiry over and over again
+ queueLogger.debug(`Inquiry ${nextInquiry._id} not taken. Unlocking and re-queueing`);
return await LivechatInquiry.unlockAndQueue(nextInquiry._id);
}
+ queueLogger.debug(`Inquiry ${nextInquiry._id} taken successfully. Unlocking`);
await LivechatInquiry.unlock(nextInquiry._id);
queueLogger.debug({
msg: 'Inquiry processed',
@@ -135,26 +138,74 @@ export class OmnichannelQueue implements IOmnichannelQueue {
void (routingSupportsAutoAssign ? this.start() : this.stop());
}
+ private async reconciliation(reason: 'closed' | 'taken' | 'missing', { roomId, inquiryId }: { roomId: string; inquiryId: string }) {
+ switch (reason) {
+ case 'closed': {
+ queueLogger.debug({
+ msg: 'Room closed. Removing inquiry',
+ roomId,
+ inquiryId,
+ step: 'reconciliation',
+ });
+ await LivechatInquiry.removeByRoomId(roomId);
+ break;
+ }
+ case 'taken': {
+ queueLogger.debug({
+ msg: 'Room taken. Updating inquiry status',
+ roomId,
+ inquiryId,
+ step: 'reconciliation',
+ });
+ // Reconciliate served inquiries, by updating their status to taken after queue tried to pick and failed
+ await LivechatInquiry.takeInquiry(inquiryId);
+ break;
+ }
+ case 'missing': {
+ queueLogger.debug({
+ msg: 'Room from inquiry missing. Removing inquiry',
+ roomId,
+ inquiryId,
+ step: 'reconciliation',
+ });
+ await LivechatInquiry.removeByRoomId(roomId);
+ break;
+ }
+ default: {
+ return true;
+ }
+ }
+
+ return true;
+ }
+
private async processWaitingQueue(department: string | undefined, inquiry: InquiryWithAgentInfo) {
const queue = department || 'Public';
- queueLogger.debug(`Processing items on queue ${queue}`);
queueLogger.debug(`Processing inquiry ${inquiry._id} from queue ${queue}`);
const { defaultAgent } = inquiry;
- const roomFromDb = await LivechatRooms.findOneById(inquiry.rid, { projection: { servedBy: 1 } });
+ const roomFromDb = await LivechatRooms.findOneById>(inquiry.rid, {
+ projection: { servedBy: 1, closedAt: 1 },
+ });
+
+ // This is a precaution to avoid taking inquiries tied to rooms that no longer exist.
+ // This should never happen.
+ if (!roomFromDb) {
+ return this.reconciliation('missing', { roomId: inquiry.rid, inquiryId: inquiry._id });
+ }
// This is a precaution to avoid taking the same inquiry multiple times. It should not happen, but it's a safety net
- if (roomFromDb?.servedBy) {
- queueLogger.debug(`Inquiry ${inquiry._id} already taken by agent ${roomFromDb.servedBy._id}. Skipping`);
- return true;
+ if (roomFromDb.servedBy) {
+ return this.reconciliation('taken', { roomId: inquiry.rid, inquiryId: inquiry._id });
}
- const room = await RoutingManager.delegateInquiry(inquiry, defaultAgent);
+ // This is another precaution. If the room is closed, we should not take it
+ if (roomFromDb.closedAt) {
+ return this.reconciliation('closed', { roomId: inquiry.rid, inquiryId: inquiry._id });
+ }
- const propagateAgentDelegated = async (rid: string, agentId: string) => {
- await dispatchAgentDelegated(rid, agentId);
- };
+ const room = await RoutingManager.delegateInquiry(inquiry, defaultAgent);
if (room?.servedBy) {
const {
@@ -163,13 +214,12 @@ export class OmnichannelQueue implements IOmnichannelQueue {
} = room;
queueLogger.debug(`Inquiry ${inquiry._id} taken successfully by agent ${agentId}. Notifying`);
setTimeout(() => {
- void propagateAgentDelegated(rid, agentId);
+ void dispatchAgentDelegated(rid, agentId);
}, 1000);
return true;
}
- queueLogger.debug(`Inquiry ${inquiry._id} not taken by any agent. Queueing again`);
return false;
}
}
diff --git a/apps/meteor/server/services/video-conference/service.ts b/apps/meteor/server/services/video-conference/service.ts
index 90a7a3302427..7c7d5950cf5f 100644
--- a/apps/meteor/server/services/video-conference/service.ts
+++ b/apps/meteor/server/services/video-conference/service.ts
@@ -1,3 +1,4 @@
+import { Apps } from '@rocket.chat/apps';
import type { AppVideoConfProviderManager } from '@rocket.chat/apps-engine/server/managers';
import type { IVideoConfService, VideoConferenceJoinOptions } from '@rocket.chat/core-services';
import { api, ServiceClassInternal } from '@rocket.chat/core-services';
@@ -41,7 +42,6 @@ import { settings } from '../../../app/settings/server';
import { updateCounter } from '../../../app/statistics/server/functions/updateStatsCounter';
import { getUserAvatarURL } from '../../../app/utils/server/getUserAvatarURL';
import { getUserPreference } from '../../../app/utils/server/lib/getUserPreference';
-import { Apps } from '../../../ee/server/apps';
import { callbacks } from '../../../lib/callbacks';
import { availabilityErrors } from '../../../lib/videoConference/constants';
import { readSecondaryPreferred } from '../../database/readSecondaryPreferred';
@@ -828,11 +828,11 @@ export class VideoConfService extends ServiceClassInternal implements IVideoConf
}
private async getProviderManager(): Promise {
- if (!Apps?.isLoaded()) {
+ if (!Apps.self?.isLoaded()) {
throw new Error('apps-engine-not-loaded');
}
- const manager = Apps.getManager()?.getVideoConfProviderManager();
+ const manager = Apps.self?.getManager()?.getVideoConfProviderManager();
if (!manager) {
throw new Error(availabilityErrors.NO_APP);
}
diff --git a/apps/meteor/server/startup/initialData.js b/apps/meteor/server/startup/initialData.js
index 01a808a3c103..8e008191ea44 100644
--- a/apps/meteor/server/startup/initialData.js
+++ b/apps/meteor/server/startup/initialData.js
@@ -12,6 +12,81 @@ import { settings } from '../../app/settings/server';
import { validateEmail } from '../../lib/emailValidator';
import { addUserRolesAsync } from '../lib/roles/addUserRoles';
+export async function insertAdminUserFromEnv() {
+ if (process.env.ADMIN_PASS) {
+ if ((await (await getUsersInRole('admin')).count()) === 0) {
+ const adminUser = {
+ name: 'Administrator',
+ username: 'admin',
+ status: 'offline',
+ statusDefault: 'online',
+ utcOffset: 0,
+ active: true,
+ };
+
+ if (process.env.ADMIN_NAME) {
+ adminUser.name = process.env.ADMIN_NAME;
+ }
+
+ console.log(colors.green(`Name: ${adminUser.name}`));
+
+ if (process.env.ADMIN_EMAIL) {
+ if (validateEmail(process.env.ADMIN_EMAIL)) {
+ if (!(await Users.findOneByEmailAddress(process.env.ADMIN_EMAIL))) {
+ adminUser.emails = [
+ {
+ address: process.env.ADMIN_EMAIL,
+ verified: process.env.ADMIN_EMAIL_VERIFIED === 'true',
+ },
+ ];
+
+ console.log(colors.green(`Email: ${process.env.ADMIN_EMAIL}`));
+ } else {
+ console.log(colors.red('Email provided already exists; Ignoring environment variables ADMIN_EMAIL'));
+ }
+ } else {
+ console.log(colors.red('Email provided is invalid; Ignoring environment variables ADMIN_EMAIL'));
+ }
+ }
+
+ if (process.env.ADMIN_USERNAME) {
+ let nameValidation;
+
+ try {
+ nameValidation = new RegExp(`^${settings.get('UTF8_User_Names_Validation')}$`);
+ } catch (error) {
+ nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
+ }
+
+ if (nameValidation.test(process.env.ADMIN_USERNAME)) {
+ try {
+ await checkUsernameAvailability(process.env.ADMIN_USERNAME);
+ adminUser.username = process.env.ADMIN_USERNAME;
+ } catch (error) {
+ console.log(
+ colors.red('Username provided already exists or is blocked from usage; Ignoring environment variables ADMIN_USERNAME'),
+ );
+ }
+ } else {
+ console.log(colors.red('Username provided is invalid; Ignoring environment variables ADMIN_USERNAME'));
+ }
+ }
+
+ console.log(colors.green(`Username: ${adminUser.username}`));
+
+ adminUser.type = 'user';
+
+ const { insertedId: userId } = await Users.create(adminUser);
+
+ await Accounts.setPasswordAsync(userId, process.env.ADMIN_PASS);
+
+ await addUserRolesAsync(userId, ['admin']);
+ } else {
+ console.log(colors.red('Users with admin role already exist; Ignoring environment variables ADMIN_PASS'));
+ }
+ }
+}
+
Meteor.startup(async () => {
const dynamicImport = {
'dynamic-import': {
@@ -91,76 +166,7 @@ Meteor.startup(async () => {
throw error;
}
- if (process.env.ADMIN_PASS) {
- if ((await (await getUsersInRole('admin')).count()) === 0) {
- console.log(colors.green('Inserting admin user:'));
- const adminUser = {
- name: 'Administrator',
- username: 'admin',
- status: 'offline',
- statusDefault: 'online',
- utcOffset: 0,
- active: true,
- };
-
- if (process.env.ADMIN_NAME) {
- adminUser.name = process.env.ADMIN_NAME;
- }
-
- console.log(colors.green(`Name: ${adminUser.name}`));
-
- if (process.env.ADMIN_EMAIL) {
- if (validateEmail(process.env.ADMIN_EMAIL)) {
- if (!(await Users.findOneByEmailAddress(process.env.ADMIN_EMAIL))) {
- adminUser.emails = [
- {
- address: process.env.ADMIN_EMAIL,
- verified: process.env.ADMIN_EMAIL_VERIFIED === 'true',
- },
- ];
-
- console.log(colors.green(`Email: ${process.env.ADMIN_EMAIL}`));
- } else {
- console.log(colors.red('Email provided already exists; Ignoring environment variables ADMIN_EMAIL'));
- }
- } else {
- console.log(colors.red('Email provided is invalid; Ignoring environment variables ADMIN_EMAIL'));
- }
- }
-
- if (process.env.ADMIN_USERNAME) {
- let nameValidation;
-
- try {
- nameValidation = new RegExp(`^${settings.get('UTF8_User_Names_Validation')}$`);
- } catch (error) {
- nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$');
- }
-
- if (nameValidation.test(process.env.ADMIN_USERNAME)) {
- if (await checkUsernameAvailability(process.env.ADMIN_USERNAME)) {
- adminUser.username = process.env.ADMIN_USERNAME;
- } else {
- console.log(colors.red('Username provided already exists; Ignoring environment variables ADMIN_USERNAME'));
- }
- } else {
- console.log(colors.red('Username provided is invalid; Ignoring environment variables ADMIN_USERNAME'));
- }
- }
-
- console.log(colors.green(`Username: ${adminUser.username}`));
-
- adminUser.type = 'user';
-
- const { insertedId: userId } = await Users.create(adminUser);
-
- await Accounts.setPasswordAsync(userId, process.env.ADMIN_PASS);
-
- await addUserRolesAsync(userId, ['admin']);
- } else {
- console.log(colors.red('Users with admin role already exist; Ignoring environment variables ADMIN_PASS'));
- }
- }
+ await insertAdminUserFromEnv();
if (typeof process.env.INITIAL_USER === 'string' && process.env.INITIAL_USER.length > 0) {
try {
diff --git a/apps/meteor/server/startup/migrations/v291.ts b/apps/meteor/server/startup/migrations/v291.ts
index 8923f3b282c3..3f94c26dc6e0 100644
--- a/apps/meteor/server/startup/migrations/v291.ts
+++ b/apps/meteor/server/startup/migrations/v291.ts
@@ -1,8 +1,7 @@
+import { Apps, type AppMetadataStorage } from '@rocket.chat/apps';
import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage';
import { Settings } from '@rocket.chat/models';
-import { Apps } from '../../../ee/server/apps';
-import type { AppRealStorage } from '../../../ee/server/apps/storage';
import { addMigration } from '../../lib/migrations';
addMigration({
@@ -13,13 +12,17 @@ addMigration({
await Settings.removeById('Apps_Framework_Development_Mode');
await Settings.removeById('Apps_Framework_enabled');
+ if (!Apps.self) {
+ throw new Error('Apps Orchestrator not registered.');
+ }
+
Apps.initialize();
- const appsStorage = Apps.getStorage() as AppRealStorage;
+ const appsStorage = Apps.getStorage();
const apps = await appsStorage.retrieveAll();
- const promises: Array> = [];
+ const promises: Array> = [];
apps.forEach((app) =>
promises.push(
diff --git a/apps/meteor/server/startup/migrations/v292.ts b/apps/meteor/server/startup/migrations/v292.ts
index 7f590f4038e2..ac523f1b197e 100644
--- a/apps/meteor/server/startup/migrations/v292.ts
+++ b/apps/meteor/server/startup/migrations/v292.ts
@@ -1,7 +1,7 @@
+import { Apps } from '@rocket.chat/apps';
import type { AppSignatureManager } from '@rocket.chat/apps-engine/server/managers/AppSignatureManager';
import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage';
-import { Apps } from '../../../ee/server/apps';
import type { AppRealStorage } from '../../../ee/server/apps/storage';
import { addMigration } from '../../lib/migrations';
@@ -9,6 +9,10 @@ addMigration({
version: 292,
name: 'Add checksum signature to existing apps',
async up() {
+ if (!Apps.self) {
+ throw new Error('Apps Orchestrator not registered.');
+ }
+
Apps.initialize();
const sigMan = Apps.getManager()?.getSignatureManager() as AppSignatureManager;
diff --git a/apps/meteor/server/startup/migrations/v294.ts b/apps/meteor/server/startup/migrations/v294.ts
index abcb20d079ec..8523db89e4b9 100644
--- a/apps/meteor/server/startup/migrations/v294.ts
+++ b/apps/meteor/server/startup/migrations/v294.ts
@@ -1,13 +1,17 @@
+import { Apps } from '@rocket.chat/apps';
import type { AppSignatureManager } from '@rocket.chat/apps-engine/server/managers/AppSignatureManager';
import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage';
-import { Apps } from '../../../ee/server/apps';
import type { AppRealStorage } from '../../../ee/server/apps/storage';
import { addMigration } from '../../lib/migrations';
addMigration({
version: 294,
async up() {
+ if (!Apps.self) {
+ throw new Error('Apps Orchestrator not registered.');
+ }
+
Apps.initialize();
const sigMan = Apps.getManager()?.getSignatureManager() as AppSignatureManager;
diff --git a/apps/meteor/tests/data/livechat/department.ts b/apps/meteor/tests/data/livechat/department.ts
index 3d18f9c394b9..ba0df137b567 100644
--- a/apps/meteor/tests/data/livechat/department.ts
+++ b/apps/meteor/tests/data/livechat/department.ts
@@ -3,7 +3,7 @@ import { expect } from 'chai';
import type { ILivechatDepartment, IUser, LivechatDepartmentDTO } from '@rocket.chat/core-typings';
import { api, credentials, methodCall, request } from '../api-data';
import { IUserCredentialsHeader } from '../user';
-import { createAnOnlineAgent } from './users';
+import { createAnOnlineAgent, createAnOfflineAgent } from './users';
import { WithRequiredProperty } from './utils';
export const NewDepartmentData = ((): Partial => ({
@@ -29,7 +29,9 @@ export const updateDepartment = async (departmentId: string, departmentData: Par
return response.body.department;
};
-export const createDepartmentWithMethod = (initialAgents: { agentId: string, username: string }[] = []) =>
+export const createDepartmentWithMethod = (
+ initialAgents: { agentId: string, username: string }[] = [],
+ allowReceiveForwardOffline = false) =>
new Promise((resolve, reject) => {
request
.post(methodCall('livechat:saveDepartment'))
@@ -37,14 +39,19 @@ new Promise((resolve, reject) => {
.send({
message: JSON.stringify({
method: 'livechat:saveDepartment',
- params: ['', {
- enabled: true,
- email: faker.internet.email(),
- showOnRegistration: true,
- showOnOfflineForm: true,
- name: `new department ${Date.now()}`,
- description: 'created from api',
- }, initialAgents],
+ params: [
+ '',
+ {
+ enabled: true,
+ email: faker.internet.email(),
+ showOnRegistration: true,
+ showOnOfflineForm: true,
+ name: `new department ${Date.now()}`,
+ description: 'created from api',
+ allowReceiveForwardOffline,
+ },
+ initialAgents,
+ ],
id: 'id',
msg: 'method',
}),
@@ -102,6 +109,31 @@ export const addOrRemoveAgentFromDepartment = async (departmentId: string, agent
throw new Error('Failed to add or remove agent from department. Status code: ' + response.status + '\n' + response.body);
}
}
+export const createDepartmentWithAnOfflineAgent = async ({
+ allowReceiveForwardOffline = false,
+}: {
+ allowReceiveForwardOffline: boolean;
+}): Promise<{
+ department: ILivechatDepartment;
+ agent: {
+ credentials: IUserCredentialsHeader;
+ user: WithRequiredProperty;
+ };
+}> => {
+ const { user, credentials } = await createAnOfflineAgent();
+
+ const department = (await createDepartmentWithMethod(undefined, allowReceiveForwardOffline)) as ILivechatDepartment;
+
+ await addOrRemoveAgentFromDepartment(department._id, { agentId: user._id, username: user.username }, true);
+
+ return {
+ department,
+ agent: {
+ credentials,
+ user,
+ },
+ };
+};
export const archiveDepartment = async (departmentId: string): Promise => {
await request.post(api(`livechat/department/${ departmentId }/archive`)).set(credentials).expect(200);
diff --git a/apps/meteor/tests/data/livechat/users.ts b/apps/meteor/tests/data/livechat/users.ts
index 3a21cbee923c..161c20749b6c 100644
--- a/apps/meteor/tests/data/livechat/users.ts
+++ b/apps/meteor/tests/data/livechat/users.ts
@@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker";
import type { ILivechatAgent, IUser } from "@rocket.chat/core-typings";
import { IUserCredentialsHeader, password } from "../user";
import { createUser, login } from "../users.helper";
-import { createAgent, makeAgentAvailable } from "./rooms";
+import { createAgent, makeAgentAvailable, makeAgentUnavailable } from "./rooms";
import { api, credentials, request } from "../api-data";
export const createBotAgent = async (): Promise<{
@@ -57,3 +57,21 @@ export const createAnOnlineAgent = async (): Promise<{
user: agent,
};
}
+
+export const createAnOfflineAgent = async (): Promise<{
+ credentials: IUserCredentialsHeader;
+ user: IUser & { username: string };
+}> => {
+ const username = `user.test.${Date.now()}.offline`;
+ const email = `${username}.offline@rocket.chat`;
+ const { body } = await request.post(api('users.create')).set(credentials).send({ email, name: username, username, password });
+ const agent = body.user;
+ const createdUserCredentials = await login(agent.username, password);
+ await createAgent(agent.username);
+ await makeAgentUnavailable(createdUserCredentials);
+
+ return {
+ credentials: createdUserCredentials,
+ user: agent,
+ };
+};
\ No newline at end of file
diff --git a/apps/meteor/tests/e2e/administration.spec.ts b/apps/meteor/tests/e2e/administration.spec.ts
index dcfb85373186..23d9d5214aaf 100644
--- a/apps/meteor/tests/e2e/administration.spec.ts
+++ b/apps/meteor/tests/e2e/administration.spec.ts
@@ -123,6 +123,21 @@ test.describe.parallel('administration', () => {
await poAdmin.getRoomRow(targetChannel).click();
await expect(poAdmin.favoriteInput).toBeChecked();
});
+
+ test('should see favorite switch disabled when default is not true', async () => {
+ await poAdmin.inputSearchRooms.type(targetChannel);
+ await poAdmin.getRoomRow(targetChannel).click();
+ await poAdmin.defaultLabel.click();
+
+ await expect(poAdmin.favoriteInput).toBeDisabled();
+ });
+
+ test('should see favorite switch enabled when default is true', async () => {
+ await poAdmin.inputSearchRooms.type(targetChannel);
+ await poAdmin.getRoomRow(targetChannel).click();
+
+ await expect(poAdmin.favoriteInput).toBeEnabled();
+ });
});
});
diff --git a/apps/meteor/tests/e2e/channel-management.spec.ts b/apps/meteor/tests/e2e/channel-management.spec.ts
index 0586598a5dda..86bab6981346 100644
--- a/apps/meteor/tests/e2e/channel-management.spec.ts
+++ b/apps/meteor/tests/e2e/channel-management.spec.ts
@@ -130,6 +130,7 @@ test.describe.serial('channel-management', () => {
await poHomeChannel.tabs.room.btnSave.click();
targetChannel = `NAME-EDITED-${targetChannel}`;
+ await expect(page.locator(`role=main >> role=heading[name="${targetChannel}"]`)).toBeVisible();
await poHomeChannel.sidenav.openChat(targetChannel);
await expect(page).toHaveURL(`/channel/${targetChannel}`);
@@ -162,7 +163,7 @@ test.describe.serial('channel-management', () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.content.btnMenuMoreActions.click();
await page.getByRole('menuitem', { name: 'Discussion' }).click();
- await page.getByRole('textbox', { name: 'Discussion name' }).fill(discussionName);
+ await page.getByRole('textbox', { name: 'Name' }).fill(discussionName);
await page.getByRole('button', { name: 'Create' }).click();
await expect(page.getByRole('heading', { name: discussionName })).toBeVisible();
diff --git a/apps/meteor/tests/e2e/create-direct.spec.ts b/apps/meteor/tests/e2e/create-direct.spec.ts
index 4e74840619ba..1b4e2d7c752d 100644
--- a/apps/meteor/tests/e2e/create-direct.spec.ts
+++ b/apps/meteor/tests/e2e/create-direct.spec.ts
@@ -14,7 +14,7 @@ test.describe.serial('channel-direct-message', () => {
});
test('expect create a direct room', async ({ page }) => {
- await poHomeChannel.sidenav.openNewByLabel('Direct messages');
+ await poHomeChannel.sidenav.openNewByLabel('Direct message');
await poHomeChannel.sidenav.inputDirectUsername.click();
await page.keyboard.type('rocket.cat');
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts
index b66ac538d5c2..d0e7b7133423 100644
--- a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts
@@ -66,8 +66,8 @@ test.describe('Omnichannel Contact Center', () => {
test.afterAll(async ({ api }) => {
// Remove added contacts
- await api.delete('/livechat/visitor', { token: EXISTING_CONTACT.token });
- await api.delete('/livechat/visitor', { token: NEW_CONTACT.token });
+ await api.delete(`/livechat/visitor/${EXISTING_CONTACT.token}`);
+ await api.delete(`/livechat/visitor/${NEW_CONTACT.token}`);
if (IS_EE) {
await api.post('method.call/livechat:removeCustomField', { message: NEW_CUSTOM_FIELD.field });
}
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts
index 50fbd69d9b79..807e00e61615 100644
--- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-api.spec.ts
@@ -70,7 +70,6 @@ test.describe('OC - Livechat API', () => {
agent = await createAgent(api, 'user1');
page = await browser.newPage();
- await expect((await api.post('/settings/Enable_CSP', { value: false })).status()).toBe(200);
poLiveChat = new OmnichannelLiveChatEmbedded(page);
@@ -80,8 +79,7 @@ test.describe('OC - Livechat API', () => {
await page.goto('/packages/rocketchat_livechat/assets/demo.html');
});
- test.afterAll(async ({ api }) => {
- await expect((await api.post('/settings/Enable_CSP', { value: true })).status()).toBe(200);
+ test.afterAll(async () => {
await agent.delete();
await poAuxContext.page.close();
await page.close();
@@ -220,6 +218,7 @@ test.describe('OC - Livechat API', () => {
let agent: Awaited>;
let agent2: Awaited>;
let departments: Awaited>[];
+ let pageContext: Page;
test.beforeAll(async ({ api }) => {
agent = await createAgent(api, 'user1');
@@ -230,8 +229,6 @@ test.describe('OC - Livechat API', () => {
await addAgentToDepartment(api, { department: departmentA, agentId: agent.data._id });
await addAgentToDepartment(api, { department: departmentB, agentId: agent2.data._id });
-
- await expect((await api.post('/settings/Enable_CSP', { value: false })).status()).toBe(200);
await expect((await api.post('/settings/Livechat_offline_email', { value: 'test@testing.com' })).status()).toBe(200);
});
@@ -262,10 +259,10 @@ test.describe('OC - Livechat API', () => {
await poAuxContext.page.close();
await page.close();
await poAuxContext2?.page.close();
+ await pageContext?.close();
});
test.afterAll(async ({ api }) => {
- await expect((await api.post('/settings/Enable_CSP', { value: true })).status()).toBe(200);
await agent.delete();
await agent2.delete();
@@ -388,20 +385,111 @@ test.describe('OC - Livechat API', () => {
});
await test.step('Expect registerGuest to log in an existing guest and load chat history', async () => {
- const { page: pageCtx } = await createAuxContext(browser, Users.user1);
+ ({ page: pageContext } = await createAuxContext(browser, Users.user1));
- await pageCtx.goto('/packages/rocketchat_livechat/assets/demo.html');
+ await pageContext.goto('/packages/rocketchat_livechat/assets/demo.html');
- await pageCtx.evaluate(() => window.RocketChat.livechat.maximizeWidget());
- await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();
+ await pageContext.evaluate(() => window.RocketChat.livechat.maximizeWidget());
+ await expect(pageContext.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();
- await pageCtx.evaluate(
+ await pageContext.evaluate(
(registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor),
registerGuestVisitor,
);
- await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
- await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('this_a_test_message_from_visitor')).toBeVisible();
+ await expect(pageContext.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
+ await expect(pageContext.frameLocator('#rocketchat-iframe').getByText('this_a_test_message_from_visitor')).toBeVisible();
+ });
+ });
+
+ test('OC - Livechat API - registerGuest different guests', async () => {
+ const registerGuestVisitor1 = {
+ name: faker.person.firstName(),
+ email: faker.internet.email(),
+ token: faker.string.uuid(),
+ };
+
+ const registerGuestVisitor2 = {
+ name: faker.person.firstName(),
+ email: faker.internet.email(),
+ token: faker.string.uuid(),
+ };
+
+ await test.step('Expect registerGuest to create guest 1', async () => {
+ await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget());
+ await expect(poLiveChat.page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();
+
+ await poLiveChat.page.evaluate(
+ (registerGuestVisitor1) => window.RocketChat.livechat.registerGuest(registerGuestVisitor1),
+ registerGuestVisitor1,
+ );
+
+ await expect(poLiveChat.page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
+
+ await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor_1');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+
+ await expect(poLiveChat.txtChatMessage('this_a_test_message_from_visitor_1')).toBeVisible();
+ });
+
+ await test.step('Expect registerGuest to create guest 2', async () => {
+ await poLiveChat.page.evaluate(
+ (registerGuestVisitor2) => window.RocketChat.livechat.registerGuest(registerGuestVisitor2),
+ registerGuestVisitor2,
+ );
+
+ await poLiveChat.page.frameLocator('#rocketchat-iframe').getByText('this_a_test_message_from_visitor').waitFor({ state: 'hidden' });
+
+ await expect(poLiveChat.page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
+
+ await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor_2');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+
+ await poLiveChat.txtChatMessage('this_a_test_message_from_visitor_2').waitFor({ state: 'visible' });
+ await expect(poLiveChat.txtChatMessage('this_a_test_message_from_visitor_2')).toBeVisible();
+ });
+ });
+
+ test('OC - Livechat API - registerGuest multiple times', async () => {
+ const registerGuestVisitor = {
+ name: faker.person.firstName(),
+ email: faker.internet.email(),
+ token: faker.string.uuid(),
+ };
+
+ await test.step('Expect registerGuest work with the same token, multiple times', async () => {
+ await poLiveChat.page.evaluate(() => window.RocketChat.livechat.maximizeWidget());
+ await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();
+
+ await poLiveChat.page.evaluate(
+ (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor),
+ registerGuestVisitor,
+ );
+
+ await expect(page.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
+
+ await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+
+ await expect(poLiveChat.txtChatMessage('this_a_test_message_from_visitor')).toBeVisible();
+
+ await poLiveChat.page.evaluate(
+ (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor),
+ registerGuestVisitor,
+ );
+
+ await page.waitForTimeout(500);
+
+ await expect(poLiveChat.txtChatMessage('this_a_test_message_from_visitor')).toBeVisible();
+
+ await poLiveChat.page.evaluate(
+ (registerGuestVisitor) => window.RocketChat.livechat.registerGuest(registerGuestVisitor),
+ registerGuestVisitor,
+ );
+
+ await page.waitForTimeout(500);
+
+ await expect(poLiveChat.txtChatMessage('this_a_test_message_from_visitor')).toBeVisible();
});
});
@@ -498,20 +586,20 @@ test.describe('OC - Livechat API', () => {
await poLiveChat.btnSendMessageToOnlineAgent.click();
await test.step('Expect setGuestToken to log in an existing guest and load chat history', async () => {
- const { page: pageCtx } = await createAuxContext(browser, Users.user1);
+ ({ page: pageContext } = await createAuxContext(browser, Users.user1));
- await pageCtx.goto('/packages/rocketchat_livechat/assets/demo.html');
+ await pageContext.goto('/packages/rocketchat_livechat/assets/demo.html');
- await pageCtx.evaluate(() => window.RocketChat.livechat.maximizeWidget());
- await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();
+ await pageContext.evaluate(() => window.RocketChat.livechat.maximizeWidget());
+ await expect(pageContext.frameLocator('#rocketchat-iframe').getByText('Start Chat')).toBeVisible();
- await pageCtx.evaluate(
+ await pageContext.evaluate(
(registerGuestVisitor) => window.RocketChat.livechat.setGuestToken(registerGuestVisitor.token),
registerGuestVisitor,
);
- await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
- await expect(pageCtx.frameLocator('#rocketchat-iframe').getByText('this_a_test_message_from_visitor')).toBeVisible();
+ await expect(pageContext.frameLocator('#rocketchat-iframe').getByText('Start Chat')).not.toBeVisible();
+ await expect(pageContext.frameLocator('#rocketchat-iframe').getByText('this_a_test_message_from_visitor')).toBeVisible();
});
});
});
@@ -526,7 +614,6 @@ test.describe('OC - Livechat API', () => {
test.beforeAll(async ({ api }) => {
agent = await createAgent(api, 'user1');
- await expect((await api.post('/settings/Enable_CSP', { value: false })).status()).toBe(200);
await expect((await api.post('/settings/Livechat_offline_email', { value: 'test@testing.com' })).status()).toBe(200);
});
@@ -553,8 +640,7 @@ test.describe('OC - Livechat API', () => {
await page.close();
});
- test.afterAll(async ({ api }) => {
- await expect((await api.post('/settings/Enable_CSP', { value: true })).status()).toBe(200);
+ test.afterAll(async () => {
await agent.delete();
});
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-department.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-department.spec.ts
new file mode 100644
index 000000000000..c0b2bf8ae852
--- /dev/null
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-department.spec.ts
@@ -0,0 +1,164 @@
+import { faker } from '@faker-js/faker';
+
+import { IS_EE } from '../config/constants';
+import { createAuxContext } from '../fixtures/createAuxContext';
+import { Users } from '../fixtures/userStates';
+import { HomeOmnichannel, OmnichannelLiveChat } from '../page-objects';
+import { createAgent } from '../utils/omnichannel/agents';
+import { addAgentToDepartment, createDepartment } from '../utils/omnichannel/departments';
+import { test, expect } from '../utils/test';
+
+
+
+test.use({ storageState: Users.user1.state });
+
+test.describe('OC - Livechat - Department Flow', () => {
+ // Needs Departments to test this, so needs an EE license for multiple deps
+ test.skip(!IS_EE, 'Enterprise Only');
+
+ let poLiveChat: OmnichannelLiveChat;
+ let poHomeOmnichannelAgent1: HomeOmnichannel;
+ let poHomeOmnichannelAgent2: HomeOmnichannel;
+ let departments: Awaited>[];
+ let departmentA: Awaited>['data'];
+ let departmentB: Awaited>['data'];
+ let agents: Awaited>[];
+ let agent1: Awaited>['data'];
+ let agent2: Awaited>['data'];
+
+ test.beforeAll(async ({ api }) => {
+ // Assign agents & departments
+ agents = await Promise.all([createAgent(api, 'user1'), createAgent(api, 'user2')]);
+ [agent1, agent2] = agents.map(({ data }) => data);
+ departments = await Promise.all([
+ createDepartment(api, { showOnRegistration: true }),
+ createDepartment(api, { showOnRegistration: true }),
+ ]);
+ [departmentA, departmentB] = departments.map(({ data }) => data);
+ await addAgentToDepartment(api, { department: departmentA, agentId: agent1._id });
+ await addAgentToDepartment(api, { department: departmentB, agentId: agent2._id });
+ });
+
+ test.beforeEach(async ({ page, api, browser }) => {
+ // Create Pages
+ const { page: agent1Page } = await createAuxContext(browser, Users.user1, '/', true);
+ poHomeOmnichannelAgent1 = new HomeOmnichannel(agent1Page);
+ const { page: agent2Page } = await createAuxContext(browser, Users.user2, '/', true);
+ poHomeOmnichannelAgent2 = new HomeOmnichannel(agent2Page);
+
+ poLiveChat = new OmnichannelLiveChat(page, api);
+ await poLiveChat.page.goto('/livechat');
+ });
+
+ test.afterEach(async ({ page }) => {
+ await poHomeOmnichannelAgent1?.page?.close();
+ await poHomeOmnichannelAgent2?.page?.close();
+ await page.close();
+ });
+
+ test.afterAll(async ({ api }) => {
+ await expect((await api.post('/settings/Omnichannel_enable_department_removal', { value: true })).status()).toBe(200);
+ await Promise.all([...agents.map((agent) => agent.delete())]);
+ await Promise.all([...departments.map((department) => department.delete())]);
+ await expect((await api.post('/settings/Omnichannel_enable_department_removal', { value: false })).status()).toBe(200);
+ });
+
+ test('OC - Livechat - Chat with Department', async () => {
+
+ const guest = {
+ name: `${faker.person.firstName()} ${faker.string.nanoid(10)}}`,
+ email: faker.internet.email(),
+ };
+
+ await test.step('expect start Chat with department', async () => {
+ await poLiveChat.openAnyLiveChat();
+ await poLiveChat.sendMessage(guest, false, departmentA.name);
+ await expect(poLiveChat.onlineAgentMessage).toBeVisible();
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible();
+ });
+
+ await test.step('expect message to be received by department', async () => {
+ await poHomeOmnichannelAgent1.sidenav.openChat(guest.name);
+ await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toBeVisible();
+ await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toContainText('this_a_test_message_from_user');
+ });
+
+ await test.step('expect message to be sent by department', async () => {
+ await poHomeOmnichannelAgent1.content.sendMessage('this_a_test_message_from_agent');
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_agent"')).toBeVisible();
+ });
+ });
+
+ test('OC - Livechat - Change Department', async () => {
+
+ const guest = {
+ name: `${faker.person.firstName()} ${faker.string.nanoid(10)}}`,
+ email: faker.internet.email(),
+
+ };
+ await test.step('expect start Chat with department', async () => {
+ await poLiveChat.openAnyLiveChat();
+ await poLiveChat.sendMessage(guest, false, departmentA.name);
+ await expect(poLiveChat.onlineAgentMessage).toBeVisible();
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible();
+ });
+
+ await test.step('expect message to be received by department 1', async () => {
+ await poHomeOmnichannelAgent1.sidenav.openChat(guest.name);
+ await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toBeVisible();
+ await expect(poHomeOmnichannelAgent1.content.lastUserMessage).toContainText('this_a_test_message_from_user');
+ });
+
+ await test.step('expect message to be sent by department 1', async () => {
+ await poHomeOmnichannelAgent1.content.sendMessage('this_a_test_message_from_agent_department_1');
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_agent_department_1"')).toBeVisible();
+ await poHomeOmnichannelAgent1.page.close();
+ });
+
+ await test.step('expect to change department', async () => {
+ await poLiveChat.btnOptions.click();
+ await poLiveChat.btnChangeDepartment.click();
+
+ await expect(poLiveChat.selectDepartment).toBeVisible();
+ await poLiveChat.selectDepartment.selectOption({ label: departmentB.name });
+
+ await expect(poLiveChat.btnSendMessage('Start chat')).toBeEnabled();
+ await poLiveChat.btnSendMessage('Start chat').click();
+
+ await expect(poLiveChat.livechatModal).toBeVisible();
+
+ await expect(poLiveChat.livechatModalText('Are you sure you want to switch the department?')).toBeVisible();
+ await poLiveChat.btnYes.click();
+
+ await expect(poLiveChat.livechatModal).toBeVisible();
+
+ await expect(poLiveChat.livechatModalText('Department switched')).toBeVisible();
+ await poLiveChat.btnOk.click();
+
+ // Expect keep chat history
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible();
+
+ // Expect user to have changed
+ await expect(await poLiveChat.headerTitle.textContent()).toEqual(agent2.username);
+
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user_to_department_2');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user_to_department_2"')).toBeVisible();
+ });
+
+ await test.step('expect message to be received by department', async () => {
+ await poHomeOmnichannelAgent2.sidenav.openChat(guest.name);
+ await expect(poHomeOmnichannelAgent2.content.lastUserMessage).toBeVisible();
+ await expect(poHomeOmnichannelAgent2.content.lastUserMessage).toContainText('this_a_test_message_from_user_to_department_2');
+ });
+
+ await test.step('expect message to be sent by department', async () => {
+ await poHomeOmnichannelAgent2.content.sendMessage('this_a_test_message_from_agent_department_2');
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_agent_department_2"')).toBeVisible();
+ });
+ });
+});
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-fileupload.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-fileupload.spec.ts
new file mode 100644
index 000000000000..1da41ba238f9
--- /dev/null
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-fileupload.spec.ts
@@ -0,0 +1,124 @@
+import { faker } from '@faker-js/faker';
+
+import { createAuxContext } from '../fixtures/createAuxContext';
+import { Users } from '../fixtures/userStates';
+import { HomeOmnichannel, OmnichannelLiveChat } from '../page-objects';
+import { createAgent } from '../utils/omnichannel/agents';
+import { test, expect } from '../utils/test';
+
+const visitor = {
+ name: `${faker.person.firstName()} ${faker.string.uuid()}}`,
+ email: faker.internet.email(),
+}
+
+// Endpoint defaults are reset after each test, so if not in matrix assume is true
+const endpointMatrix = [
+ [{ url: '/settings/FileUpload_Enabled', value: false}],
+ [{ url: '/settings/Livechat_fileupload_enabled', value: false}],
+ [{ url: '/settings/FileUpload_Enabled', value: false}, { url: '/settings/Livechat_fileupload_enabled', value: false}],
+]
+
+const beforeTest = async (poLiveChat: OmnichannelLiveChat) => {
+ await poLiveChat.page.goto('/livechat');
+
+ await poLiveChat.openAnyLiveChat();
+ await poLiveChat.sendMessage(visitor, false);
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+
+ await poLiveChat.txtChatMessage('this_a_test_message_from_user').waitFor({state: 'visible'});
+}
+
+test.describe('OC - Livechat - OC - File Upload', () => {
+ let poLiveChat: OmnichannelLiveChat;
+ let poHomeOmnichannel: HomeOmnichannel;
+ let agent: Awaited>;
+
+ test.beforeAll(async ({ browser, api }) => {
+ agent = await createAgent(api, 'user1');
+
+ const { page } = await createAuxContext(browser, Users.user1, '/');
+ poHomeOmnichannel = new HomeOmnichannel(page);
+ });
+
+ test.beforeEach(async ({ page, api }) => {
+ poLiveChat = new OmnichannelLiveChat(page, api);
+ });
+
+ test.afterAll(async ({api}) => {
+ await api.post('/settings/FileUpload_Enabled', { value: true });
+ await api.post('/settings/Livechat_fileupload_enabled', { value: true });
+
+ await poHomeOmnichannel.page?.close();
+ await agent.delete();
+ });
+
+ // Default settings are FileUpload_Enabled true and Livechat_fileupload_enabled true
+ test('OC - Livechat - txt Drag & Drop', async () => {
+ await beforeTest(poLiveChat);
+
+ await test.step('expect to upload a txt file', async () => {
+ await poLiveChat.dragAndDropTxtFile();
+ await expect(poLiveChat.findUploadedFileLink('any_file.txt')).toBeVisible();
+ });
+ });
+
+ test('OC - Livechat - lst Drag & Drop', async () => {
+ await beforeTest(poLiveChat);
+
+ await test.step('expect to upload a lst file', async () => {
+ await poLiveChat.dragAndDropLstFile();
+ await expect(poLiveChat.findUploadedFileLink('lst-test.lst')).toBeVisible();
+ });
+ });
+});
+
+test.describe('OC - Livechat - OC - File Upload - Disabled', () => {
+ let poLiveChat: OmnichannelLiveChat;
+ let poHomeOmnichannel: HomeOmnichannel;
+ let agent: Awaited>;
+
+ test.beforeAll(async ({ browser, api }) => {
+ agent = await createAgent(api, 'user1');
+
+ const { page } = await createAuxContext(browser, Users.user1, '/');
+ poHomeOmnichannel = new HomeOmnichannel(page);
+ });
+
+ test.afterAll(async ({api}) => {
+ await api.post('/settings/FileUpload_Enabled', { value: true });
+ await api.post('/settings/Livechat_fileupload_enabled', { value: true });
+
+ await poHomeOmnichannel.page?.close();
+ await agent.delete();
+ });
+
+ endpointMatrix.forEach((endpoints) => {
+ const testName = endpoints.map((endpoint) => endpoint.url.split('/').pop()?.concat(`=${endpoint.value}`)).join(' ');
+
+ test(`OC - Livechat - txt Drag & Drop - ${testName}`, async ({ page, api }) => {
+ test.fail();
+
+ poLiveChat = new OmnichannelLiveChat(page, api);
+
+ await Promise.all(endpoints.map(async (endpoint: { url: string, value: boolean }) => {
+ await api.post(endpoint.url, { value: endpoint.value });
+ }));
+
+ await poLiveChat.page.goto('/livechat');
+
+ await poLiveChat.openAnyLiveChat();
+ await poLiveChat.sendMessage(visitor, false);
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+
+ await poLiveChat.txtChatMessage('this_a_test_message_from_user').waitFor({state: 'visible'});
+
+ await test.step('expect to upload a txt file', async () => {
+ await poLiveChat.dragAndDropTxtFile();
+
+ await expect(poLiveChat.alertMessage('file_upload_disabled')).toBeVisible();
+ });
+ });
+ });
+});
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-widget.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-widget.spec.ts
index 37279923dbda..5b57920bb7a4 100644
--- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-widget.spec.ts
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat-widget.spec.ts
@@ -8,16 +8,14 @@ test.describe('Omnichannel - Livechat Widget Embedded', () => {
let page: Page;
let poLiveChat: OmnichannelLiveChatEmbedded;
- test.beforeAll(async ({ browser, api }) => {
+ test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
poLiveChat = new OmnichannelLiveChatEmbedded(page);
- await expect((await api.post('/settings/Enable_CSP', { value: false })).status()).toBe(200);
await page.goto('/packages/rocketchat_livechat/assets/demo.html');
});
- test.afterAll(async ({ api }) => {
- await expect((await api.post('/settings/Enable_CSP', { value: true })).status()).toBe(200);
+ test.afterAll(async () => {
await page.close();
});
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts
index 39bc606b1382..35629880e749 100644
--- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts
@@ -3,6 +3,7 @@ import { faker } from '@faker-js/faker';
import { createAuxContext } from '../fixtures/createAuxContext';
import { Users } from '../fixtures/userStates';
import { HomeOmnichannel, OmnichannelLiveChat } from '../page-objects';
+import { createAgent } from '../utils/omnichannel/agents';
import { test, expect } from '../utils/test';
const firstUser = {
@@ -47,10 +48,10 @@ test.describe.serial('OC - Livechat', () => {
test('OC - Livechat - Send message to online agent', async () => {
await test.step('expect message to be sent by livechat', async () => {
await poLiveChat.page.reload();
- await poLiveChat.openLiveChat();
+ await poLiveChat.openAnyLiveChat();
await poLiveChat.sendMessage(firstUser, false);
- await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_user');
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
await poLiveChat.btnSendMessageToOnlineAgent.click();
await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible();
@@ -72,7 +73,7 @@ test.describe.serial('OC - Livechat', () => {
});
await test.step('expect when user minimizes the livechat screen, the composer should be hidden', async () => {
- await poLiveChat.openLiveChat();
+ await poLiveChat.openAnyLiveChat();
await expect(poLiveChat.page.locator('[contenteditable="true"]')).not.toBeVisible();
});
@@ -126,10 +127,10 @@ test.describe.serial('OC - Livechat - Resub after close room', () => {
});
test('OC - Livechat - Resub after close room', async () => {
- await test.step('expect livechat conversation to be opened again', async () => {
+ await test.step('expect livechat conversation to be opened again, different guest', async () => {
await poLiveChat.startNewChat();
await poLiveChat.sendMessage(secondUser, false);
- await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_user');
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
await poLiveChat.btnSendMessageToOnlineAgent.click();
await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible();
});
@@ -147,6 +148,114 @@ test.describe.serial('OC - Livechat - Resub after close room', () => {
});
});
+test.describe('OC - Livechat - Resume chat after closing', () => {
+ let poLiveChat: OmnichannelLiveChat;
+ let poHomeOmnichannel: HomeOmnichannel;
+
+ test.beforeAll(async ({ api }) => {
+ const statusCode = (await api.post('/livechat/users/agent', { username: 'user1' })).status();
+ await expect(statusCode).toBe(200);
+ });
+
+ test.beforeAll(async ({ browser, api }) => {
+ const { page: omniPage } = await createAuxContext(browser, Users.user1, '/', true);
+ poHomeOmnichannel = new HomeOmnichannel(omniPage);
+
+ const { page: livechatPage } = await createAuxContext(browser, Users.user1, '/livechat', false);
+ poLiveChat = new OmnichannelLiveChat(livechatPage, api);
+
+ await poLiveChat.sendMessageAndCloseChat(firstUser);
+ });
+
+ test.afterAll(async ({ api }) => {
+ await api.delete('/livechat/users/agent/user1');
+ await poLiveChat.page?.close();
+ });
+
+ test('OC - Livechat - Resume chat after closing', async () => {
+ await test.step('expect livechat conversation to be opened again', async () => {
+ await poLiveChat.startNewChat();
+ await expect(poLiveChat.onlineAgentMessage).toBeVisible();
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_user"')).toBeVisible();
+ });
+
+ await test.step('expect message to be received by agent', async () => {
+ await poHomeOmnichannel.sidenav.openChat(firstUser.name);
+ await expect(poHomeOmnichannel.content.lastUserMessage).toBeVisible();
+ await expect(poHomeOmnichannel.content.lastUserMessage).toContainText('this_a_test_message_from_user');
+ });
+
+ await test.step('expect message to be sent by agent', async () => {
+ await poHomeOmnichannel.content.sendMessage('this_a_test_message_from_agent');
+ await expect(poLiveChat.page.locator('div >> text="this_a_test_message_from_agent"')).toBeVisible();
+ });
+ });
+});
+
+test.describe('OC - Livechat - Close chat using widget', () => {
+ let poLiveChat: OmnichannelLiveChat;
+ let poHomeOmnichannel: HomeOmnichannel;
+ let agent: Awaited>;
+
+ test.beforeAll(async ({ browser, api }) => {
+ agent = await createAgent(api, 'user1');
+
+ const { page } = await createAuxContext(browser, Users.user1, '/');
+ poHomeOmnichannel = new HomeOmnichannel(page);
+ });
+
+ test.beforeEach(async ({ page, api }) => {
+ poLiveChat = new OmnichannelLiveChat(page, api);
+
+ await poLiveChat.page.goto('/livechat');
+ });
+
+ test.afterAll(async () => {
+ await poHomeOmnichannel.page?.close();
+ await agent.delete();
+ });
+
+ test('OC - Livechat - Close Chat', async () => {
+ await poLiveChat.openAnyLiveChat();
+ await poLiveChat.sendMessage(firstUser, false);
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+
+ await test.step('expect to close a livechat conversation', async () => {
+ await expect(poLiveChat.btnOptions).toBeVisible();
+ await poLiveChat.btnOptions.click();
+
+ await expect(poLiveChat.btnCloseChat).toBeVisible();
+ await poLiveChat.btnCloseChat.click();
+
+ await poLiveChat.btnCloseChatConfirm.click();
+
+ await expect(poLiveChat.btnNewChat).toBeVisible();
+ });
+ });
+
+ test('OC - Livechat - Close Chat twice', async () => {
+ await poLiveChat.sendMessageAndCloseChat(firstUser);
+ await poLiveChat.startNewChat();
+ await poLiveChat.onlineAgentMessage.fill('this_a_test_message_from_user');
+ await poLiveChat.btnSendMessageToOnlineAgent.click();
+
+ await test.step('expect to close a livechat conversation a second time', async () => {
+ await expect(poLiveChat.btnOptions).toBeVisible();
+ await poLiveChat.btnOptions.click();
+
+ await expect(poLiveChat.btnCloseChat).toBeVisible();
+ await poLiveChat.btnCloseChat.click();
+
+ await poLiveChat.btnCloseChatConfirm.click();
+
+ await expect(poLiveChat.btnNewChat).toBeVisible();
+ });
+ });
+});
+
test.describe('OC - Livechat - Livechat_Display_Offline_Form', () => {
let poLiveChat: OmnichannelLiveChat;
const message = 'This form is not available';
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts
index f9550ebbac0e..31add936d346 100644
--- a/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts
@@ -63,6 +63,7 @@ test.describe.serial('OC - Priorities [Sidebar]', () => {
await test.step('expect to change inquiry priority using sidebar menu', async () => {
await poHomeChannel.sidenav.getSidebarItemByName(NEW_USER.name).click();
+ await expect(poHomeChannel.content.btnTakeChat).toBeVisible();
await expect(poRoomInfo.getLabel('Priority')).not.toBeVisible();
diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-units.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-units.spec.ts
index 703da8e1be9e..5e24dc8622ae 100644
--- a/apps/meteor/tests/e2e/omnichannel/omnichannel-units.spec.ts
+++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-units.spec.ts
@@ -106,9 +106,6 @@ test.describe('OC - Manage Units', () => {
test('OC - Manage Units - Edit unit', async ({ api, page }) => {
const editedUnitName = faker.string.uuid();
- await page.goto('/omnichannel');
- await poOmnichannelUnits.sidenav.linkUnits.click();
-
const unit = await test.step('expect to create new unit', async () => {
const { data: unit } = await createOrUpdateUnit(api, {
name: faker.string.uuid(),
@@ -120,6 +117,9 @@ test.describe('OC - Manage Units', () => {
return unit;
});
+ await page.goto('/omnichannel');
+ await poOmnichannelUnits.sidenav.linkUnits.click();
+
await test.step('expect to edit unit', async () => {
await poOmnichannelUnits.search(unit.name);
await poOmnichannelUnits.findRowByName(unit.name).click();
@@ -129,8 +129,7 @@ test.describe('OC - Manage Units', () => {
});
await test.step('expect unit to have been edited', async () => {
- await poOmnichannelUnits.sidenav.linkPriorities.click();
- await poOmnichannelUnits.sidenav.linkUnits.click(); // refresh the page
+ await expect(poOmnichannelUnits.inputSearch).toBeVisible();
await poOmnichannelUnits.search(editedUnitName);
await expect(poOmnichannelUnits.findRowByName(editedUnitName)).toBeVisible();
});
@@ -158,15 +157,9 @@ test.describe('OC - Manage Units', () => {
});
await test.step('expect to have been deleted', async () => {
- await poOmnichannelUnits.sidenav.linkPriorities.click();
- await poOmnichannelUnits.sidenav.linkUnits.click(); // refresh the page
-
- if (await poOmnichannelUnits.inputSearch.isVisible()) {
- await poOmnichannelUnits.search(editedUnitName);
- await expect(poOmnichannelUnits.findRowByName(editedUnitName)).not.toBeVisible();
- } else {
- await expect(page.locator('h3 >> text="No units yet"')).toBeVisible();
- }
+ await expect(poOmnichannelUnits.inputSearch).toBeVisible();
+ await poOmnichannelUnits.inputSearch.clear();
+ await expect(page.locator('h3 >> text="No units yet"')).toBeVisible();
});
});
});
diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts
index c711d6bcdc30..d7b0b3c2d9cd 100644
--- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts
+++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts
@@ -28,7 +28,7 @@ export class HomeFlextabRoom {
}
get checkboxReadOnly(): Locator {
- return this.page.locator('text=Read OnlyOnly authorized users can write new messages >> i');
+ return this.page.locator('label', { has: this.page.getByRole('checkbox', { name: 'Read-only' }) });
}
get btnSave(): Locator {
diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts
index 12a38f3b6185..ec085dce6bc7 100644
--- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts
+++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts
@@ -10,15 +10,15 @@ export class HomeSidenav {
}
get checkboxPrivateChannel(): Locator {
- return this.page.locator('role=dialog[name="Create Channel"] >> label >> text="Private"');
+ return this.page.locator('label', { has: this.page.getByRole('checkbox', { name: 'Private' }) });
}
get checkboxEncryption(): Locator {
- return this.page.locator('role=dialog[name="Create Channel"] >> label >> text="Encrypted"');
+ return this.page.locator('role=dialog[name="Create channel"] >> label >> text="Encrypted"');
}
get checkboxReadOnly(): Locator {
- return this.page.locator('role=dialog[name="Create Channel"] >> label >> text="Read Only"');
+ return this.page.locator('label', { has: this.page.getByRole('checkbox', { name: 'Read-only' }) });
}
get inputChannelName(): Locator {
diff --git a/apps/meteor/tests/e2e/page-objects/home-discussion.ts b/apps/meteor/tests/e2e/page-objects/home-discussion.ts
index dc055df73bce..58e129d745a1 100644
--- a/apps/meteor/tests/e2e/page-objects/home-discussion.ts
+++ b/apps/meteor/tests/e2e/page-objects/home-discussion.ts
@@ -19,18 +19,18 @@ export class HomeDiscussion {
}
get inputChannelName(): Locator {
- return this.page.locator('.rcx-input-box--undecorated.rcx-input-box').first();
+ return this.page.locator('role=textbox[name="Parent channel or team"]');
}
get inputName(): Locator {
- return this.page.locator('[placeholder="A meaningful name for the discussion room"]');
+ return this.page.locator('role=textbox[name="Name"]');
}
get inputMessage(): Locator {
- return this.page.locator('textarea.rcx-input-box');
+ return this.page.locator('role=textbox[name="Message"]');
}
get btnCreate(): Locator {
- return this.page.locator('button.rcx-button--primary.rcx-button >> text="Create"');
+ return this.page.locator('role=dialog >> role=group >> role=button[name="Create"]');
}
}
diff --git a/apps/meteor/tests/e2e/page-objects/home-omnichannel.ts b/apps/meteor/tests/e2e/page-objects/home-omnichannel.ts
index e93e90b0b492..9111df7080ab 100644
--- a/apps/meteor/tests/e2e/page-objects/home-omnichannel.ts
+++ b/apps/meteor/tests/e2e/page-objects/home-omnichannel.ts
@@ -10,7 +10,7 @@ import { OmnichannelTranscript } from './omnichannel-transcript';
import { OmnichannelTriggers } from './omnichannel-triggers';
export class HomeOmnichannel {
- private readonly page: Page;
+ readonly page: Page;
readonly content: HomeOmnichannelContent;
diff --git a/apps/meteor/tests/e2e/page-objects/home-team.ts b/apps/meteor/tests/e2e/page-objects/home-team.ts
index 9c890da05db8..a3b9130eb341 100644
--- a/apps/meteor/tests/e2e/page-objects/home-team.ts
+++ b/apps/meteor/tests/e2e/page-objects/home-team.ts
@@ -19,11 +19,11 @@ export class HomeTeam {
}
get inputTeamName(): Locator {
- return this.page.locator('.rcx-field-group__item:nth-child(1) input');
+ return this.page.locator('role=textbox[name="Name"]');
}
async addMember(memberName: string): Promise {
- await this.page.locator('.rcx-field-group__item:nth-child(7) input').type(memberName, { delay: 100 });
+ await this.page.locator('role=textbox[name="Members"]').type(memberName, { delay: 100 });
await this.page.locator(`.rcx-option__content:has-text("${memberName}")`).click();
}
@@ -32,10 +32,10 @@ export class HomeTeam {
}
get textPrivate(): Locator {
- return this.page.locator('role=dialog[name="Create Team"] >> label >> text="Private"');
+ return this.page.locator('label', {has: this.page.getByRole('checkbox', {name: 'Private'})});
}
get textReadOnly(): Locator {
- return this.page.locator('role=dialog[name="Create Team"] >> label >> text="Read Only"');
+ return this.page.locator('label', {has: this.page.getByRole('checkbox', {name: 'Read-only'})});
}
}
diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts
index 8d5fff57412b..db4993eaaac6 100644
--- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts
+++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat-embedded.ts
@@ -40,7 +40,7 @@ export class OmnichannelLiveChatEmbedded {
}
txtChatMessage(message: string): Locator {
- return this.page.frameLocator('#rocketchat-iframe').locator(`text="${message}"`);
+ return this.page.frameLocator('#rocketchat-iframe').locator(`li >> text="${message}"`);
}
async closeChat(): Promise {
diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts
index af539a7783f1..1029c8ba2819 100644
--- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts
+++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts
@@ -1,3 +1,5 @@
+import fs from 'fs/promises';
+
import type { Page, Locator, APIResponse } from '@playwright/test';
import { expect } from '../utils/test';
@@ -29,6 +31,10 @@ export class OmnichannelLiveChat {
return this.page.locator(`button >> text="Finish this chat"`);
}
+ get btnChangeDepartment(): Locator {
+ return this.page.locator(`button >> text="Change department"`);
+ }
+
get btnCloseChatConfirm(): Locator {
return this.page.locator(`button >> text="Yes"`);
}
@@ -40,6 +46,14 @@ export class OmnichannelLiveChat {
get btnChatNow(): Locator {
return this.page.locator('[type="button"] >> text="Chat now"');
}
+
+ get headerTitle(): Locator {
+ return this.page.locator('[data-qa="header-title"]');
+ }
+
+ alertMessage(message: string): Locator {
+ return this.page.getByRole('alert').locator(`text="${message}"`);
+ }
txtChatMessage(message: string): Locator {
return this.page.locator(`text="${message}"`);
@@ -80,6 +94,10 @@ export class OmnichannelLiveChat {
return this.page.locator('[name="email"]');
}
+ get selectDepartment(): Locator {
+ return this.page.locator('[name="department"]');
+ }
+
get textAreaMessage(): Locator {
return this.page.locator('[name="message"]');
}
@@ -92,6 +110,10 @@ export class OmnichannelLiveChat {
return this.page.locator('role=button[name="OK"]');
}
+ get btnYes(): Locator {
+ return this.page.locator('role=button[name="Yes"]');
+ }
+
get onlineAgentMessage(): Locator {
return this.page.locator('[contenteditable="true"]');
}
@@ -100,17 +122,35 @@ export class OmnichannelLiveChat {
return this.page.locator('footer div div div:nth-child(3) button');
}
- get firstAutoMessage(): Locator {
- return this.page.locator('div.message-text__WwYco p');
+ get livechatModal(): Locator {
+ return this.page.locator('[data-qa-type="modal-overlay"]');
+ }
+
+ livechatModalText(text: string): Locator {
+ return this.page.locator(`[data-qa-type="modal-overlay"] >> text=${text}`);
+ }
+
+ get fileUploadTarget(): Locator {
+ return this.page.locator('#files-drop-target');
}
- public async sendMessage(liveChatUser: { name: string; email: string }, isOffline = true): Promise {
+ findUploadedFileLink (fileName: string): Locator {
+ return this.page.getByRole('link', { name: fileName });
+ }
+
+ public async sendMessage(liveChatUser: { name: string; email: string }, isOffline = true, department?: string): Promise {
const buttonLabel = isOffline ? 'Send' : 'Start chat';
- await this.inputName.type(liveChatUser.name);
- await this.inputEmail.type(liveChatUser.email);
+ await this.inputName.fill(liveChatUser.name);
+ await this.inputEmail.fill(liveChatUser.email);
+
+ if (department) {
+ await this.selectDepartment.selectOption({ label: department });
+ }
+
if (isOffline) {
await this.textAreaMessage.type('any_message');
}
+
await this.btnSendMessage(buttonLabel).click();
await this.page.waitForSelector('[data-qa="livechat-composer"]');
}
@@ -119,11 +159,43 @@ export class OmnichannelLiveChat {
liveChatUser: { name: string; email: string },
message = 'this_a_test_message_from_user',
): Promise {
- await this.openLiveChat();
+ await this.openAnyLiveChat();
await this.sendMessage(liveChatUser, false);
await this.onlineAgentMessage.type(message);
await this.btnSendMessageToOnlineAgent.click();
await expect(this.txtChatMessage(message)).toBeVisible();
await this.closeChat();
}
+
+ async dragAndDropTxtFile(): Promise {
+ const contract = await fs.readFile('./tests/e2e/fixtures/files/any_file.txt', 'utf-8');
+ const dataTransfer = await this.page.evaluateHandle((contract) => {
+ const data = new DataTransfer();
+ const file = new File([`${contract}`], 'any_file.txt', {
+ type: 'text/plain',
+ });
+ data.items.add(file);
+ return data;
+ }, contract);
+
+ await this.fileUploadTarget.dispatchEvent('dragenter', { dataTransfer });
+
+ await this.fileUploadTarget.dispatchEvent('drop', { dataTransfer });
+ }
+
+ async dragAndDropLstFile(): Promise {
+ const contract = await fs.readFile('./tests/e2e/fixtures/files/lst-test.lst', 'utf-8');
+ const dataTransfer = await this.page.evaluateHandle((contract) => {
+ const data = new DataTransfer();
+ const file = new File([`${contract}`], 'lst-test.lst', {
+ type: 'text/plain',
+ });
+ data.items.add(file);
+ return data;
+ }, contract);
+
+ await this.fileUploadTarget.dispatchEvent('dragenter', { dataTransfer });
+
+ await this.fileUploadTarget.dispatchEvent('drop', { dataTransfer });
+ }
}
diff --git a/apps/meteor/tests/e2e/utils/omnichannel/departments.ts b/apps/meteor/tests/e2e/utils/omnichannel/departments.ts
index 2b4fefd666c6..455410808720 100644
--- a/apps/meteor/tests/e2e/utils/omnichannel/departments.ts
+++ b/apps/meteor/tests/e2e/utils/omnichannel/departments.ts
@@ -3,24 +3,54 @@ import { ILivechatDepartment } from '@rocket.chat/core-typings';
import { BaseTest } from '../test';
-type CreateDepartmentParams = { name?: string; maxNumberSimultaneousChat?: number };
+type CreateDepartmentParams = {
+ name?: string;
+ enabled?: boolean;
+ description?: string;
+ showOnRegistration?: boolean;
+ showOnOfflineForm?: boolean;
+ requestTagBeforeClosingChat?: boolean;
+ email?: string;
+ chatClosingTags?: string[];
+ offlineMessageChannelName?: string;
+ abandonedRoomsCloseCustomMessage?: string,
+ waitingQueueMessage?: string;
+ departmentsAllowedToForward?: string[];
+ fallbackForwardDepartment?: string;
+ maxNumberSimultaneousChat?: number;
+ };
-export const createDepartment = async (api: BaseTest['api'], { name = '', maxNumberSimultaneousChat }: CreateDepartmentParams = {}) => {
+export const createDepartment = async (api: BaseTest['api'], {
+ name = '',
+ enabled = true,
+ description = '',
+ showOnRegistration = false,
+ showOnOfflineForm = false,
+ requestTagBeforeClosingChat = false,
+ email = '',
+ chatClosingTags = [],
+ offlineMessageChannelName = '',
+ abandonedRoomsCloseCustomMessage = '',
+ waitingQueueMessage = '',
+ departmentsAllowedToForward = [],
+ fallbackForwardDepartment = '',
+ maxNumberSimultaneousChat
+}: CreateDepartmentParams = {}) => {
const response = await api.post('/livechat/department', {
department: {
name: name || faker.string.uuid(),
- enabled: true,
- description: '',
- showOnRegistration: false,
- showOnOfflineForm: false,
- requestTagBeforeClosingChat: false,
- email: faker.internet.email(),
- chatClosingTags: [],
- offlineMessageChannelName: '',
- abandonedRoomsCloseCustomMessage: '',
- waitingQueueMessage: '',
- departmentsAllowedToForward: [],
- fallbackForwardDepartment: '',
+ enabled,
+ description,
+ showOnRegistration,
+ showOnOfflineForm,
+ requestTagBeforeClosingChat,
+ email: email || faker.internet.email(),
+ chatClosingTags,
+ offlineMessageChannelName,
+ abandonedRoomsCloseCustomMessage,
+ waitingQueueMessage,
+ departmentsAllowedToForward,
+ fallbackForwardDepartment,
maxNumberSimultaneousChat,
},
});
diff --git a/apps/meteor/tests/end-to-end/api/09-rooms.js b/apps/meteor/tests/end-to-end/api/09-rooms.js
index e60cf8c1f6b4..92b73f34557e 100644
--- a/apps/meteor/tests/end-to-end/api/09-rooms.js
+++ b/apps/meteor/tests/end-to-end/api/09-rooms.js
@@ -1742,7 +1742,7 @@ describe('[Rooms]', function () {
});
});
- describe('/rooms.saveRoomSettings', () => {
+ describe('rooms.saveRoomSettings', () => {
let testChannel;
const randomString = `randomString${Date.now()}`;
let discussion;
@@ -1847,5 +1847,64 @@ describe('[Rooms]', function () {
expect(res.body.room).to.have.property('fname', newDiscussionName);
});
});
+
+ it('should mark a room as favorite', async () => {
+ await request
+ .post(api('rooms.saveRoomSettings'))
+ .set(credentials)
+ .send({
+ rid: testChannel._id,
+ favorite: {
+ favorite: true,
+ defaultValue: true,
+ },
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(200);
+
+ await request
+ .get(api('rooms.info'))
+ .set(credentials)
+ .query({
+ roomId: testChannel._id,
+ })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('room').and.to.be.an('object');
+
+ expect(res.body.room).to.have.property('_id', testChannel._id);
+ expect(res.body.room).to.have.property('favorite', true);
+ });
+ });
+ it('should not mark a room as favorite when room is not a default room', async () => {
+ await request
+ .post(api('rooms.saveRoomSettings'))
+ .set(credentials)
+ .send({
+ rid: testChannel._id,
+ favorite: {
+ favorite: true,
+ defaultValue: false,
+ },
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(200);
+
+ await request
+ .get(api('rooms.info'))
+ .set(credentials)
+ .query({
+ roomId: testChannel._id,
+ })
+ .expect(200)
+ .expect((res) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body).to.have.property('room').and.to.be.an('object');
+
+ expect(res.body.room).to.have.property('_id', testChannel._id);
+ expect(res.body.room).to.not.have.property('favorite');
+ });
+ });
});
});
diff --git a/apps/meteor/tests/end-to-end/api/27-presence.ts b/apps/meteor/tests/end-to-end/api/27-presence.ts
index 25eceb4beeaa..80a95e18e5b3 100644
--- a/apps/meteor/tests/end-to-end/api/27-presence.ts
+++ b/apps/meteor/tests/end-to-end/api/27-presence.ts
@@ -1,23 +1,26 @@
import { expect } from 'chai';
-import { before, describe, it } from 'mocha';
+import { before, describe, it, after } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../data/api-data.js';
import { updatePermission } from '../../data/permissions.helper';
import { password } from '../../data/user';
-import { createUser, login } from '../../data/users.helper';
+import { createUser, deleteUser, login } from '../../data/users.helper';
describe('[Presence]', function () {
+ let createdUser: any;
this.retries(0);
before((done) => getCredentials(done));
let unauthorizedUserCredentials: any;
before(async () => {
- const createdUser = await createUser();
+ createdUser = await createUser();
unauthorizedUserCredentials = await login(createdUser.username, password);
});
+ after(() => Promise.all([updatePermission('manage-user-status', ['admin']), deleteUser(createdUser)]));
+
describe('[/presence.getConnections]', () => {
it('should throw an error if not authenticated', async () => {
await request
diff --git a/apps/meteor/tests/end-to-end/api/28-roles.ts b/apps/meteor/tests/end-to-end/api/28-roles.ts
index 1900dcc323a9..ad0e693bf2d8 100644
--- a/apps/meteor/tests/end-to-end/api/28-roles.ts
+++ b/apps/meteor/tests/end-to-end/api/28-roles.ts
@@ -1,5 +1,5 @@
import { expect } from 'chai';
-import { before, describe, it } from 'mocha';
+import { after, before, describe, it } from 'mocha';
import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../data/api-data.js';
@@ -84,6 +84,15 @@ describe('[Roles]', function () {
});
});
+ after(async () => {
+ if (!isEnterprise) {
+ return;
+ }
+ await request.post(api('roles.delete')).set(credentials).send({
+ roleId: testRoleId,
+ });
+ });
+
it('should throw an error when not running EE to update a role', async function () {
// TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE,
// ideally we should have a single CI job that adds a license and runs both CE and EE tests.
diff --git a/apps/meteor/tests/end-to-end/api/31-failed-login-attempts.ts b/apps/meteor/tests/end-to-end/api/31-failed-login-attempts.ts
index 92ea1ac56ed3..906b19d0a931 100644
--- a/apps/meteor/tests/end-to-end/api/31-failed-login-attempts.ts
+++ b/apps/meteor/tests/end-to-end/api/31-failed-login-attempts.ts
@@ -17,25 +17,31 @@ describe('[Failed Login Attempts]', function () {
before((done) => getCredentials(done));
- before(async () => {
- await updateSetting('Block_Multiple_Failed_Logins_Enabled', true);
- await updateSetting('Block_Multiple_Failed_Logins_By_Ip', true);
- await updateSetting('Block_Multiple_Failed_Logins_By_User', true);
- await updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_by_User', maxAttemptsByUser);
- await updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes', userBlockSeconds / 60);
- await updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_By_Ip', maxAttemptsByIp);
- await updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes', ipBlockSeconds / 60);
-
- await updatePermission('logout-other-user', ['admin']);
- });
-
- after(async () => {
- await updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_by_User', 10);
- await updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes', 5);
- await updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_By_Ip', 50);
- await updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes', 5);
- await updateSetting('Block_Multiple_Failed_Logins_Enabled', false);
- });
+ before(() =>
+ Promise.all([
+ updateSetting('Block_Multiple_Failed_Logins_Enabled', true),
+ updateSetting('Block_Multiple_Failed_Logins_By_Ip', true),
+ updateSetting('Block_Multiple_Failed_Logins_By_User', true),
+ updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_by_User', maxAttemptsByUser),
+ updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes', userBlockSeconds / 60),
+ updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_By_Ip', maxAttemptsByIp),
+ updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes', ipBlockSeconds / 60),
+ updatePermission('logout-other-user', ['admin']),
+ ]),
+ );
+
+ after(() =>
+ Promise.all([
+ updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_by_User', 10),
+ updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_User_In_Minutes', 5),
+ updateSetting('Block_Multiple_Failed_Logins_Attempts_Until_Block_By_Ip', 50),
+ updateSetting('Block_Multiple_Failed_Logins_Time_To_Unblock_By_Ip_In_Minutes', 5),
+ updateSetting('Block_Multiple_Failed_Logins_Enabled', true),
+ updateSetting('Block_Multiple_Failed_Logins_By_Ip', true),
+ updateSetting('Block_Multiple_Failed_Logins_By_User', true),
+ updatePermission('logout-other-user', ['admin']),
+ ]),
+ );
async function shouldFailLoginWithUser(username: string, password: string) {
await request
@@ -48,7 +54,7 @@ describe('[Failed Login Attempts]', function () {
.expect(401)
.expect((res) => {
expect(res.body).to.have.property('status', 'error');
- expect(res.body).to.have.property('message', 'Incorrect password');
+ expect(res.body).to.have.property('message', 'Unauthorized');
});
}
@@ -163,11 +169,7 @@ describe('[Failed Login Attempts]', function () {
userLogin = await createUser();
});
- afterEach(async () => {
- await deleteUser(user);
- await deleteUser(user2);
- await deleteUser(userLogin);
- });
+ afterEach(() => Promise.all([deleteUser(user), deleteUser(user2), deleteUser(userLogin)]));
afterEach(async () => {
// reset counter
diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
index 0d9e5fff0a65..e99c893abf9a 100644
--- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
+++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
@@ -18,7 +18,7 @@ import type { Response } from 'supertest';
import type { SuccessResult } from '../../../../app/api/server/definition';
import { getCredentials, api, request, credentials, methodCall } from '../../../data/api-data';
import { createCustomField } from '../../../data/livechat/custom-fields';
-import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department';
+import { createDepartmentWithAnOfflineAgent, createDepartmentWithAnOnlineAgent, deleteDepartment } from '../../../data/livechat/department';
import { createSLA, getRandomPriority } from '../../../data/livechat/priorities';
import {
createVisitor,
@@ -32,6 +32,7 @@ import {
closeOmnichannelRoom,
createDepartment,
fetchMessages,
+ makeAgentUnavailable,
} from '../../../data/livechat/rooms';
import { saveTags } from '../../../data/livechat/tags';
import type { DummyResponse } from '../../../data/livechat/utils';
@@ -700,6 +701,35 @@ describe('LIVECHAT - rooms', function () {
await deleteUser(initialAgentAssignedToChat);
await deleteUser(forwardChatToUser);
});
+
+ (IS_EE ? it : it.skip)('should return error message when transferred to a offline agent', async () => {
+ await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
+ const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent();
+ const { department: forwardToOfflineDepartment } = await createDepartmentWithAnOfflineAgent({ allowReceiveForwardOffline: false });
+
+ const newVisitor = await createVisitor(initialDepartment._id);
+ const newRoom = await createLivechatRoom(newVisitor.token);
+
+ await request
+ .post(api('livechat/room.forward'))
+ .set(credentials)
+ .send({
+ roomId: newRoom._id,
+ departmentId: forwardToOfflineDepartment._id,
+ clientAction: true,
+ comment: 'test comment',
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(400)
+ .expect((res: Response) => {
+ expect(res.body).to.have.property('success', false);
+ expect(res.body).to.have.property('error', 'error-no-agents-online-in-department');
+ });
+
+ await deleteDepartment(initialDepartment._id);
+ await deleteDepartment(forwardToOfflineDepartment._id);
+ });
+
(IS_EE ? it : it.skip)('should return a success message when transferred successfully to a department', async () => {
const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent();
const { department: forwardToDepartment } = await createDepartmentWithAnOnlineAgent();
@@ -734,6 +764,112 @@ describe('LIVECHAT - rooms', function () {
expect((latestRoom.lastMessage as any)?.transferData?.scope).to.be.equal('department');
expect((latestRoom.lastMessage as any)?.transferData?.nextDepartment?._id).to.be.equal(forwardToDepartment._id);
});
+ (IS_EE ? it : it.skip)(
+ 'should return a success message when transferred successfully to an offline department when the department accepts it',
+ async () => {
+ const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent();
+ const { department: forwardToOfflineDepartment } = await createDepartmentWithAnOfflineAgent({ allowReceiveForwardOffline: true });
+
+ const newVisitor = await createVisitor(initialDepartment._id);
+ const newRoom = await createLivechatRoom(newVisitor.token);
+
+ await request
+ .post(api('livechat/room.forward'))
+ .set(credentials)
+ .send({
+ roomId: newRoom._id,
+ departmentId: forwardToOfflineDepartment._id,
+ clientAction: true,
+ comment: 'test comment',
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(200)
+ .expect((res: Response) => {
+ expect(res.body).to.have.property('success', true);
+ });
+
+ await deleteDepartment(initialDepartment._id);
+ await deleteDepartment(forwardToOfflineDepartment._id);
+ },
+ );
+ (IS_EE ? it : it.skip)('inquiry should be taken automatically when agent on department is online again', async () => {
+ await updateSetting('Livechat_Routing_Method', 'Auto_Selection');
+ const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent();
+ const { department: forwardToOfflineDepartment } = await createDepartmentWithAnOfflineAgent({ allowReceiveForwardOffline: true });
+
+ const newVisitor = await createVisitor(initialDepartment._id);
+ const newRoom = await createLivechatRoom(newVisitor.token);
+
+ await request.post(api('livechat/room.forward')).set(credentials).send({
+ roomId: newRoom._id,
+ departmentId: forwardToOfflineDepartment._id,
+ clientAction: true,
+ comment: 'test comment',
+ });
+
+ await makeAgentAvailable();
+
+ const latestRoom = await getLivechatRoomInfo(newRoom._id);
+
+ expect(latestRoom).to.have.property('departmentId');
+ expect(latestRoom.departmentId).to.be.equal(forwardToOfflineDepartment._id);
+
+ expect(latestRoom).to.have.property('lastMessage');
+ expect(latestRoom.lastMessage?.t).to.be.equal('livechat_transfer_history');
+ expect(latestRoom.lastMessage?.u?.username).to.be.equal(adminUsername);
+ expect((latestRoom.lastMessage as any)?.transferData?.comment).to.be.equal('test comment');
+ expect((latestRoom.lastMessage as any)?.transferData?.scope).to.be.equal('department');
+ expect((latestRoom.lastMessage as any)?.transferData?.nextDepartment?._id).to.be.equal(forwardToOfflineDepartment._id);
+
+ await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
+ await deleteDepartment(initialDepartment._id);
+ await deleteDepartment(forwardToOfflineDepartment._id);
+ });
+
+ (IS_EE ? it : it.skip)('when manager forward to offline department the inquiry should be set to the queue', async () => {
+ await updateSetting('Livechat_Routing_Method', 'Manual_Selection');
+ const { department: initialDepartment } = await createDepartmentWithAnOnlineAgent();
+ const { department: forwardToOfflineDepartment, agent: offlineAgent } = await createDepartmentWithAnOfflineAgent({
+ allowReceiveForwardOffline: true,
+ });
+
+ const newVisitor = await createVisitor(initialDepartment._id);
+ const newRoom = await createLivechatRoom(newVisitor.token);
+
+ await makeAgentUnavailable(offlineAgent.credentials);
+
+ const manager: IUser = await createUser();
+ const managerCredentials = await login(manager.username, password);
+ await createManager(manager.username);
+
+ await request.post(api('livechat/room.forward')).set(managerCredentials).send({
+ roomId: newRoom._id,
+ departmentId: forwardToOfflineDepartment._id,
+ clientAction: true,
+ comment: 'test comment',
+ });
+
+ await request
+ .get(api(`livechat/queue`))
+ .set(credentials)
+ .query({
+ count: 1,
+ })
+ .expect('Content-Type', 'application/json')
+ .expect(200)
+ .expect((res: Response) => {
+ expect(res.body).to.have.property('success', true);
+ expect(res.body.queue).to.be.an('array');
+ expect(res.body.queue[0].chats).not.to.undefined;
+ expect(res.body).to.have.property('offset');
+ expect(res.body).to.have.property('total');
+ expect(res.body).to.have.property('count');
+ });
+
+ await deleteDepartment(initialDepartment._id);
+ await deleteDepartment(forwardToOfflineDepartment._id);
+ });
+
let roomId: string;
let visitorToken: string;
(IS_EE ? it : it.skip)('should return a success message when transferring to a fallback department', async () => {
diff --git a/apps/meteor/tests/end-to-end/api/livechat/02-appearance.ts b/apps/meteor/tests/end-to-end/api/livechat/02-appearance.ts
index 281db92f7233..1c193cc95e22 100644
--- a/apps/meteor/tests/end-to-end/api/livechat/02-appearance.ts
+++ b/apps/meteor/tests/end-to-end/api/livechat/02-appearance.ts
@@ -5,6 +5,7 @@ import type { Response } from 'supertest';
import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { sleep } from '../../../data/livechat/utils';
import { removePermissionFromAllRoles, restorePermissionToRoles, updatePermission, updateSetting } from '../../../data/permissions.helper';
+import { IS_EE } from '../../../e2e/config/constants';
describe('LIVECHAT - appearance', function () {
this.retries(0);
@@ -198,5 +199,61 @@ describe('LIVECHAT - appearance', function () {
const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
expect(body.config.settings.limitTextLength).to.be.false;
});
+ (IS_EE ? it : it.skip)('should accept an array setting', async () => {
+ await request
+ .post(api('livechat/appearance'))
+ .set(credentials)
+ .send([{ _id: 'Livechat_hide_system_messages', value: ['uj'] }])
+ .expect(200);
+ await sleep(500);
+
+ // Get data from livechat/config
+ const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
+ expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
+ expect(body.config.settings.hiddenSystemMessages).to.include('uj');
+ });
+ (IS_EE ? it : it.skip)('should accept an array setting with multiple values', async () => {
+ await request
+ .post(api('livechat/appearance'))
+ .set(credentials)
+ .send([{ _id: 'Livechat_hide_system_messages', value: ['uj', 'ul'] }])
+ .expect(200);
+ await sleep(500);
+
+ // Get data from livechat/config
+ const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
+ expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
+ expect(body.config.settings.hiddenSystemMessages).to.include('uj');
+ expect(body.config.settings.hiddenSystemMessages).to.include('ul');
+ });
+ (IS_EE ? it : it.skip)('should not update an array setting with a value other than array', async () => {
+ await request
+ .post(api('livechat/appearance'))
+ .set(credentials)
+ .send([{ _id: 'Livechat_hide_system_messages', value: 'uj' }])
+ .expect(200);
+
+ await sleep(500);
+
+ // Get data from livechat/config
+ const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
+ expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
+ expect(body.config.settings.hiddenSystemMessages).to.include('uj');
+ });
+ (IS_EE ? it : it.skip)('should not update an array setting with values that are not valid setting values', async () => {
+ await request
+ .post(api('livechat/appearance'))
+ .set(credentials)
+ .send([{ _id: 'Livechat_hide_system_messages', value: ['uj', 'invalid'] }])
+ .expect(200);
+
+ await sleep(500);
+
+ // Get data from livechat/config
+ const { body } = await request.get(api('livechat/config')).set(credentials).expect(200);
+ expect(body.config.settings.hiddenSystemMessages).to.be.an('array');
+ expect(body.config.settings.hiddenSystemMessages).to.include('uj');
+ expect(body.config.settings.hiddenSystemMessages).to.not.include('invalid');
+ });
});
});
diff --git a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
index cfa976fc1d97..58484e857c40 100644
--- a/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
+++ b/apps/meteor/tests/end-to-end/api/livechat/09-visitors.ts
@@ -115,6 +115,105 @@ describe('LIVECHAT - visitors', function () {
expect(body2.visitor).to.have.property('phone');
expect(body2.visitor.phone[0].phoneNumber).to.equal(phone);
});
+ it('should update a visitor custom fields when customFields key is provided', async () => {
+ const token = `${new Date().getTime()}-test`;
+ const customFieldName = `new_custom_field_${Date.now()}`;
+ await createCustomField({
+ searchable: true,
+ field: customFieldName,
+ label: customFieldName,
+ defaultValue: 'test_default_address',
+ scope: 'visitor',
+ visibility: 'public',
+ regexp: '',
+ });
+ const { body } = await request.post(api('livechat/visitor')).send({
+ visitor: {
+ token,
+ customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }],
+ },
+ });
+
+ expect(body).to.have.property('success', true);
+ expect(body).to.have.property('visitor');
+ expect(body.visitor).to.have.property('token', token);
+ expect(body.visitor).to.have.property('livechatData');
+ expect(body.visitor.livechatData).to.have.property(customFieldName, 'Not a real address :)');
+ });
+
+ it('should not update a custom field when it does not exists', async () => {
+ const token = `${new Date().getTime()}-test`;
+ const customFieldName = `new_custom_field_${Date.now()}`;
+ const { body } = await request.post(api('livechat/visitor')).send({
+ visitor: {
+ token,
+ customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }],
+ },
+ });
+
+ expect(body).to.have.property('success', true);
+ expect(body).to.have.property('visitor');
+ expect(body.visitor).to.have.property('token', token);
+ expect(body.visitor).to.not.have.property('livechatData');
+ });
+
+ it('should not update a custom field when the scope of it is not visitor', async () => {
+ const token = `${new Date().getTime()}-test`;
+ const customFieldName = `new_custom_field_${Date.now()}`;
+ await createCustomField({
+ searchable: true,
+ field: customFieldName,
+ label: customFieldName,
+ defaultValue: 'test_default_address',
+ scope: 'room',
+ visibility: 'public',
+ regexp: '',
+ });
+ const { body } = await request.post(api('livechat/visitor')).send({
+ visitor: {
+ token,
+ customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }],
+ },
+ });
+
+ expect(body).to.have.property('success', true);
+ expect(body).to.have.property('visitor');
+ expect(body.visitor).to.have.property('token', token);
+ expect(body.visitor).to.not.have.property('livechatData');
+ });
+
+ it('should not update a custom field whe the overwrite flag is false', async () => {
+ const token = `${new Date().getTime()}-test`;
+ const customFieldName = `new_custom_field_${Date.now()}`;
+ await createCustomField({
+ searchable: true,
+ field: customFieldName,
+ label: customFieldName,
+ defaultValue: 'test_default_address',
+ scope: 'visitor',
+ visibility: 'public',
+ regexp: '',
+ });
+ await request.post(api('livechat/visitor')).send({
+ visitor: {
+ token,
+ customFields: [{ key: customFieldName, value: 'Not a real address :)', overwrite: true }],
+ },
+ });
+
+ const { body } = await request.post(api('livechat/visitor')).send({
+ visitor: {
+ token,
+ customFields: [{ key: customFieldName, value: 'This should not change!', overwrite: false }],
+ },
+ });
+
+ expect(body).to.have.property('success', true);
+ expect(body).to.have.property('visitor');
+ expect(body.visitor).to.have.property('token', token);
+ expect(body.visitor).to.have.property('livechatData');
+ expect(body.visitor.livechatData).to.have.property(customFieldName, 'Not a real address :)');
+ });
});
describe('livechat/visitors.info', () => {
diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts
index 54f8739efee3..fc9af8d4580e 100644
--- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts
+++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts
@@ -49,7 +49,16 @@ import { IS_EE } from '../../../e2e/config/constants';
const { body } = await request
.post(api('livechat/department'))
.set(credentials)
- .send({ department: { name: 'Test', enabled: true, showOnOfflineForm: true, showOnRegistration: true, email: 'bla@bla' } })
+ .send({
+ department: {
+ name: 'Test',
+ enabled: true,
+ showOnOfflineForm: true,
+ showOnRegistration: true,
+ email: 'bla@bla',
+ allowReceiveForwardOffline: true,
+ },
+ })
.expect('Content-Type', 'application/json')
.expect(200);
expect(body).to.have.property('success', true);
@@ -59,6 +68,8 @@ import { IS_EE } from '../../../e2e/config/constants';
expect(body.department).to.have.property('enabled', true);
expect(body.department).to.have.property('showOnOfflineForm', true);
expect(body.department).to.have.property('showOnRegistration', true);
+ expect(body.department).to.have.property('allowReceiveForwardOffline', true);
+
departmentId = body.department._id;
});
diff --git a/apps/meteor/tests/unit/server/services/omnichannel/queue.tests.ts b/apps/meteor/tests/unit/server/services/omnichannel/queue.tests.ts
new file mode 100644
index 000000000000..ce24b0ded64a
--- /dev/null
+++ b/apps/meteor/tests/unit/server/services/omnichannel/queue.tests.ts
@@ -0,0 +1,475 @@
+import { expect } from 'chai';
+import { beforeEach, describe, after, it } from 'mocha';
+import p from 'proxyquire';
+import Sinon from 'sinon';
+
+const dispatchAgentDelegated = Sinon.stub();
+const getConfig = Sinon.stub();
+const delegateInquiry = Sinon.stub();
+const libSettings = { getInquirySortMechanismSetting: Sinon.stub().returns('timestamp') };
+const settings = {
+ get: Sinon.stub(),
+};
+
+const queueLogger = {
+ info: Sinon.stub(),
+ debug: Sinon.stub(),
+ error: Sinon.stub(),
+};
+
+const mockedInquiry = {
+ _id: 'inquiryId',
+ rid: 'rid',
+ department: 'department1',
+ ts: new Date(),
+};
+
+const models = {
+ LivechatInquiry: {
+ unlockAll: Sinon.stub(),
+ findNextAndLock: Sinon.stub(),
+ getDistinctQueuedDepartments: Sinon.stub(),
+ unlockAndQueue: Sinon.stub(),
+ unlock: Sinon.stub(),
+ removeByRoomId: Sinon.stub(),
+ takeInquiry: Sinon.stub(),
+ },
+ LivechatRooms: {
+ findOneById: Sinon.stub(),
+ },
+};
+
+const license = {
+ shouldPreventAction: Sinon.stub(),
+};
+
+const { OmnichannelQueue } = p.noCallThru().load('../../../../../server/services/omnichannel/queue', {
+ '../../../app/livechat/server/lib/Helper': {
+ dispatchAgentDelegated,
+ },
+ '../../../app/livechat/server/lib/RoutingManager': {
+ RoutingManager: {
+ getConfig,
+ delegateInquiry,
+ },
+ },
+ '../../../app/livechat/server/lib/settings': libSettings,
+ '../../../app/settings/server': { settings },
+ './logger': { queueLogger },
+ '@rocket.chat/models': models,
+ '@rocket.chat/license': { License: license },
+});
+
+describe('Omnichannel Queue processor', () => {
+ describe('isRunning', () => {
+ it('should return the running status', () => {
+ const queue = new OmnichannelQueue();
+ expect(queue.isRunning()).to.be.false;
+ });
+ it('should return the running status', () => {
+ const queue = new OmnichannelQueue();
+ queue.running = true;
+ expect(queue.isRunning()).to.be.true;
+ });
+ });
+ describe('delay', () => {
+ after(() => {
+ settings.get.reset();
+ });
+ it('should return 5000 if setting is not set', () => {
+ settings.get.returns(undefined);
+
+ const queue = new OmnichannelQueue();
+ expect(queue.delay()).to.be.equal(5000);
+ });
+ it('should return the right value if setting has a value above 1', () => {
+ settings.get.returns(10);
+
+ const queue = new OmnichannelQueue();
+ expect(queue.delay()).to.be.equal(10000);
+ });
+ });
+ describe('getActiveQueues', () => {
+ after(() => {
+ models.LivechatInquiry.getDistinctQueuedDepartments.reset();
+ });
+ it('should return [undefined] when there is no other queues', async () => {
+ models.LivechatInquiry.getDistinctQueuedDepartments.returns([]);
+
+ const queue = new OmnichannelQueue();
+ expect(await queue.getActiveQueues()).to.be.eql([undefined]);
+ });
+ it('should return [undefined, department1] when department1 is an active queue', async () => {
+ models.LivechatInquiry.getDistinctQueuedDepartments.returns(['department1']);
+
+ const queue = new OmnichannelQueue();
+ expect(await queue.getActiveQueues()).to.be.eql([undefined, 'department1']);
+ });
+ });
+ describe('nextQueue', () => {
+ after(() => {
+ models.LivechatInquiry.getDistinctQueuedDepartments.reset();
+ });
+ it('should return undefined when thats the only queue', async () => {
+ models.LivechatInquiry.getDistinctQueuedDepartments.returns([]);
+
+ const queue = new OmnichannelQueue();
+ queue.getActiveQueues = Sinon.stub().returns([undefined]);
+ expect(await queue.nextQueue()).to.be.undefined;
+ });
+ it('should return undefined, and then the following queue', async () => {
+ models.LivechatInquiry.getDistinctQueuedDepartments.returns(['department1']);
+
+ const queue = new OmnichannelQueue();
+ queue.getActiveQueues = Sinon.stub().returns([undefined, 'department1']);
+ expect(await queue.nextQueue()).to.be.undefined;
+ expect(await queue.nextQueue()).to.be.equal('department1');
+ });
+ it('should not call getActiveQueues if there are still queues to process', async () => {
+ models.LivechatInquiry.getDistinctQueuedDepartments.returns(['department1']);
+
+ const queue = new OmnichannelQueue();
+ queue.queues = ['department1'];
+ queue.getActiveQueues = Sinon.stub();
+
+ expect(await queue.nextQueue()).to.be.equal('department1');
+ expect(queue.getActiveQueues.notCalled).to.be.true;
+ });
+ });
+ describe('checkQueue', () => {
+ let clock: any;
+ beforeEach(() => {
+ models.LivechatInquiry.findNextAndLock.resetHistory();
+ models.LivechatInquiry.takeInquiry.resetHistory();
+ models.LivechatInquiry.unlockAndQueue.resetHistory();
+ models.LivechatInquiry.unlock.resetHistory();
+ queueLogger.error.resetHistory();
+ queueLogger.info.resetHistory();
+ clock = Sinon.useFakeTimers();
+ });
+ afterEach(() => {
+ clock.restore();
+ });
+ after(() => {
+ models.LivechatInquiry.findNextAndLock.reset();
+ models.LivechatInquiry.takeInquiry.reset();
+ models.LivechatInquiry.unlockAndQueue.reset();
+ models.LivechatInquiry.unlock.reset();
+ queueLogger.error.reset();
+ queueLogger.info.reset();
+ clock.reset();
+ });
+
+ it('should return undefined when the queue is empty', async () => {
+ models.LivechatInquiry.findNextAndLock.returns(null);
+
+ const queue = new OmnichannelQueue();
+ queue.execute = Sinon.stub();
+ expect(await queue.checkQueue()).to.be.undefined;
+ });
+ it('should try to process the inquiry when there is one', async () => {
+ models.LivechatInquiry.findNextAndLock.returns(mockedInquiry);
+
+ const queue = new OmnichannelQueue();
+ queue.processWaitingQueue = Sinon.stub().throws('error');
+ queue.execute = Sinon.stub();
+ await queue.checkQueue();
+
+ expect(models.LivechatInquiry.findNextAndLock.calledOnce).to.be.true;
+ expect(queue.processWaitingQueue.calledOnce).to.be.true;
+ });
+ it('should call unlockAndRequeue when the inquiry could not be processed', async () => {
+ models.LivechatInquiry.findNextAndLock.returns(mockedInquiry);
+
+ const queue = new OmnichannelQueue();
+ queue.processWaitingQueue = Sinon.stub().returns(false);
+ queue.execute = Sinon.stub();
+ await queue.checkQueue();
+
+ expect(queue.processWaitingQueue.calledOnce).to.be.true;
+ expect(models.LivechatInquiry.unlockAndQueue.calledOnce).to.be.true;
+ });
+ it('should unlock the inquiry when it was processed succesfully', async () => {
+ models.LivechatInquiry.findNextAndLock.returns(mockedInquiry);
+
+ const queue = new OmnichannelQueue();
+ queue.processWaitingQueue = Sinon.stub().returns(true);
+ queue.execute = Sinon.stub();
+ await queue.checkQueue();
+
+ expect(queue.processWaitingQueue.calledOnce).to.be.true;
+ expect(models.LivechatInquiry.unlock.calledOnce).to.be.true;
+ });
+ it('should print a log when there was an error processing inquiry', async () => {
+ models.LivechatInquiry.findNextAndLock.throws('error');
+
+ const queue = new OmnichannelQueue();
+ queue.execute = Sinon.stub();
+ await queue.checkQueue();
+
+ expect(queueLogger.error.calledOnce).to.be.true;
+ });
+ it('should call execute after finishing', async () => {
+ models.LivechatInquiry.findNextAndLock.returns(mockedInquiry);
+
+ const queue = new OmnichannelQueue();
+ queue.processWaitingQueue = Sinon.stub().returns(true);
+ queue.execute = Sinon.stub();
+ queue.delay = Sinon.stub().returns(100);
+ await queue.checkQueue();
+ clock.tick(100);
+
+ expect(queue.execute.calledOnce).to.be.true;
+ expect(models.LivechatInquiry.unlock.calledOnce).to.be.true;
+ expect(queue.execute.calledAfter(models.LivechatInquiry.unlock)).to.be.true;
+ expect(queue.execute.calledOnce).to.be.true;
+ });
+ });
+ describe('shouldStart', () => {
+ beforeEach(() => {
+ settings.get.resetHistory();
+ getConfig.resetHistory();
+ });
+ after(() => {
+ settings.get.reset();
+ getConfig.reset();
+ });
+
+ it('should call stop if Livechat is not enabled', async () => {
+ settings.get.returns(false);
+
+ const queue = new OmnichannelQueue();
+ queue.stop = Sinon.stub();
+ await queue.shouldStart();
+
+ expect(queue.stop.calledOnce).to.be.true;
+ });
+ it('should call start if routing algorithm supports auto assignment', async () => {
+ settings.get.returns(true);
+ getConfig.returns({ autoAssignAgent: true });
+
+ const queue = new OmnichannelQueue();
+ queue.start = Sinon.stub();
+ await queue.shouldStart();
+
+ expect(queue.start.calledOnce).to.be.true;
+ expect(queue.start.calledAfter(getConfig)).to.be.true;
+ });
+ it('should call stop if routing algorithm does not support auto assignment', async () => {
+ settings.get.returns(true);
+ getConfig.returns({ autoAssignAgent: false });
+
+ const queue = new OmnichannelQueue();
+ queue.stop = Sinon.stub();
+ await queue.shouldStart();
+
+ expect(queue.stop.calledOnce).to.be.true;
+ expect(queue.stop.calledAfter(getConfig)).to.be.true;
+ });
+ });
+ describe('reconciliation', () => {
+ beforeEach(() => {
+ models.LivechatInquiry.removeByRoomId.resetHistory();
+ models.LivechatInquiry.takeInquiry.resetHistory();
+ });
+
+ it('should remove inquiries from rooms that do not exist', async () => {
+ const queue = new OmnichannelQueue();
+ await queue.reconciliation('missing', { roomId: 'rid', inquiryId: 'inquiryId' });
+
+ expect(models.LivechatInquiry.removeByRoomId.calledOnce).to.be.true;
+ });
+ it('should take an inquiry if the room was taken', async () => {
+ const queue = new OmnichannelQueue();
+ await queue.reconciliation('taken', { roomId: 'rid', inquiryId: 'inquiryId' });
+
+ expect(models.LivechatInquiry.takeInquiry.calledOnce).to.be.true;
+ });
+ it('should remove inquiries from rooms that were closed', async () => {
+ const queue = new OmnichannelQueue();
+ await queue.reconciliation('closed', { roomId: 'rid', inquiryId: 'inquiryId' });
+
+ expect(models.LivechatInquiry.removeByRoomId.calledOnce).to.be.true;
+ });
+ it('should return true for any other case', async () => {
+ const queue = new OmnichannelQueue();
+ expect(await queue.reconciliation('random', { roomId: 'rid', inquiryId: 'inquiryId' })).to.be.true;
+ expect(models.LivechatInquiry.removeByRoomId.notCalled).to.be.true;
+ expect(models.LivechatInquiry.takeInquiry.notCalled).to.be.true;
+ });
+ });
+ describe('processWaitingQueue', () => {
+ let clock: any;
+ beforeEach(() => {
+ models.LivechatRooms.findOneById.reset();
+ models.LivechatInquiry.takeInquiry.resetHistory();
+ models.LivechatInquiry.removeByRoomId.resetHistory();
+ delegateInquiry.resetHistory();
+ queueLogger.debug.resetHistory();
+ clock = Sinon.useFakeTimers();
+ });
+ afterEach(() => {
+ clock.restore();
+ });
+ after(() => {
+ models.LivechatRooms.findOneById.reset();
+ models.LivechatInquiry.takeInquiry.reset();
+ delegateInquiry.reset();
+ queueLogger.debug.reset();
+ clock.reset();
+ });
+
+ it('should process the public queue when department is undefined', async () => {
+ const queue = new OmnichannelQueue();
+
+ expect(await queue.processWaitingQueue(undefined, mockedInquiry)).to.be.true;
+ expect(queueLogger.debug.calledWith('Processing inquiry inquiryId from queue Public'));
+ expect(models.LivechatRooms.findOneById.calledOnce).to.be.true;
+ });
+ it('should call removeInquiry when findOneById returns null', async () => {
+ models.LivechatRooms.findOneById.returns(null);
+
+ const queue = new OmnichannelQueue();
+ expect(await queue.processWaitingQueue('department1', mockedInquiry)).to.be.true;
+ expect(
+ queueLogger.debug.calledWith({
+ msg: 'Room from inquiry missing. Removing inquiry',
+ roomId: 'rid',
+ inquiryId: 'inquiryId',
+ step: 'reconciliation',
+ }),
+ ).to.be.true;
+ expect(models.LivechatInquiry.removeByRoomId.calledOnce).to.be.true;
+ });
+ it('should call takeInquiry when findOneById returns a room thats already being served', async () => {
+ models.LivechatRooms.findOneById.returns({ _id: 'rid', servedBy: { some: 'thing' } });
+
+ const queue = new OmnichannelQueue();
+ queue.reconciliation = Sinon.stub().returns(true);
+ expect(await queue.processWaitingQueue('department1', mockedInquiry)).to.be.true;
+ expect(queue.reconciliation.calledOnce).to.be.true;
+ });
+ it('should call removeInquiry when findOneById returns a room that was closed', async () => {
+ models.LivechatRooms.findOneById.returns({ _id: 'rid', closedAt: new Date() });
+
+ const queue = new OmnichannelQueue();
+ queue.reconciliation = Sinon.stub().returns(true);
+ expect(await queue.processWaitingQueue('department1', mockedInquiry)).to.be.true;
+ expect(queue.reconciliation.calledOnce).to.be.true;
+ });
+ it('should call delegateInquiry when prechecks are met and return false if inquiry was not served', async () => {
+ models.LivechatRooms.findOneById.returns({ _id: 'rid' });
+ delegateInquiry.returns({});
+
+ const queue = new OmnichannelQueue();
+ expect(await queue.processWaitingQueue('department1', mockedInquiry)).to.be.false;
+ expect(delegateInquiry.calledOnce).to.be.true;
+ });
+ it('should call delegateInquiry and return true if inquiry was served', async () => {
+ models.LivechatRooms.findOneById.returns({ _id: 'rid' });
+ delegateInquiry.returns({ _id: 'rid', servedBy: { _id: 'agentId' } });
+
+ const queue = new OmnichannelQueue();
+ expect(await queue.processWaitingQueue('department1', mockedInquiry)).to.be.true;
+ expect(delegateInquiry.calledOnce).to.be.true;
+ });
+ it('should call dispatchAgentDelegated if inquiry was served (after 1s)', async () => {
+ models.LivechatRooms.findOneById.returns({ _id: 'rid' });
+ delegateInquiry.returns({ _id: 'rid', servedBy: { _id: 'agentId' } });
+
+ const queue = new OmnichannelQueue();
+ expect(await queue.processWaitingQueue('department1', mockedInquiry)).to.be.true;
+ expect(delegateInquiry.calledOnce).to.be.true;
+ clock.tick(1000);
+ expect(dispatchAgentDelegated.calledOnce).to.be.true;
+ });
+ });
+ describe('execute', () => {
+ beforeEach(() => {
+ license.shouldPreventAction.reset();
+ queueLogger.debug.reset();
+ });
+
+ after(() => {
+ license.shouldPreventAction.reset();
+ queueLogger.debug.reset();
+ });
+
+ it('should return undefined if service is not running', async () => {
+ const queue = new OmnichannelQueue();
+ queue.running = false;
+ expect(await queue.execute()).to.be.undefined;
+ });
+ it('should return undefined if license is over mac limits', async () => {
+ license.shouldPreventAction.returns(true);
+
+ const queue = new OmnichannelQueue();
+ queue.running = true;
+ expect(await queue.execute()).to.be.undefined;
+ expect(license.shouldPreventAction.calledOnce).to.be.true;
+ expect(queue.running).to.be.false;
+ });
+ it('should try to process a queue if license is not over mac limits', async () => {
+ license.shouldPreventAction.returns(false);
+
+ const queue = new OmnichannelQueue();
+ queue.running = true;
+ queue.nextQueue = Sinon.stub();
+ await queue.execute();
+
+ expect(queue.nextQueue.calledOnce).to.be.true;
+ expect(queueLogger.debug.calledWith('Executing queue Public with timeout of 5000')).to.be.true;
+ });
+ });
+ describe('start', () => {
+ beforeEach(() => {
+ queueLogger.info.resetHistory();
+ queueLogger.debug.resetHistory();
+ });
+ after(() => {
+ queueLogger.info.reset();
+ queueLogger.debug.reset();
+ });
+ it('should do nothing if queue is already running', async () => {
+ const queue = new OmnichannelQueue();
+ queue.running = true;
+ queue.execute = Sinon.stub();
+ await queue.start();
+
+ expect(queue.execute.notCalled).to.be.true;
+ });
+ it('should fetch active queues and set running to true', async () => {
+ const queue = new OmnichannelQueue();
+ queue.running = false;
+ queue.getActiveQueues = Sinon.stub().returns(['department1']);
+ queue.execute = Sinon.stub();
+ await queue.start();
+
+ expect(queue.running).to.be.true;
+ expect(queue.getActiveQueues.calledOnce).to.be.true;
+ expect(queueLogger.info.calledOnce).to.be.true;
+ expect(queueLogger.info.calledWith('Service started')).to.be.true;
+ expect(queue.execute.calledOnce).to.be.true;
+ });
+ });
+ describe('stop', () => {
+ beforeEach(() => {
+ models.LivechatInquiry.unlockAll.reset();
+ queueLogger.info.resetHistory();
+ });
+ after(() => {
+ models.LivechatInquiry.unlockAll.reset();
+ queueLogger.info.reset();
+ });
+ it('should unlock all inquiries and set running to false', async () => {
+ const queue = new OmnichannelQueue();
+ queue.running = true;
+ await queue.stop();
+
+ expect(queue.running).to.be.false;
+ expect(models.LivechatInquiry.unlockAll.calledOnce).to.be.true;
+ expect(queueLogger.info.calledOnce).to.be.true;
+ expect(queueLogger.info.calledWith('Service stopped')).to.be.true;
+ });
+ });
+});
diff --git a/apps/meteor/tests/unit/server/startup/initialData.tests.ts b/apps/meteor/tests/unit/server/startup/initialData.tests.ts
new file mode 100644
index 000000000000..c9980db5ba3e
--- /dev/null
+++ b/apps/meteor/tests/unit/server/startup/initialData.tests.ts
@@ -0,0 +1,235 @@
+import { expect } from 'chai';
+import { beforeEach, it } from 'mocha';
+import proxyquire from 'proxyquire';
+import sinon from 'sinon';
+
+const getUsersInRole = sinon.stub();
+const checkUsernameAvailability = sinon.stub();
+const validateEmail = sinon.stub();
+const addUserRolesAsync = sinon.stub();
+const models = {
+ Settings: {},
+ Rooms: {},
+ Users: {
+ create: sinon.stub(),
+ findOneByEmailAddress: sinon.stub(),
+ },
+};
+const setPasswordAsync = sinon.stub();
+const settingsGet = sinon.stub();
+
+const { insertAdminUserFromEnv } = proxyquire.noCallThru().load('../../../../server/startup/initialData.js', {
+ 'meteor/accounts-base': {
+ Accounts: {
+ setPasswordAsync,
+ },
+ },
+ 'meteor/meteor': {
+ Meteor: {
+ startup: sinon.stub(),
+ },
+ },
+ '../../app/authorization/server': {
+ getUsersInRole,
+ },
+ '../../app/file-upload/server': {},
+ '../../app/file/server': {},
+ '../../app/lib/server/functions/addUserToDefaultChannels': {},
+ '../../app/lib/server/functions/checkUsernameAvailability': {
+ checkUsernameAvailability,
+ },
+ '../../app/settings/server': {
+ settings: { get: settingsGet },
+ },
+ '../../lib/emailValidator': {
+ validateEmail,
+ },
+ '../lib/roles/addUserRoles': {
+ addUserRolesAsync,
+ },
+ '@rocket.chat/models': models,
+});
+
+describe('insertAdminUserFromEnv', () => {
+ beforeEach(() => {
+ getUsersInRole.reset();
+ checkUsernameAvailability.reset();
+ validateEmail.reset();
+ addUserRolesAsync.reset();
+ models.Users.create.reset();
+ models.Users.findOneByEmailAddress.reset();
+ setPasswordAsync.reset();
+ settingsGet.reset();
+ process.env.ADMIN_PASS = 'pass';
+ });
+
+ it('should do nothing if process.env.ADMIN_PASS is empty', async () => {
+ process.env.ADMIN_PASS = '';
+ const result = await insertAdminUserFromEnv();
+ expect(getUsersInRole.called).to.be.false;
+ expect(result).to.be.undefined;
+ });
+ it('should do nothing if theres already an admin user', async () => {
+ getUsersInRole.returns({ count: () => 1 });
+
+ const result = await insertAdminUserFromEnv();
+ expect(getUsersInRole.called).to.be.true;
+ expect(validateEmail.called).to.be.false;
+ expect(result).to.be.undefined;
+ });
+ it('should try to validate an email when process.env.ADMIN_EMAIL is set', async () => {
+ process.env.ADMIN_EMAIL = 'email';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(false);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+
+ const result = await insertAdminUserFromEnv();
+ expect(getUsersInRole.called).to.be.true;
+ expect(validateEmail.called).to.be.true;
+ expect(validateEmail.calledWith('email')).to.be.true;
+ expect(models.Users.create.called).to.be.true;
+ expect(setPasswordAsync.called).to.be.true;
+ expect(result).to.be.undefined;
+ });
+ it('should override the admins name when process.env.ADMIN_NAME is set', async () => {
+ process.env.ADMIN_EMAIL = 'email';
+ process.env.ADMIN_NAME = 'name';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ validateEmail.returns(false);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+
+ await insertAdminUserFromEnv();
+
+ expect(
+ models.Users.create.calledWith(
+ sinon.match({
+ name: 'name',
+ username: 'admin',
+ status: 'offline',
+ statusDefault: 'online',
+ utcOffset: 0,
+ active: true,
+ type: 'user',
+ }),
+ ),
+ ).to.be.true;
+ });
+ it('should ignore the admin email when another user already has it set', async () => {
+ process.env.ADMIN_EMAIL = 'email';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+ models.Users.findOneByEmailAddress.returns({ _id: 'someuser' });
+
+ await insertAdminUserFromEnv();
+
+ expect(models.Users.create.getCall(0).firstArg).to.not.to.have.property('email');
+ });
+ it('should add the email from env when its valid and no users are using it', async () => {
+ process.env.ADMIN_EMAIL = 'email';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+ models.Users.findOneByEmailAddress.returns(undefined);
+
+ await insertAdminUserFromEnv();
+
+ expect(models.Users.create.getCall(0).firstArg)
+ .to.have.property('emails')
+ .to.deep.equal([{ address: 'email', verified: false }]);
+ });
+ it('should mark the admin email as verified when process.env.ADMIN_EMAIL_VERIFIED is set to true', async () => {
+ process.env.ADMIN_EMAIL = 'email';
+ process.env.ADMIN_EMAIL_VERIFIED = 'true';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+ models.Users.findOneByEmailAddress.returns(undefined);
+
+ await insertAdminUserFromEnv();
+
+ expect(models.Users.create.getCall(0).firstArg)
+ .to.have.property('emails')
+ .to.deep.equal([{ address: 'email', verified: true }]);
+ });
+ it('should validate a username with setting UTF8_User_Names_Validation when process.env.ADMIN_USERNAME is set', async () => {
+ process.env.ADMIN_USERNAME = '1234';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ settingsGet.returns('[0-9]+');
+ models.Users.create.returns({ insertedId: 'newuserid' });
+
+ await insertAdminUserFromEnv();
+
+ expect(checkUsernameAvailability.called).to.be.true;
+ });
+ it('should override the username from admin if the env ADMIN_USERNAME is set, is valid and the username is available', async () => {
+ process.env.ADMIN_USERNAME = '1234';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ settingsGet.returns('[0-9]+');
+ checkUsernameAvailability.returns(true);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+
+ await insertAdminUserFromEnv();
+
+ expect(models.Users.create.calledWith(sinon.match({ username: '1234' }))).to.be.true;
+ });
+ it('should ignore the username when it does not pass setting regexp validation', async () => {
+ process.env.ADMIN_USERNAME = '1234';
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ settingsGet.returns('[A-Z]+');
+ checkUsernameAvailability.returns(true);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+
+ await insertAdminUserFromEnv();
+
+ expect(models.Users.create.calledWith(sinon.match({ username: 'admin' }))).to.be.true;
+ });
+ it('should call addUserRolesAsync as the last step when all data is valid and all overrides are valid', async () => {
+ process.env.ADMIN_EMAIL = 'email';
+ process.env.ADMIN_NAME = 'name';
+ process.env.ADMIN_USERNAME = '1234';
+ process.env.ADMIN_EMAIL_VERIFIED = 'true';
+
+ getUsersInRole.returns({ count: () => 0 });
+ validateEmail.returns(true);
+ settingsGet.returns('[0-9]+');
+ checkUsernameAvailability.returns(true);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+ models.Users.findOneByEmailAddress.returns(undefined);
+
+ await insertAdminUserFromEnv();
+
+ expect(addUserRolesAsync.called).to.be.true;
+ expect(setPasswordAsync.called).to.be.true;
+ expect(models.Users.create.calledWith(sinon.match({ name: 'name', username: '1234', emails: [{ address: 'email', verified: true }] })))
+ .to.be.true;
+ });
+ it('should use the default nameValidation regex when the regex on the setting is invalid', async () => {
+ process.env.ADMIN_NAME = 'name';
+ process.env.ADMIN_USERNAME = '$$$$$$';
+
+ getUsersInRole.returns({ count: () => 0 });
+ settingsGet.returns('[');
+ checkUsernameAvailability.returns(true);
+ models.Users.create.returns({ insertedId: 'newuserid' });
+
+ await insertAdminUserFromEnv();
+
+ expect(models.Users.create.calledWith(sinon.match({ username: 'admin' })));
+ });
+ it('should ignore the username when is not available', async () => {
+ process.env.ADMIN_USERNAME = '1234';
+
+ getUsersInRole.returns({ count: () => 0 });
+ checkUsernameAvailability.throws('some error');
+ models.Users.create.returns({ insertedId: 'newuserid' });
+
+ await insertAdminUserFromEnv();
+
+ expect(models.Users.create.calledWith(sinon.match({ username: 'admin' }))).to.be.true;
+ });
+});
diff --git a/ee/apps/account-service/CHANGELOG.md b/ee/apps/account-service/CHANGELOG.md
index 63bffcb05637..bd0c5fd051b9 100644
--- a/ee/apps/account-service/CHANGELOG.md
+++ b/ee/apps/account-service/CHANGELOG.md
@@ -1,5 +1,70 @@
# @rocket.chat/account-service
+## 0.3.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/rest-typings@6.6.6
+ - @rocket.chat/model-typings@0.3.6
+
+
+## 0.3.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/rest-typings@6.6.5
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+
+
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/rest-typings@6.6.4
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.3.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/rest-typings@6.6.3
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/models@0.0.31
+
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/rest-typings@6.6.2
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+
+
## 0.3.5
### Patch Changes
diff --git a/ee/apps/account-service/package.json b/ee/apps/account-service/package.json
index f02242b0661e..56965945c5e1 100644
--- a/ee/apps/account-service/package.json
+++ b/ee/apps/account-service/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/account-service",
"private": true,
- "version": "0.3.5",
+ "version": "0.3.10",
"description": "Rocket.Chat Account service",
"scripts": {
"build": "tsc -p tsconfig.json",
diff --git a/ee/apps/authorization-service/CHANGELOG.md b/ee/apps/authorization-service/CHANGELOG.md
index 221defa5426b..f2230808862d 100644
--- a/ee/apps/authorization-service/CHANGELOG.md
+++ b/ee/apps/authorization-service/CHANGELOG.md
@@ -1,5 +1,70 @@
# @rocket.chat/authorization-service
+## 0.3.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/rest-typings@6.6.6
+ - @rocket.chat/model-typings@0.3.6
+
+
+## 0.3.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/rest-typings@6.6.5
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+
+
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/rest-typings@6.6.4
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.3.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/rest-typings@6.6.3
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/models@0.0.31
+
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/rest-typings@6.6.2
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+
+
## 0.3.5
### Patch Changes
diff --git a/ee/apps/authorization-service/package.json b/ee/apps/authorization-service/package.json
index 18caff67ffbc..98c007876335 100644
--- a/ee/apps/authorization-service/package.json
+++ b/ee/apps/authorization-service/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/authorization-service",
"private": true,
- "version": "0.3.5",
+ "version": "0.3.10",
"description": "Rocket.Chat Authorization service",
"scripts": {
"build": "tsc -p tsconfig.json",
diff --git a/ee/apps/ddp-streamer/CHANGELOG.md b/ee/apps/ddp-streamer/CHANGELOG.md
index 86b751f67cc3..2fcfebc4c5f4 100644
--- a/ee/apps/ddp-streamer/CHANGELOG.md
+++ b/ee/apps/ddp-streamer/CHANGELOG.md
@@ -1,5 +1,82 @@
# @rocket.chat/ddp-streamer
+## 0.2.9
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/instance-status@0.0.34
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/rest-typings@6.6.6
+ - @rocket.chat/model-typings@0.3.6
+ - @rocket.chat/ui-contexts@4.0.6
+
+
+## 0.2.8
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/ui-contexts@4.0.5
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/rest-typings@6.6.5
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+ - @rocket.chat/instance-status@0.0.33
+
+
+## 0.2.7
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/rest-typings@6.6.4
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/ui-contexts@4.0.4
+ - @rocket.chat/models@0.0.32
+ - @rocket.chat/instance-status@0.0.32
+
+
+## 0.2.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/rest-typings@6.6.3
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/ui-contexts@4.0.3
+ - @rocket.chat/models@0.0.31
+ - @rocket.chat/instance-status@0.0.31
+
+
+## 0.2.5
+
+### Patch Changes
+
+- ([#31833](https://github.com/RocketChat/Rocket.Chat/pull/31833)) Fix web UI not showing users presence updating to offline
+
+- Updated dependencies []:
+
+ - @rocket.chat/ui-contexts@4.0.2
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/rest-typings@6.6.2
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+ - @rocket.chat/instance-status@0.0.30
+
+
## 0.2.5
### Patch Changes
diff --git a/ee/apps/ddp-streamer/package.json b/ee/apps/ddp-streamer/package.json
index 78f54af6d339..b42bad5b0aad 100644
--- a/ee/apps/ddp-streamer/package.json
+++ b/ee/apps/ddp-streamer/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/ddp-streamer",
"private": true,
- "version": "0.2.5-next.1",
+ "version": "0.2.9",
"description": "Rocket.Chat DDP-Streamer service",
"scripts": {
"build": "tsc -p tsconfig.json",
diff --git a/ee/apps/omnichannel-transcript/CHANGELOG.md b/ee/apps/omnichannel-transcript/CHANGELOG.md
index 4f152d8d02b4..ed363a2a89c2 100644
--- a/ee/apps/omnichannel-transcript/CHANGELOG.md
+++ b/ee/apps/omnichannel-transcript/CHANGELOG.md
@@ -1,5 +1,75 @@
# @rocket.chat/omnichannel-transcript
+## 0.3.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/omnichannel-services@0.1.10
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/pdf-worker@0.0.34
+ - @rocket.chat/model-typings@0.3.6
+
+
+## 0.3.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/omnichannel-services@0.1.9
+ - @rocket.chat/pdf-worker@0.0.33
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+
+
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/omnichannel-services@0.1.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/pdf-worker@0.0.32
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.3.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/omnichannel-services@0.1.7
+ - @rocket.chat/pdf-worker@0.0.31
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/models@0.0.31
+
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/omnichannel-services@0.1.6
+ - @rocket.chat/pdf-worker@0.0.30
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+
+
## 0.3.5
### Patch Changes
diff --git a/ee/apps/omnichannel-transcript/package.json b/ee/apps/omnichannel-transcript/package.json
index 3e7a6b1f44e9..0083cdc5fdc2 100644
--- a/ee/apps/omnichannel-transcript/package.json
+++ b/ee/apps/omnichannel-transcript/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/omnichannel-transcript",
"private": true,
- "version": "0.3.5",
+ "version": "0.3.10",
"description": "Rocket.Chat service",
"scripts": {
"build": "tsc -p tsconfig.json",
diff --git a/ee/apps/presence-service/CHANGELOG.md b/ee/apps/presence-service/CHANGELOG.md
index 598d20d46bb2..ff6d4ab2fabd 100644
--- a/ee/apps/presence-service/CHANGELOG.md
+++ b/ee/apps/presence-service/CHANGELOG.md
@@ -1,5 +1,70 @@
# @rocket.chat/presence-service
+## 0.3.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/presence@0.1.10
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/model-typings@0.3.6
+
+
+## 0.3.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/presence@0.1.9
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+
+
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/presence@0.1.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.3.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/presence@0.1.7
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/models@0.0.31
+
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/presence@0.1.6
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+
+
## 0.3.5
### Patch Changes
diff --git a/ee/apps/presence-service/package.json b/ee/apps/presence-service/package.json
index e682630ee594..e45b442b4255 100644
--- a/ee/apps/presence-service/package.json
+++ b/ee/apps/presence-service/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/presence-service",
"private": true,
- "version": "0.3.5",
+ "version": "0.3.10",
"description": "Rocket.Chat Presence service",
"scripts": {
"build": "tsc -p tsconfig.json",
diff --git a/ee/apps/queue-worker/CHANGELOG.md b/ee/apps/queue-worker/CHANGELOG.md
index 790c9949a02b..22dbe407505b 100644
--- a/ee/apps/queue-worker/CHANGELOG.md
+++ b/ee/apps/queue-worker/CHANGELOG.md
@@ -1,5 +1,70 @@
# @rocket.chat/queue-worker
+## 0.3.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/omnichannel-services@0.1.10
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/model-typings@0.3.6
+
+
+## 0.3.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/omnichannel-services@0.1.9
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+
+
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/omnichannel-services@0.1.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.3.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/omnichannel-services@0.1.7
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/models@0.0.31
+
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/omnichannel-services@0.1.6
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+
+
## 0.3.5
### Patch Changes
diff --git a/ee/apps/queue-worker/package.json b/ee/apps/queue-worker/package.json
index 80d7b604f964..755cc04212ab 100644
--- a/ee/apps/queue-worker/package.json
+++ b/ee/apps/queue-worker/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/queue-worker",
"private": true,
- "version": "0.3.5",
+ "version": "0.3.10",
"description": "Rocket.Chat service",
"scripts": {
"build": "tsc -p tsconfig.json",
diff --git a/ee/apps/stream-hub-service/CHANGELOG.md b/ee/apps/stream-hub-service/CHANGELOG.md
index 84bdf503ac7c..7135c4472dd0 100644
--- a/ee/apps/stream-hub-service/CHANGELOG.md
+++ b/ee/apps/stream-hub-service/CHANGELOG.md
@@ -1,5 +1,65 @@
# @rocket.chat/stream-hub-service
+## 0.3.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/model-typings@0.3.6
+
+
+## 0.3.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+
+
+## 0.3.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.3.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/models@0.0.31
+
+
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+
+
## 0.3.5
### Patch Changes
diff --git a/ee/apps/stream-hub-service/package.json b/ee/apps/stream-hub-service/package.json
index ba0cefb140dd..c29e878d6917 100644
--- a/ee/apps/stream-hub-service/package.json
+++ b/ee/apps/stream-hub-service/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/stream-hub-service",
"private": true,
- "version": "0.3.5",
+ "version": "0.3.10",
"description": "Rocket.Chat Stream Hub service",
"scripts": {
"build": "tsc -p tsconfig.json",
diff --git a/ee/packages/api-client/CHANGELOG.md b/ee/packages/api-client/CHANGELOG.md
index c149cd5cabf9..5650bb863880 100644
--- a/ee/packages/api-client/CHANGELOG.md
+++ b/ee/packages/api-client/CHANGELOG.md
@@ -1,5 +1,55 @@
# @rocket.chat/api-client
+## 0.1.28
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/rest-typings@6.6.6
+
+
+## 0.1.27
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/rest-typings@6.6.5
+
+
+## 0.1.26
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/rest-typings@6.6.4
+
+
+## 0.1.25
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/rest-typings@6.6.3
+
+
+## 0.1.24
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/rest-typings@6.6.2
+
+
## 0.1.23
### Patch Changes
diff --git a/ee/packages/api-client/package.json b/ee/packages/api-client/package.json
index 93a60df954d5..a6d48efaf22c 100644
--- a/ee/packages/api-client/package.json
+++ b/ee/packages/api-client/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/api-client",
"private": true,
- "version": "0.1.23",
+ "version": "0.1.28",
"devDependencies": {
"@swc/core": "^1.3.95",
"@swc/jest": "^0.2.29",
diff --git a/ee/packages/ddp-client/CHANGELOG.md b/ee/packages/ddp-client/CHANGELOG.md
index 42e11557ba86..b62e5979bee8 100644
--- a/ee/packages/ddp-client/CHANGELOG.md
+++ b/ee/packages/ddp-client/CHANGELOG.md
@@ -1,5 +1,59 @@
# @rocket.chat/ddp-client
+## 0.2.19
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/rest-typings@6.6.6
+ - @rocket.chat/api-client@0.1.28
+
+
+## 0.2.18
+
+### Patch Changes
+
+- ([#31941](https://github.com/RocketChat/Rocket.Chat/pull/31941)) fix: livechat sdk reconnect not resubscribing
+
+- Updated dependencies []:
+
+ - @rocket.chat/rest-typings@6.6.5
+ - @rocket.chat/api-client@0.1.27
+
+
+## 0.2.17
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/rest-typings@6.6.4
+ - @rocket.chat/api-client@0.1.26
+
+
+## 0.2.16
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/rest-typings@6.6.3
+ - @rocket.chat/api-client@0.1.25
+
+
+## 0.2.15
+
+### Patch Changes
+
+- ([#31823](https://github.com/RocketChat/Rocket.Chat/pull/31823)) Revert unintentional changes real time presence data payload
+
+- Updated dependencies []:
+
+ - @rocket.chat/rest-typings@6.6.2
+ - @rocket.chat/api-client@0.1.24
+
+
## 0.2.14
### Patch Changes
diff --git a/ee/packages/ddp-client/package.json b/ee/packages/ddp-client/package.json
index c4b632b4bd7f..ed56d9c40bcc 100644
--- a/ee/packages/ddp-client/package.json
+++ b/ee/packages/ddp-client/package.json
@@ -1,7 +1,7 @@
{
"name": "@rocket.chat/ddp-client",
"private": true,
- "version": "0.2.14",
+ "version": "0.2.19",
"devDependencies": {
"@swc/core": "^1.3.95",
"@swc/jest": "^0.2.29",
diff --git a/ee/packages/license/CHANGELOG.md b/ee/packages/license/CHANGELOG.md
index 1059f9fd8cf4..a9b1da8c2d63 100644
--- a/ee/packages/license/CHANGELOG.md
+++ b/ee/packages/license/CHANGELOG.md
@@ -1,5 +1,50 @@
# @rocket.chat/license
+## 0.1.10
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.6
+
+
+## 0.1.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+
+
+## 0.1.8
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.4
+
+
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+
+
+## 0.1.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+
+
## 0.1.5
### Patch Changes
diff --git a/ee/packages/license/package.json b/ee/packages/license/package.json
index f5f9f9dbe8ec..bdcc46518948 100644
--- a/ee/packages/license/package.json
+++ b/ee/packages/license/package.json
@@ -1,6 +1,6 @@
{
"name": "@rocket.chat/license",
- "version": "0.1.5",
+ "version": "0.1.10",
"private": true,
"devDependencies": {
"@swc/core": "^1.3.95",
diff --git a/ee/packages/omnichannel-services/CHANGELOG.md b/ee/packages/omnichannel-services/CHANGELOG.md
index a7ae2cd1e919..38665b7a4d5f 100644
--- a/ee/packages/omnichannel-services/CHANGELOG.md
+++ b/ee/packages/omnichannel-services/CHANGELOG.md
@@ -1,5 +1,75 @@
# @rocket.chat/omnichannel-services
+## 0.1.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+ - @rocket.chat/rest-typings@6.6.6
+ - @rocket.chat/pdf-worker@0.0.34
+ - @rocket.chat/model-typings@0.3.6
+
+
+## 0.1.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/rest-typings@6.6.5
+ - @rocket.chat/pdf-worker@0.0.33
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/model-typings@0.3.5
+ - @rocket.chat/models@0.0.33
+
+
+## 0.1.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/rest-typings@6.6.4
+ - @rocket.chat/pdf-worker@0.0.32
+ - @rocket.chat/model-typings@0.3.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/rest-typings@6.6.3
+ - @rocket.chat/pdf-worker@0.0.31
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/model-typings@0.3.3
+ - @rocket.chat/models@0.0.31
+
+
+## 0.1.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/rest-typings@6.6.2
+ - @rocket.chat/pdf-worker@0.0.30
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/model-typings@0.3.2
+ - @rocket.chat/models@0.0.30
+
+
## 0.1.5
### Patch Changes
diff --git a/ee/packages/omnichannel-services/package.json b/ee/packages/omnichannel-services/package.json
index 253f44999d97..3d0ef1897018 100644
--- a/ee/packages/omnichannel-services/package.json
+++ b/ee/packages/omnichannel-services/package.json
@@ -1,6 +1,6 @@
{
"name": "@rocket.chat/omnichannel-services",
- "version": "0.1.5",
+ "version": "0.1.10",
"private": true,
"devDependencies": {
"@rocket.chat/eslint-config": "workspace:^",
diff --git a/ee/packages/pdf-worker/CHANGELOG.md b/ee/packages/pdf-worker/CHANGELOG.md
index 517741f52c7f..741430705396 100644
--- a/ee/packages/pdf-worker/CHANGELOG.md
+++ b/ee/packages/pdf-worker/CHANGELOG.md
@@ -1,5 +1,50 @@
# @rocket.chat/pdf-worker
+## 0.0.34
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.6
+
+
+## 0.0.33
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+
+
+## 0.0.32
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.4
+
+
+## 0.0.31
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+
+
+## 0.0.30
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+
+
## 0.0.29
### Patch Changes
diff --git a/ee/packages/pdf-worker/package.json b/ee/packages/pdf-worker/package.json
index 37ad74302b46..4e653d4007be 100644
--- a/ee/packages/pdf-worker/package.json
+++ b/ee/packages/pdf-worker/package.json
@@ -1,6 +1,6 @@
{
"name": "@rocket.chat/pdf-worker",
- "version": "0.0.29",
+ "version": "0.0.34",
"private": true,
"devDependencies": {
"@storybook/addon-essentials": "~6.5.16",
diff --git a/ee/packages/presence/CHANGELOG.md b/ee/packages/presence/CHANGELOG.md
index 254c00aa2aca..b5feae0ced63 100644
--- a/ee/packages/presence/CHANGELOG.md
+++ b/ee/packages/presence/CHANGELOG.md
@@ -1,5 +1,60 @@
# @rocket.chat/presence
+## 0.1.10
+
+### Patch Changes
+
+- Updated dependencies [ada096901a]:
+
+ - @rocket.chat/models@0.0.34
+ - @rocket.chat/core-services@0.3.10
+ - @rocket.chat/core-typings@6.6.6
+
+
+## 0.1.9
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.5
+ - @rocket.chat/core-services@0.3.9
+ - @rocket.chat/models@0.0.33
+
+
+## 0.1.8
+
+### Patch Changes
+
+- Updated dependencies [c2872a93f2]:
+
+ - @rocket.chat/core-services@0.3.8
+ - @rocket.chat/core-typings@6.6.4
+ - @rocket.chat/models@0.0.32
+
+
+## 0.1.7
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.3
+ - @rocket.chat/core-services@0.3.7
+ - @rocket.chat/models@0.0.31
+
+
+## 0.1.6
+
+### Patch Changes
+
+- Updated dependencies []:
+
+ - @rocket.chat/core-typings@6.6.2
+ - @rocket.chat/core-services@0.3.6
+ - @rocket.chat/models@0.0.30
+
+
## 0.1.5
### Patch Changes
diff --git a/ee/packages/presence/package.json b/ee/packages/presence/package.json
index fc862e4a7194..82dce9fcca3e 100644
--- a/ee/packages/presence/package.json
+++ b/ee/packages/presence/package.json
@@ -1,6 +1,6 @@
{
"name": "@rocket.chat/presence",
- "version": "0.1.5",
+ "version": "0.1.10",
"private": true,
"devDependencies": {
"@babel/core": "~7.22.20",
diff --git a/ee/packages/ui-theming/package.json b/ee/packages/ui-theming/package.json
index e4b2908ff0a7..32b628c6835a 100644
--- a/ee/packages/ui-theming/package.json
+++ b/ee/packages/ui-theming/package.json
@@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@rocket.chat/css-in-js": "~0.31.25",
- "@rocket.chat/fuselage": "^0.51.1",
+ "@rocket.chat/fuselage": "^0.52.0",
"@rocket.chat/fuselage-hooks": "^0.33.0",
"@rocket.chat/icons": "^0.34.0",
"@rocket.chat/ui-contexts": "workspace:~",
diff --git a/package.json b/package.json
index 1498a54ec200..240e1a9a1e02 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rocket.chat",
- "version": "6.7.0-develop",
+ "version": "6.8.0-develop",
"description": "Rocket.Chat Monorepo",
"main": "index.js",
"private": true,
diff --git a/packages/apps/src/AppsEngine.ts b/packages/apps/src/AppsEngine.ts
index 117e93c0ec2f..856bc1253790 100644
--- a/packages/apps/src/AppsEngine.ts
+++ b/packages/apps/src/AppsEngine.ts
@@ -8,6 +8,7 @@ export type {
IVisitorPhone as IAppsVisitorPhone,
} from '@rocket.chat/apps-engine/definition/livechat';
export type { IMessage as IAppsMessage } from '@rocket.chat/apps-engine/definition/messages';
+export { AppInterface as AppEvents } from '@rocket.chat/apps-engine/definition/metadata';
export type { IUser as IAppsUser } from '@rocket.chat/apps-engine/definition/users';
export type { IRole as IAppsRole } from '@rocket.chat/apps-engine/definition/roles';
export type { IRoom as IAppsRoom } from '@rocket.chat/apps-engine/definition/rooms';
@@ -18,3 +19,5 @@ export type {
VideoConference as AppsVideoConference,
} from '@rocket.chat/apps-engine/definition/videoConferences';
export { AppManager } from '@rocket.chat/apps-engine/server/AppManager';
+export { AppBridges } from '@rocket.chat/apps-engine/server/bridges';
+export { AppMetadataStorage } from '@rocket.chat/apps-engine/server/storage';
diff --git a/packages/apps/src/IAppServerOrchestrator.ts b/packages/apps/src/IAppServerOrchestrator.ts
index dbfc5aee7a20..2f1f7db5d4b5 100644
--- a/packages/apps/src/IAppServerOrchestrator.ts
+++ b/packages/apps/src/IAppServerOrchestrator.ts
@@ -1,12 +1,16 @@
import type { AppManager } from '@rocket.chat/apps-engine/server/AppManager';
+import type { AppSourceStorage } from '@rocket.chat/apps-engine/server/storage';
import type { Logger } from '@rocket.chat/logger';
import type { IAppsPersistenceModel } from '@rocket.chat/model-typings';
+import type { AppBridges, AppEvents, AppMetadataStorage } from './AppsEngine';
import type { IAppServerNotifier } from './IAppServerNotifier';
import type { IAppConvertersMap } from './converters';
export interface IAppServerOrchestrator {
initialize(): void;
+ isInitialized(): boolean;
+ isLoaded(): boolean;
getNotifier(): IAppServerNotifier;
isDebugging(): boolean;
debugLog(...args: any[]): void;
@@ -14,4 +18,8 @@ export interface IAppServerOrchestrator {
getConverters(): IAppConvertersMap;
getPersistenceModel(): IAppsPersistenceModel;
getRocketChatLogger(): Logger;
+ triggerEvent(event: AppEvents, ...payload: any[]): Promise;
+ getBridges(): AppBridges;
+ getStorage(): AppMetadataStorage;
+ getAppSourceStorage(): AppSourceStorage;
}
diff --git a/packages/apps/src/bridges/IListenerBridge.ts b/packages/apps/src/bridges/IListenerBridge.ts
new file mode 100644
index 000000000000..faf34118cd30
--- /dev/null
+++ b/packages/apps/src/bridges/IListenerBridge.ts
@@ -0,0 +1,48 @@
+import type { IMessage, IRoom, IUser, ILivechatDepartment, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings';
+
+import type { AppEvents } from '../AppsEngine';
+
+declare module '@rocket.chat/apps-engine/server/bridges' {
+ interface IListenerBridge {
+ messageEvent(int: 'IPostMessageDeleted', message: IMessage, userDeleted: IUser): Promise;
+ messageEvent(int: 'IPostMessageReacted', message: IMessage, userReacted: IUser, reaction: string, isReacted: boolean): Promise;
+ messageEvent(int: 'IPostMessageFollowed', message: IMessage, userFollowed: IUser, isFollowed: boolean): Promise;
+ messageEvent(int: 'IPostMessagePinned', message: IMessage, userPinned: IUser, isPinned: boolean): Promise;
+ messageEvent(int: 'IPostMessageStarred', message: IMessage, userStarred: IUser, isStarred: boolean): Promise;
+ messageEvent(int: 'IPostMessageReported', message: IMessage, userReported: IUser, reason: boolean): Promise;
+
+ messageEvent(
+ int: 'IPreMessageSentPrevent' | 'IPreMessageDeletePrevent' | 'IPreMessageUpdatedPrevent',
+ message: IMessage,
+ ): Promise;
+ messageEvent(
+ int: 'IPreMessageSentExtend' | 'IPreMessageSentModify' | 'IPreMessageUpdatedExtend' | 'IPreMessageUpdatedModify',
+ message: IMessage,
+ ): Promise;
+ messageEvent(int: 'IPostMessageSent' | 'IPostMessageUpdated', message: IMessage): Promise