snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
This commit is contained in:
3510
packages/core-services/CHANGELOG.md
Normal file
3510
packages/core-services/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
6
packages/core-services/jest.config.ts
Normal file
6
packages/core-services/jest.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import server from '@rocket.chat/jest-presets/server';
|
||||
import type { Config } from 'jest';
|
||||
|
||||
export default {
|
||||
preset: server.preset,
|
||||
} satisfies Config;
|
||||
44
packages/core-services/package.json
Normal file
44
packages/core-services/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@rocket.chat/core-services",
|
||||
"version": "0.16.0",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc",
|
||||
"dev": "tsc --watch --preserveWatchOutput",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test": "jest",
|
||||
"testunit": "jest",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rocket.chat/core-typings": "workspace:^",
|
||||
"@rocket.chat/federation-sdk": "0.7.0",
|
||||
"@rocket.chat/http-router": "workspace:^",
|
||||
"@rocket.chat/icons": "^0.49.0",
|
||||
"@rocket.chat/media-signaling": "workspace:^",
|
||||
"@rocket.chat/message-parser": "workspace:^",
|
||||
"@rocket.chat/models": "workspace:^",
|
||||
"@rocket.chat/rest-typings": "workspace:^",
|
||||
"@rocket.chat/tracing": "workspace:^",
|
||||
"@rocket.chat/ui-kit": "workspace:~"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rocket.chat/apps": "workspace:^",
|
||||
"@rocket.chat/jest-presets": "workspace:~",
|
||||
"@rocket.chat/tsconfig": "workspace:*",
|
||||
"@types/jest": "~30.0.0",
|
||||
"eslint": "~9.39.5",
|
||||
"jest": "~30.2.0",
|
||||
"mongodb": "6.16.0",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
231
packages/core-services/src/LocalBroker.ts
Normal file
231
packages/core-services/src/LocalBroker.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
|
||||
import { Logger } from '@rocket.chat/logger';
|
||||
import { InstanceStatus } from '@rocket.chat/models';
|
||||
import { injectCurrentContext, tracerActiveSpan } from '@rocket.chat/tracing';
|
||||
|
||||
import { asyncLocalStorage } from '.';
|
||||
import type { EventSignatures } from './events/Events';
|
||||
import type { CallingOptions, IBroker, IBrokerNode } from './types/IBroker';
|
||||
import type { ServiceClass, IServiceClass } from './types/ServiceClass';
|
||||
|
||||
type ExtendedServiceClass = { instance: IServiceClass; dependencies: string[]; isStarted: boolean };
|
||||
|
||||
const logger = new Logger('LocalBroker');
|
||||
|
||||
const INTERVAL = 1000;
|
||||
const TIMEOUT = INTERVAL * 10;
|
||||
|
||||
export class LocalBroker implements IBroker {
|
||||
private started = false;
|
||||
|
||||
private methods = new Map<string, (...params: any) => any>();
|
||||
|
||||
private events = new EventEmitter();
|
||||
|
||||
private services = new Map<string, ExtendedServiceClass>();
|
||||
|
||||
private pendingServices: Set<string> = new Set();
|
||||
|
||||
private defaultDependencies = ['settings'];
|
||||
|
||||
async call(method: string, data: any, options?: CallingOptions): Promise<any> {
|
||||
if (options) {
|
||||
logger.warn('Options are not supported in LocalBroker');
|
||||
}
|
||||
|
||||
return tracerActiveSpan(
|
||||
`action ${method}`,
|
||||
{},
|
||||
() => {
|
||||
return asyncLocalStorage.run(
|
||||
{
|
||||
id: 'ctx.id',
|
||||
nodeID: 'ctx.nodeID',
|
||||
requestID: 'ctx.requestID',
|
||||
broker: this,
|
||||
},
|
||||
(): any => this.methods.get(method)?.(...data),
|
||||
);
|
||||
},
|
||||
injectCurrentContext(),
|
||||
);
|
||||
}
|
||||
|
||||
async destroyService(instance: ServiceClass): Promise<void> {
|
||||
const namespace = instance.getName();
|
||||
|
||||
instance.getEvents().forEach((event) => event.listeners.forEach((listener) => this.events.removeListener(event.eventName, listener)));
|
||||
|
||||
const methods =
|
||||
instance.constructor?.name === 'Object'
|
||||
? Object.getOwnPropertyNames(instance)
|
||||
: Object.getOwnPropertyNames(Object.getPrototypeOf(instance));
|
||||
|
||||
for (const method of methods) {
|
||||
if (method === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.methods.delete(`${namespace}.${method}`);
|
||||
}
|
||||
instance.removeAllListeners();
|
||||
await instance.stopped();
|
||||
|
||||
this.services.delete(namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a service and adds it to the local broker. In case of the broker is already started, it will start the service automatically.
|
||||
*/
|
||||
createService(instance: IServiceClass, serviceDependencies: string[] = []): void {
|
||||
const serviceName = instance.getName();
|
||||
|
||||
if (!serviceName || serviceName === '') {
|
||||
throw new Error('Service name cannot be empty');
|
||||
}
|
||||
|
||||
if (this.services.has(serviceName)) {
|
||||
throw new Error(`Service ${serviceName} already exists`);
|
||||
}
|
||||
|
||||
// TODO: find a better way to handle default dependencies and avoid loops
|
||||
const dependencies = [...serviceDependencies, ...(serviceName === 'settings' ? [] : this.defaultDependencies)].filter(
|
||||
(dependency) => dependency !== serviceName,
|
||||
);
|
||||
|
||||
void instance.created();
|
||||
|
||||
instance.getEvents().forEach((event) => event.listeners.forEach((listener) => this.events.on(event.eventName, listener)));
|
||||
|
||||
const methods =
|
||||
instance.constructor?.name === 'Object'
|
||||
? Object.getOwnPropertyNames(instance)
|
||||
: Object.getOwnPropertyNames(Object.getPrototypeOf(instance));
|
||||
|
||||
for (const method of methods) {
|
||||
if (method === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
const i = instance as any;
|
||||
this.methods.set(`${serviceName}.${method}`, i[method].bind(i));
|
||||
}
|
||||
|
||||
this.services.set(serviceName, { instance, dependencies, isStarted: false });
|
||||
this.registerPendingServices(Array.from(new Set([serviceName, ...dependencies])));
|
||||
|
||||
if (this.started) {
|
||||
void this.start();
|
||||
}
|
||||
}
|
||||
|
||||
onBroadcast(callback: (eventName: string, args: unknown[]) => void): void {
|
||||
this.events.on('broadcast', callback);
|
||||
}
|
||||
|
||||
async broadcast<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void> {
|
||||
void this.broadcastLocal(event, ...args);
|
||||
|
||||
this.events.emit('broadcast', event, args);
|
||||
}
|
||||
|
||||
async broadcastLocal<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void> {
|
||||
this.events.emit(event, ...args);
|
||||
}
|
||||
|
||||
async broadcastToServices<T extends keyof EventSignatures>(
|
||||
_services: string[],
|
||||
event: T,
|
||||
...args: Parameters<EventSignatures[T]>
|
||||
): Promise<void> {
|
||||
this.events.emit(event, ...args);
|
||||
}
|
||||
|
||||
async nodeList(): Promise<IBrokerNode[]> {
|
||||
// TODO models should not be called form here. we should create an abstraction to an internal service to perform this query
|
||||
const instances = await InstanceStatus.find({}, { projection: { _id: 1 } }).toArray();
|
||||
|
||||
return instances.map(({ _id }) => ({ id: _id, available: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers services to be started. We're assuming that each service will only have one level of dependencies.
|
||||
*/
|
||||
private registerPendingServices(services: string[] = []): void {
|
||||
services
|
||||
.filter((e) => !this.services.has(e) || !this.services.get(e)?.isStarted)
|
||||
.forEach((service) => this.pendingServices.add(service));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a service from the pending services set.
|
||||
*/
|
||||
private removePendingService(service: string): void {
|
||||
this.pendingServices.delete(service);
|
||||
}
|
||||
|
||||
private async startService(service: ExtendedServiceClass): Promise<void> {
|
||||
const serviceName = service.instance.getName();
|
||||
|
||||
if (typeof service === 'string') {
|
||||
logger.debug({ msg: 'Service is not in the services map. Bringing it back to queue', serviceName });
|
||||
return;
|
||||
}
|
||||
|
||||
if (service?.isStarted) {
|
||||
logger.debug({ msg: 'Service already started', serviceName });
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingDependencies = service.dependencies.filter((e) => !this.services.has(e) || !this.services.get(e)?.isStarted);
|
||||
if (pendingDependencies.length > 0) {
|
||||
logger.debug({
|
||||
msg: 'Service has dependencies that are not started yet, bringing it back to queue',
|
||||
serviceName,
|
||||
pendingDependencies,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await service.instance.started();
|
||||
this.services.set(serviceName, { ...service, isStarted: true });
|
||||
this.removePendingService(serviceName);
|
||||
|
||||
logger.debug({ msg: 'Service successfully started', serviceName });
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const intervalId = setInterval(async () => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
if (this.pendingServices.size === 0) {
|
||||
const availableServices = Array.from(this.services.values()).filter((service) => service.isStarted);
|
||||
|
||||
logger.info({ msg: 'All services available', count: availableServices.length });
|
||||
clearInterval(intervalId);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
if (elapsed > TIMEOUT) {
|
||||
clearInterval(intervalId);
|
||||
const pendingServices = Array.from(this.pendingServices).join(', ');
|
||||
const err = new Error(`Timeout while waiting for LocalBroker services: ${pendingServices}`);
|
||||
logger.error({ msg: 'Timeout while waiting for LocalBroker services', err, pendingServices });
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
for (const service of Array.from(this.pendingServices)) {
|
||||
const serviceInstance = this.services.get(service);
|
||||
if (serviceInstance) {
|
||||
await this.startService(serviceInstance);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug({ msg: 'Waiting for pending services', count: this.pendingServices.size });
|
||||
}, INTERVAL);
|
||||
});
|
||||
}
|
||||
}
|
||||
26
packages/core-services/src/MeteorError.ts
Normal file
26
packages/core-services/src/MeteorError.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export class MeteorError extends Error {
|
||||
public isClientSafe = true;
|
||||
|
||||
public readonly errorType = 'Meteor.Error';
|
||||
|
||||
public constructor(
|
||||
public readonly error: string | number,
|
||||
public readonly reason?: string,
|
||||
public readonly details?: any,
|
||||
) {
|
||||
super(`${reason ? `${reason} ` : ''}[${String(error)}]`);
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
return {
|
||||
isClientSafe: this.isClientSafe,
|
||||
errorType: this.errorType,
|
||||
error: this.error,
|
||||
reason: this.reason,
|
||||
message: this.message,
|
||||
...(this.details && { details: this.details }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const isMeteorError = (error: any): error is MeteorError => error?.errorType === 'Meteor.Error';
|
||||
4
packages/core-services/src/api.ts
Normal file
4
packages/core-services/src/api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Api } from './lib/Api';
|
||||
|
||||
// Singleton instance of the Service API
|
||||
export const api = new Api();
|
||||
309
packages/core-services/src/events/Events.ts
Normal file
309
packages/core-services/src/events/Events.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
|
||||
import type { ISetting as AppsSetting } from '@rocket.chat/apps-engine/definition/settings';
|
||||
import type {
|
||||
IEmailInbox,
|
||||
IEmoji,
|
||||
IInstanceStatus,
|
||||
IIntegration,
|
||||
IIntegrationHistory,
|
||||
ILivechatDepartmentAgents,
|
||||
ILoginServiceConfiguration,
|
||||
IMessage,
|
||||
INotificationDesktop,
|
||||
IRole,
|
||||
IRoom,
|
||||
ISetting,
|
||||
ISocketConnection,
|
||||
ISubscription,
|
||||
IUser,
|
||||
IInvite,
|
||||
ICustomSound,
|
||||
UserStatus,
|
||||
ILivechatPriority,
|
||||
VideoConference,
|
||||
ICalendarNotification,
|
||||
AtLeast,
|
||||
ILivechatInquiryRecord,
|
||||
ILivechatAgent,
|
||||
IBanner,
|
||||
ILivechatVisitor,
|
||||
LicenseLimitKind,
|
||||
ICustomUserStatus,
|
||||
IWebdavAccount,
|
||||
MessageAttachment,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import type { ClientMediaSignalBody, ServerMediaSignal } from '@rocket.chat/media-signaling';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
import type { AutoUpdateRecord } from '../types/IMeteor';
|
||||
|
||||
export type ClientAction = 'inserted' | 'updated' | 'removed' | 'changed';
|
||||
|
||||
type LoginServiceConfigurationEvent = {
|
||||
id: string;
|
||||
} & (
|
||||
| {
|
||||
clientAction: 'removed';
|
||||
data?: never;
|
||||
}
|
||||
| {
|
||||
clientAction: Omit<ClientAction, 'removed'>;
|
||||
data: Omit<Partial<ILoginServiceConfiguration>, 'secret'> & { secret?: never };
|
||||
}
|
||||
);
|
||||
|
||||
export type EventSignatures = {
|
||||
'room.video-conference': (params: { rid: string; callId: string }) => void;
|
||||
'shutdown': (params: Record<string, string[]>) => void;
|
||||
'$services.changed': (info: { localService: boolean }) => void;
|
||||
'accounts.login': (info: { userId: string; connection: ISocketConnection }) => void;
|
||||
'accounts.logout': (info: { userId: string; connection: ISocketConnection }) => void;
|
||||
'authorization.guestPermissions': (permissions: string[]) => void;
|
||||
'socket.connected': (connection: ISocketConnection) => void;
|
||||
'socket.disconnected': (connection: ISocketConnection) => void;
|
||||
'banner.new'(bannerId: string): void;
|
||||
'banner.enabled'(bannerId: string): void;
|
||||
'banner.disabled'(bannerId: string): void;
|
||||
'banner.user'(userId: string, banner: IBanner): void;
|
||||
'emoji.deleteCustom'(emoji: IEmoji): void;
|
||||
'emoji.updateCustom'(emoji: IEmoji): void;
|
||||
'license.module'(data: { module: string; valid: boolean }): void;
|
||||
'license.sync'(): void;
|
||||
'license.actions'(actions: Record<Partial<LicenseLimitKind>, boolean>): void;
|
||||
|
||||
'livechat-inquiry-queue-observer'(data: { action: string; inquiry: ILivechatInquiryRecord }): void;
|
||||
'message'(data: { action: string; message: IMessage }): void;
|
||||
'meteor.clientVersionUpdated'(data: AutoUpdateRecord): void;
|
||||
'notify.desktop'(uid: string, data: INotificationDesktop): void;
|
||||
'notify.uiInteraction'(uid: string, data: UiKit.ServerInteraction): void;
|
||||
'notify.updateInvites'(uid: string, data: { invite: Omit<IInvite, '_updatedAt'> }): void;
|
||||
'notify.ephemeralMessage'(uid: string, rid: string, message: AtLeast<IMessage, 'msg'>): void;
|
||||
'notify.webdav'(
|
||||
uid: string,
|
||||
data:
|
||||
| {
|
||||
type: 'changed';
|
||||
account: Partial<IWebdavAccount>;
|
||||
}
|
||||
| {
|
||||
type: 'removed';
|
||||
account: { _id: IWebdavAccount['_id'] };
|
||||
},
|
||||
): void;
|
||||
'notify.e2e.keyRequest'(rid: string, data: IRoom['e2eKeyId']): void;
|
||||
'notify.deleteMessage'(rid: string, data: { _id: string }): void;
|
||||
'notify.deleteMessageBulk'(
|
||||
rid: string,
|
||||
data: {
|
||||
rid: string;
|
||||
excludePinned: boolean;
|
||||
ignoreDiscussion: boolean;
|
||||
ts: Record<string, Date>;
|
||||
users: string[];
|
||||
ids?: string[]; // message ids have priority over ts
|
||||
showDeletedStatus?: boolean;
|
||||
} & (
|
||||
| {
|
||||
filesOnly: true;
|
||||
replaceFileAttachmentsWith?: MessageAttachment;
|
||||
}
|
||||
| {
|
||||
filesOnly?: false;
|
||||
}
|
||||
),
|
||||
): void;
|
||||
'notify.deleteCustomSound'(data: { soundData: ICustomSound }): void;
|
||||
'notify.updateCustomSound'(data: { soundData: ICustomSound }): void;
|
||||
'notify.calendar'(uid: string, data: ICalendarNotification): void;
|
||||
'notify.messagesRead'(data: { rid: string; until: Date; tmid?: string }): void;
|
||||
'notify.importedMessages'(data: { roomIds: string[] }): void;
|
||||
'permission.changed'(data: { clientAction: ClientAction; data: any }): void;
|
||||
'room'(data: { action: string; room: Partial<IRoom> }): void;
|
||||
'room.avatarUpdate'(room: Pick<IRoom, '_id' | 'avatarETag'>): void;
|
||||
'setting'(data: { action: string; setting: Partial<ISetting> }): void;
|
||||
'stream'([streamer, eventName, payload]: [string, string, any[]]): void;
|
||||
'subscription'(data: { action: string; subscription: Partial<ISubscription> }): void;
|
||||
'user.avatarUpdate'(user: Partial<IUser>): void;
|
||||
'user.deleted'(
|
||||
user: Pick<IUser, '_id'>,
|
||||
data:
|
||||
| {
|
||||
messageErasureType: 'Delete';
|
||||
}
|
||||
| {
|
||||
messageErasureType: 'Unlink';
|
||||
replaceByUser: { _id: IUser['_id']; username: IUser['username']; alias: string };
|
||||
},
|
||||
): void;
|
||||
'user.deleteCustomStatus'(userStatus: Omit<ICustomUserStatus, '_updatedAt'>): void;
|
||||
'user.forceLogout': (uid: string, sessionId?: string) => void;
|
||||
'user.media-signal'(data: { userId: IUser['_id']; signal: ServerMediaSignal }): void;
|
||||
'user.nameChanged'(user: Pick<IUser, '_id' | 'name' | 'username'>): void;
|
||||
'user.realNameChanged'(user: Partial<IUser>): void;
|
||||
'user.roleUpdate'(update: {
|
||||
type: 'added' | 'removed' | 'changed';
|
||||
_id: string;
|
||||
u?: { _id: IUser['_id']; username: IUser['username']; name?: IUser['name'] };
|
||||
scope?: string;
|
||||
}): void;
|
||||
'user.updateCustomStatus'(userStatus: Omit<ICustomUserStatus, '_updatedAt'>): void;
|
||||
'user.activity'(data: { user: string; isTyping: boolean; roomId: string }): void;
|
||||
'user.video-conference'(data: {
|
||||
userId: IUser['_id'];
|
||||
action: string;
|
||||
params: {
|
||||
callId: VideoConference['_id'];
|
||||
uid: IUser['_id'];
|
||||
rid: IRoom['_id'];
|
||||
};
|
||||
}): void;
|
||||
'presence.invalidateVisibility'(data: { targets?: IUser['_id'][]; viewers?: IUser['_id'][] }): void;
|
||||
'presence.status'(data: {
|
||||
user: Pick<IUser, '_id' | 'username' | 'status' | 'statusText' | 'statusSource' | 'statusExpiresAt' | 'name' | 'roles'>;
|
||||
previousStatus: UserStatus | undefined;
|
||||
}): void;
|
||||
'watch.messages'(data: { message: IMessage }): void;
|
||||
'watch.roles'(
|
||||
data:
|
||||
| { clientAction: Exclude<ClientAction, 'removed'>; role: IRole }
|
||||
| {
|
||||
clientAction: 'removed';
|
||||
role: {
|
||||
_id: string;
|
||||
name: string;
|
||||
};
|
||||
},
|
||||
): void;
|
||||
'watch.rooms'(data: { clientAction: ClientAction; room: Pick<IRoom, '_id'> | IRoom }): void;
|
||||
'watch.subscriptions'(
|
||||
data:
|
||||
| {
|
||||
clientAction: 'updated' | 'inserted';
|
||||
subscription: Pick<
|
||||
ISubscription,
|
||||
| 't'
|
||||
| 'ts'
|
||||
| 'ls'
|
||||
| 'lr'
|
||||
| 'name'
|
||||
| 'fname'
|
||||
| 'rid'
|
||||
| 'code'
|
||||
| 'f'
|
||||
| 'u'
|
||||
| 'open'
|
||||
| 'alert'
|
||||
| 'roles'
|
||||
| 'unread'
|
||||
| 'prid'
|
||||
| 'userMentions'
|
||||
| 'groupMentions'
|
||||
| 'archived'
|
||||
| 'audioNotificationValue'
|
||||
| 'desktopNotifications'
|
||||
| 'mobilePushNotifications'
|
||||
| 'emailNotifications'
|
||||
| 'desktopPrefOrigin'
|
||||
| 'mobilePrefOrigin'
|
||||
| 'emailPrefOrigin'
|
||||
| 'unreadAlert'
|
||||
| '_updatedAt'
|
||||
| 'blocked'
|
||||
| 'blocker'
|
||||
| 'autoTranslate'
|
||||
| 'autoTranslateLanguage'
|
||||
| 'disableNotifications'
|
||||
| 'hideUnreadStatus'
|
||||
| 'hideMentionStatus'
|
||||
| 'muteGroupMentions'
|
||||
| 'ignored'
|
||||
| 'E2EKey'
|
||||
| 'E2ESuggestedKey'
|
||||
| 'oldRoomKeys'
|
||||
| 'tunread'
|
||||
| 'tunreadGroup'
|
||||
| 'tunreadUser'
|
||||
|
||||
// Omnichannel fields
|
||||
| 'department'
|
||||
| 'v'
|
||||
| 'onHold'
|
||||
>;
|
||||
}
|
||||
| {
|
||||
clientAction: 'removed';
|
||||
subscription: {
|
||||
_id: string;
|
||||
u?: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
rid?: string;
|
||||
t?: string;
|
||||
};
|
||||
},
|
||||
): void;
|
||||
'watch.inquiries'(data: { clientAction: ClientAction; inquiry: ILivechatInquiryRecord; diff?: undefined | Record<string, any> }): void;
|
||||
'watch.settings'(data: { clientAction: ClientAction; setting: ISetting }): void;
|
||||
'watch.users'(
|
||||
data: {
|
||||
id: string;
|
||||
} & (
|
||||
| {
|
||||
clientAction: 'inserted';
|
||||
data: IUser;
|
||||
}
|
||||
| {
|
||||
clientAction: 'removed';
|
||||
}
|
||||
| {
|
||||
clientAction: 'updated';
|
||||
diff: Record<string, any>;
|
||||
unset: Record<string, number>;
|
||||
}
|
||||
),
|
||||
): void;
|
||||
'watch.loginServiceConfiguration'(data: LoginServiceConfigurationEvent): void;
|
||||
'watch.instanceStatus'(data: {
|
||||
clientAction: ClientAction;
|
||||
data?: undefined | Partial<IInstanceStatus>;
|
||||
diff?: undefined | Record<string, any>;
|
||||
id: string;
|
||||
}): void;
|
||||
'watch.integrationHistory'(data: {
|
||||
clientAction: ClientAction;
|
||||
data: Partial<IIntegrationHistory>;
|
||||
diff?: undefined | Record<string, any>;
|
||||
id: string;
|
||||
}): void;
|
||||
'watch.integrations'(data: { clientAction: ClientAction; data: Partial<IIntegration>; id: string }): void;
|
||||
'watch.emailInbox'(data: { clientAction: ClientAction; data: Partial<IEmailInbox>; id: string }): void;
|
||||
'watch.livechatDepartmentAgents'(data: {
|
||||
clientAction: ClientAction;
|
||||
data: Partial<ILivechatDepartmentAgents>;
|
||||
diff?: undefined | Record<string, any>;
|
||||
id: string;
|
||||
}): void;
|
||||
'omnichannel.room'(
|
||||
roomId: string,
|
||||
data:
|
||||
| { type: 'agentStatus'; status: string }
|
||||
| { type: 'queueData'; data: { [k: string]: unknown } | undefined }
|
||||
| { type: 'agentData'; data: ILivechatAgent | undefined | { hiddenInfo: boolean } }
|
||||
| { type: 'visitorData'; visitor: ILivechatVisitor },
|
||||
): void;
|
||||
|
||||
// Send all events from here
|
||||
'call.callerhangup'(userId: string, data: { roomId: string }): void;
|
||||
'connector.statuschanged'(enabled: boolean): void;
|
||||
'federation.userRoleChanged'(update: Record<string, any>): void;
|
||||
'watch.priorities'(data: { clientAction: ClientAction; id: ILivechatPriority['_id']; diff?: Record<string, string> }): void;
|
||||
'apps.added'(appId: string): void;
|
||||
'apps.removed'(appId: string): void;
|
||||
'apps.updated'(appId: string, originInstanceId?: string): void;
|
||||
'apps.statusUpdate'(appId: string, status: AppStatus): void;
|
||||
'apps.settingUpdated'(appId: string, setting: AppsSetting): void;
|
||||
'command.added'(command: string): void;
|
||||
'command.disabled'(command: string): void;
|
||||
'command.updated'(command: string): void;
|
||||
'command.removed'(command: string): void;
|
||||
'actions.changed'(): void;
|
||||
'media-call.updated'(data: { callId: string; dtmf?: ClientMediaSignalBody<'dtmf'> }): void;
|
||||
};
|
||||
227
packages/core-services/src/index.ts
Normal file
227
packages/core-services/src/index.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { proxify } from './lib/proxify';
|
||||
import type {
|
||||
IAISearchService,
|
||||
AISearchAnswerMessage,
|
||||
AISearchAnswerResult,
|
||||
AISearchFilters,
|
||||
AISearchModelOption,
|
||||
AISearchResult,
|
||||
AISearchStatus,
|
||||
} from './types/IAISearchService';
|
||||
import type { IAbacService } from './types/IAbacService';
|
||||
import type { IAccount, ILoginResult } from './types/IAccount';
|
||||
import type { IAnalyticsService } from './types/IAnalyticsService';
|
||||
import type { IApiService } from './types/IApiService';
|
||||
import type { IAppsEngineService } from './types/IAppsEngineService';
|
||||
import type { IAuthorization, RoomAccessValidator, UserWithRoles } from './types/IAuthorization';
|
||||
import type { IAuthorizationLivechat } from './types/IAuthorizationLivechat';
|
||||
import type { IBannerService } from './types/IBannerService';
|
||||
import type { ICalendarService } from './types/ICalendarService';
|
||||
import type { ICallHistoryService } from './types/ICallHistoryService';
|
||||
import type { IDeviceManagementService } from './types/IDeviceManagementService';
|
||||
import type { IEnterpriseSettings } from './types/IEnterpriseSettings';
|
||||
import type { IFederationMatrixService } from './types/IFederationMatrixService';
|
||||
import type { IFederationService, IFederationServiceEE } from './types/IFederationService';
|
||||
import type { IImportService } from './types/IImportService';
|
||||
import type { ILDAPEEService } from './types/ILDAPEEService';
|
||||
import type { ILDAPService } from './types/ILDAPService';
|
||||
import type { ILicense } from './types/ILicense';
|
||||
import type { IMediaCallService } from './types/IMediaCallService';
|
||||
import type { IMediaService, ResizeResult } from './types/IMediaService';
|
||||
import type { IMessageReadsService } from './types/IMessageReadsService';
|
||||
import type { IMessageService } from './types/IMessageService';
|
||||
import type { IMeteor, AutoUpdateRecord } from './types/IMeteor';
|
||||
import type { INPSService, NPSCreatePayload, NPSVotePayload } from './types/INPSService';
|
||||
import type { IOmnichannelAnalyticsService } from './types/IOmnichannelAnalyticsService';
|
||||
import type { IOmnichannelEEService } from './types/IOmnichannelEEService';
|
||||
import type { IOmnichannelIntegrationService } from './types/IOmnichannelIntegrationService';
|
||||
import type { IOmnichannelService } from './types/IOmnichannelService';
|
||||
import type { IOmnichannelTranscriptService } from './types/IOmnichannelTranscriptService';
|
||||
import type { IPresence } from './types/IPresence';
|
||||
import type { IPushService } from './types/IPushService';
|
||||
import type { IQueueWorkerService, HealthAggResult } from './types/IQueueWorkerService';
|
||||
import type { IRoomService, ICreateRoomParams, ISubscriptionExtraData } from './types/IRoomService';
|
||||
import type { ISAUMonitorService } from './types/ISAUMonitorService';
|
||||
import type { ISettingsService } from './types/ISettingsService';
|
||||
import type { IStatusVisibilityService } from './types/IStatusVisibilityService';
|
||||
import type {
|
||||
ITeamService,
|
||||
ITeamUpdateData,
|
||||
ITeamMemberParams,
|
||||
ITeamMemberInfo,
|
||||
ITeamInfo,
|
||||
ITeamCreateParams,
|
||||
ITeamAutocompleteResult,
|
||||
IListRoomsFilter,
|
||||
} from './types/ITeamService';
|
||||
import type { ITelemetryEvent, TelemetryMap, TelemetryEvents } from './types/ITelemetryEvent';
|
||||
import type {
|
||||
UiKitCoreAppBlockActionPayload,
|
||||
UiKitCoreAppViewClosedPayload,
|
||||
UiKitCoreAppViewSubmitPayload,
|
||||
IUiKitCoreApp,
|
||||
IUiKitCoreAppService,
|
||||
} from './types/IUiKitCoreApp';
|
||||
import type { ISendFileLivechatMessageParams, ISendFileMessageParams, IUploadFileParams, IUploadService } from './types/IUploadService';
|
||||
import type { IUserService } from './types/IUserService';
|
||||
import type { IVideoConfService, VideoConferenceJoinOptions } from './types/IVideoConfService';
|
||||
|
||||
export type { AppStatusReport } from './types/IAppsEngineService';
|
||||
export type { IAbacService, AbacActor } from './types/IAbacService';
|
||||
export { asyncLocalStorage } from './lib/asyncLocalStorage';
|
||||
export { MeteorError, isMeteorError } from './MeteorError';
|
||||
export { api } from './api';
|
||||
export type { EventSignatures } from './events/Events';
|
||||
export { LocalBroker } from './LocalBroker';
|
||||
|
||||
export type { IBroker, IBrokerNode, BaseMetricOptions, CallingOptions, IServiceMetrics } from './types/IBroker';
|
||||
|
||||
export type { IServiceContext, IServiceClass } from './types/ServiceClass';
|
||||
export { ServiceClass, ServiceClassInternal } from './types/ServiceClass';
|
||||
|
||||
export type {
|
||||
IFederationService,
|
||||
IFederationServiceEE,
|
||||
IFederationJoinExternalPublicRoomInput,
|
||||
FederationConfigurationStatus,
|
||||
} from './types/IFederationService';
|
||||
|
||||
export type { IFederationMatrixService } from './types/IFederationMatrixService';
|
||||
|
||||
export type {
|
||||
ConversationData,
|
||||
AgentOverviewDataOptions,
|
||||
ChartDataOptions,
|
||||
AnalyticsOverviewDataOptions,
|
||||
ChartDataResult,
|
||||
AnalyticsOverviewDataResult,
|
||||
} from './types/IOmnichannelAnalyticsService';
|
||||
|
||||
export { getConnection, getTrashCollection } from './lib/mongo';
|
||||
export { ServiceStarter } from './lib/ServiceStarter';
|
||||
|
||||
export type { ICreateRoomOptions } from './types/IRoomService';
|
||||
|
||||
export type {
|
||||
AutoUpdateRecord,
|
||||
IAccount,
|
||||
IAnalyticsService,
|
||||
IApiService,
|
||||
IAppsEngineService,
|
||||
IAuthorization,
|
||||
IAuthorizationLivechat,
|
||||
IBannerService,
|
||||
ICreateRoomParams,
|
||||
IDeviceManagementService,
|
||||
IEnterpriseSettings,
|
||||
ILDAPEEService,
|
||||
ILDAPService,
|
||||
ILicense,
|
||||
IListRoomsFilter,
|
||||
ILoginResult,
|
||||
IMediaService,
|
||||
IMeteor,
|
||||
INPSService,
|
||||
IOmnichannelService,
|
||||
IPresence,
|
||||
IPushService,
|
||||
IMediaCallService,
|
||||
IMessageReadsService,
|
||||
IRoomService,
|
||||
ISAUMonitorService,
|
||||
ISubscriptionExtraData,
|
||||
ITeamAutocompleteResult,
|
||||
ITeamCreateParams,
|
||||
ITeamInfo,
|
||||
ITeamMemberInfo,
|
||||
ITeamMemberParams,
|
||||
ITeamService,
|
||||
ITeamUpdateData,
|
||||
ITelemetryEvent,
|
||||
UiKitCoreAppBlockActionPayload,
|
||||
UiKitCoreAppViewClosedPayload,
|
||||
UiKitCoreAppViewSubmitPayload,
|
||||
IUiKitCoreApp,
|
||||
IUiKitCoreAppService,
|
||||
IVideoConfService,
|
||||
NPSCreatePayload,
|
||||
NPSVotePayload,
|
||||
ResizeResult,
|
||||
RoomAccessValidator,
|
||||
UserWithRoles,
|
||||
TelemetryEvents,
|
||||
TelemetryMap,
|
||||
VideoConferenceJoinOptions,
|
||||
ISendFileLivechatMessageParams,
|
||||
ISendFileMessageParams,
|
||||
IUploadFileParams,
|
||||
IUploadService,
|
||||
ICalendarService,
|
||||
IAISearchService,
|
||||
AISearchAnswerMessage,
|
||||
AISearchAnswerResult,
|
||||
AISearchFilters,
|
||||
AISearchModelOption,
|
||||
AISearchResult,
|
||||
AISearchStatus,
|
||||
ICallHistoryService,
|
||||
IOmnichannelTranscriptService,
|
||||
IQueueWorkerService,
|
||||
HealthAggResult,
|
||||
IMessageService,
|
||||
ISettingsService,
|
||||
IStatusVisibilityService,
|
||||
IOmnichannelEEService,
|
||||
IOmnichannelIntegrationService,
|
||||
IImportService,
|
||||
IOmnichannelAnalyticsService,
|
||||
IUserService,
|
||||
};
|
||||
export { proxify };
|
||||
|
||||
// TODO think in a way to not have to pass the service name to proxify here as well
|
||||
export const Authorization = proxify<IAuthorization>('authorization');
|
||||
export const Apps = proxify<IAppsEngineService>('apps-engine');
|
||||
export const Presence = proxify<IPresence>('presence');
|
||||
export const Account = proxify<IAccount>('accounts');
|
||||
export const License = proxify<ILicense>('license');
|
||||
export const MeteorService = proxify<IMeteor>('meteor');
|
||||
export const Banner = proxify<IBannerService>('banner');
|
||||
export const UiKitCoreApp = proxify<IUiKitCoreAppService>('uikit-core-app');
|
||||
export const NPS = proxify<INPSService>('nps');
|
||||
export const Team = proxify<ITeamService>('team');
|
||||
export const MessageReads = proxify<IMessageReadsService>('message-reads');
|
||||
export const Room = proxify<IRoomService>('room');
|
||||
export const Media = proxify<IMediaService>('media');
|
||||
export const MediaCall = proxify<IMediaCallService>('media-call');
|
||||
export const Analytics = proxify<IAnalyticsService>('analytics');
|
||||
export const LDAP = proxify<ILDAPService>('ldap');
|
||||
export const LDAPEnterprise = proxify<ILDAPEEService>('ldap-enterprise');
|
||||
export const SAUMonitor = proxify<ISAUMonitorService>('sau-monitor');
|
||||
export const DeviceManagement = proxify<IDeviceManagementService>('device-management');
|
||||
export const VideoConf = proxify<IVideoConfService>('video-conference');
|
||||
export const Upload = proxify<IUploadService>('upload');
|
||||
export const Calendar = proxify<ICalendarService>('calendar');
|
||||
export const CallHistory = proxify<ICallHistoryService>('call-history');
|
||||
export const QueueWorker = proxify<IQueueWorkerService>('queue-worker');
|
||||
export const OmnichannelTranscript = proxify<IOmnichannelTranscriptService>('omnichannel-transcript');
|
||||
export const Message = proxify<IMessageService>('message');
|
||||
export const Settings = proxify<ISettingsService>('settings');
|
||||
export const StatusVisibility = proxify<IStatusVisibilityService>('status-visibility');
|
||||
export const OmnichannelIntegration = proxify<IOmnichannelIntegrationService>('omnichannel-integration');
|
||||
export const Federation = proxify<IFederationService>('federation');
|
||||
export const FederationEE = proxify<IFederationServiceEE>('federation-enterprise');
|
||||
export const Omnichannel = proxify<IOmnichannelService>('omnichannel');
|
||||
export const OmnichannelEEService = proxify<IOmnichannelEEService>('omnichannel-ee');
|
||||
export const Import = proxify<IImportService>('import');
|
||||
export const OmnichannelAnalytics = proxify<IOmnichannelAnalyticsService>('omnichannel-analytics');
|
||||
export const User = proxify<IUserService>('user');
|
||||
export const Push = proxify<IPushService>('push');
|
||||
|
||||
// Calls without wait. Means that the service is optional and the result may be an error
|
||||
// of service/method not available
|
||||
export const EnterpriseSettings = proxify<IEnterpriseSettings>('ee-settings');
|
||||
|
||||
export const FederationMatrix = proxify<IFederationMatrixService>('federation-matrix');
|
||||
export const Abac = proxify<IAbacService>('abac');
|
||||
export const AISearch = proxify<IAISearchService>('ai-search');
|
||||
77
packages/core-services/src/lib/Api.ts
Normal file
77
packages/core-services/src/lib/Api.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { EventSignatures } from '../events/Events';
|
||||
import type { IApiService } from '../types/IApiService';
|
||||
import type { CallingOptions, IBroker, IBrokerNode } from '../types/IBroker';
|
||||
import type { IServiceClass } from '../types/ServiceClass';
|
||||
|
||||
export class Api implements IApiService {
|
||||
private services: Set<IServiceClass> = new Set<IServiceClass>();
|
||||
|
||||
private broker?: IBroker;
|
||||
|
||||
// set a broker for the API and registers all services in the broker
|
||||
setBroker(broker: IBroker): void {
|
||||
this.broker = broker;
|
||||
|
||||
this.services.forEach((service) => this.broker?.createService(service));
|
||||
}
|
||||
|
||||
async destroyService(instance: IServiceClass): Promise<void> {
|
||||
if (!this.services.has(instance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.broker) {
|
||||
await this.broker.destroyService(instance);
|
||||
}
|
||||
|
||||
this.services.delete(instance);
|
||||
}
|
||||
|
||||
registerService(instance: IServiceClass, serviceDependencies?: string[]): void {
|
||||
this.services.add(instance);
|
||||
|
||||
instance.setApi(this);
|
||||
|
||||
if (this.broker) {
|
||||
this.broker.createService(instance, serviceDependencies);
|
||||
}
|
||||
}
|
||||
|
||||
async call(method: string, data?: unknown, options?: CallingOptions): Promise<any> {
|
||||
return this.broker?.call(method, data, options);
|
||||
}
|
||||
|
||||
async broadcast<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void> {
|
||||
if (!this.broker) {
|
||||
throw new Error(`No broker set to broadcast: ${event}, ${JSON.stringify(args)}`);
|
||||
}
|
||||
|
||||
return this.broker.broadcast(event, ...args);
|
||||
}
|
||||
|
||||
async broadcastToServices<T extends keyof EventSignatures>(
|
||||
services: string[],
|
||||
event: T,
|
||||
...args: Parameters<EventSignatures[T]>
|
||||
): Promise<void> {
|
||||
return this.broker?.broadcastToServices(services, event, ...args);
|
||||
}
|
||||
|
||||
async broadcastLocal<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void> {
|
||||
return this.broker?.broadcastLocal(event, ...args);
|
||||
}
|
||||
|
||||
nodeList(): Promise<IBrokerNode[]> {
|
||||
if (!this.broker) {
|
||||
throw new Error('No broker set to start.');
|
||||
}
|
||||
return this.broker.nodeList();
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
if (!this.broker) {
|
||||
throw new Error('No broker set to start.');
|
||||
}
|
||||
await this.broker.start();
|
||||
}
|
||||
}
|
||||
9
packages/core-services/src/lib/ContextStore.ts
Normal file
9
packages/core-services/src/lib/ContextStore.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { AsyncLocalStorage } from 'async_hooks';
|
||||
|
||||
interface IContextStore<T> {
|
||||
getStore(): T | undefined;
|
||||
run(store: T, callback: (...args: any) => void, ...args: any): void;
|
||||
}
|
||||
|
||||
// This is the default implementation of the context store but there is a bug on Meteor 2.5 that prevents us from using it
|
||||
export class AsyncContextStore<T> extends AsyncLocalStorage<T> implements IContextStore<T> {}
|
||||
68
packages/core-services/src/lib/ServiceStarter.ts
Normal file
68
packages/core-services/src/lib/ServiceStarter.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// This class is used to manage calls to a service's .start and .stop functions
|
||||
// Specifically for cases where the start function has different conditions that may cause the service to actually start or not,
|
||||
// or when the start process can take a while to complete
|
||||
// Using this class, you ensure that calls to .start and .stop will be chained, so you avoid race conditions
|
||||
// At the same time, it prevents those functions from running more times than necessary if there are several calls to them (for example when loading setting values)
|
||||
export class ServiceStarter {
|
||||
private lock = Promise.resolve();
|
||||
|
||||
private currentCall?: 'start' | 'stop';
|
||||
|
||||
private nextCall?: 'start' | 'stop';
|
||||
|
||||
private starterFn: () => Promise<void>;
|
||||
|
||||
private stopperFn?: () => Promise<void>;
|
||||
|
||||
constructor(starterFn: () => Promise<void>, stopperFn?: () => Promise<void>) {
|
||||
this.starterFn = starterFn;
|
||||
this.stopperFn = stopperFn;
|
||||
}
|
||||
|
||||
private async checkStatus(): Promise<void> {
|
||||
if (this.nextCall === 'start') {
|
||||
return this.doCall('start');
|
||||
}
|
||||
|
||||
if (this.nextCall === 'stop') {
|
||||
return this.doCall('stop');
|
||||
}
|
||||
}
|
||||
|
||||
private async doCall(call: 'start' | 'stop'): Promise<void> {
|
||||
this.nextCall = undefined;
|
||||
this.currentCall = call;
|
||||
try {
|
||||
if (call === 'start') {
|
||||
await this.starterFn();
|
||||
} else if (this.stopperFn) {
|
||||
await this.stopperFn();
|
||||
}
|
||||
} finally {
|
||||
this.currentCall = undefined;
|
||||
await this.checkStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private async call(call: 'start' | 'stop'): Promise<void> {
|
||||
// If something is already chained to run after the current call, it's okay to replace it with the new call
|
||||
this.nextCall = call;
|
||||
if (this.currentCall) {
|
||||
return this.lock;
|
||||
}
|
||||
this.lock = this.checkStatus();
|
||||
return this.lock;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
return this.call('start');
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
return this.call('stop');
|
||||
}
|
||||
|
||||
async wait(): Promise<void> {
|
||||
return this.lock;
|
||||
}
|
||||
}
|
||||
4
packages/core-services/src/lib/asyncLocalStorage.ts
Normal file
4
packages/core-services/src/lib/asyncLocalStorage.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { AsyncContextStore } from './ContextStore';
|
||||
import type { IServiceContext } from '../types/ServiceClass';
|
||||
|
||||
export const asyncLocalStorage = new AsyncContextStore<IServiceContext>();
|
||||
47
packages/core-services/src/lib/mongo.ts
Normal file
47
packages/core-services/src/lib/mongo.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { isTracingEnabled } from '@rocket.chat/tracing';
|
||||
import { MongoClient } from 'mongodb';
|
||||
import type { Db, Collection, MongoClientOptions, Document } from 'mongodb';
|
||||
|
||||
const { MONGO_URL = 'mongodb://localhost:27017/rocketchat' } = process.env;
|
||||
|
||||
const name = /^mongodb:\/\/.*?(?::[0-9]+)?\/([^?]*)/.exec(MONGO_URL)?.[1];
|
||||
|
||||
function connectDb(options?: MongoClientOptions): Promise<MongoClient> {
|
||||
const client = new MongoClient(MONGO_URL, {
|
||||
...options,
|
||||
monitorCommands: isTracingEnabled(),
|
||||
});
|
||||
|
||||
return client.connect().catch((error) => {
|
||||
// exits the process in case of any error
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
let db: Db;
|
||||
|
||||
export const getConnection = ((): ((options?: MongoClientOptions) => Promise<{ db: Db; client: MongoClient }>) => {
|
||||
let client: MongoClient;
|
||||
|
||||
return async (options): Promise<{ db: Db; client: MongoClient }> => {
|
||||
if (db) {
|
||||
return { db, client };
|
||||
}
|
||||
if (client == null) {
|
||||
client = await connectDb(options);
|
||||
db = client.db(name);
|
||||
}
|
||||
|
||||
// if getConnection was called multiple times before it was connected, wait for the connection
|
||||
return { client, db: client.db(name) };
|
||||
};
|
||||
})();
|
||||
|
||||
export async function getTrashCollection<T extends Document>(): Promise<Collection<T>> {
|
||||
if (!db) {
|
||||
const { db: clientDb } = await getConnection();
|
||||
db = clientDb;
|
||||
}
|
||||
return db.collection<T>('rocketchat__trash');
|
||||
}
|
||||
22
packages/core-services/src/lib/proxify.ts
Normal file
22
packages/core-services/src/lib/proxify.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { api } from '../api';
|
||||
|
||||
type Promisify<T> = {
|
||||
[K in keyof T as T[K] extends (...params: any[]) => unknown ? K : never]: T[K] extends (...params: any[]) => Promise<any>
|
||||
? T[K]
|
||||
: T[K] extends (...params: infer P) => infer R
|
||||
? (...params: P) => Promise<R>
|
||||
: never;
|
||||
};
|
||||
|
||||
function handler<T extends object>(namespace: string): ProxyHandler<T> {
|
||||
return {
|
||||
get:
|
||||
(_target: T, prop: string): any =>
|
||||
(...params: any): Promise<any> =>
|
||||
api.call(`${namespace}.${prop}`, params),
|
||||
};
|
||||
}
|
||||
|
||||
export function proxify<T>(namespace: string): Promisify<T> {
|
||||
return new Proxy({}, handler(namespace)) as unknown as Promisify<T>;
|
||||
}
|
||||
62
packages/core-services/src/types/IAISearchService.ts
Normal file
62
packages/core-services/src/types/IAISearchService.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export type AISearchFilters = {
|
||||
rid?: string;
|
||||
rids?: string[];
|
||||
roomNames?: string[];
|
||||
fromUsername?: string;
|
||||
fromUsernames?: string[];
|
||||
startDate?: string | Date;
|
||||
endDate?: string | Date;
|
||||
};
|
||||
|
||||
export type AISearchStatus = {
|
||||
hasIntelligentSearchLicense: boolean;
|
||||
intelligentSearchEnabled: boolean;
|
||||
intelligentSearchConfigured: boolean;
|
||||
answerGenerationConfigured: boolean;
|
||||
};
|
||||
|
||||
export type AISearchAnswerMessage = {
|
||||
text: string;
|
||||
username?: string;
|
||||
roomName?: string;
|
||||
ts?: string;
|
||||
score?: number;
|
||||
};
|
||||
|
||||
export type AISearchAnswerResult = {
|
||||
answer: string;
|
||||
provider: {
|
||||
name: string;
|
||||
model: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type AISearchModelOption = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type AISearchResult = {
|
||||
_id: string;
|
||||
rid?: string;
|
||||
msgId?: string;
|
||||
text: string;
|
||||
score?: number;
|
||||
ts?: string;
|
||||
u?: { username: string; name?: string };
|
||||
room?: Pick<IRoom, '_id' | 't' | 'name' | 'fname'>;
|
||||
};
|
||||
|
||||
export interface IAISearchService extends IServiceClass {
|
||||
status(): Promise<AISearchStatus>;
|
||||
|
||||
search(params: { query: string; userId: string; filters?: AISearchFilters; limit?: number }): Promise<AISearchResult[]>;
|
||||
|
||||
answer(params: { query: string; messages: AISearchAnswerMessage[] }): Promise<AISearchAnswerResult>;
|
||||
|
||||
models(): Promise<AISearchModelOption[]>;
|
||||
}
|
||||
56
packages/core-services/src/types/IAbacService.ts
Normal file
56
packages/core-services/src/types/IAbacService.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type {
|
||||
IAbacAttributeDefinition,
|
||||
IAbacAttribute,
|
||||
IRoom,
|
||||
IRoomAbacRedaction,
|
||||
IUser,
|
||||
AbacAccessOperation,
|
||||
AbacObjectType,
|
||||
ILDAPEntry,
|
||||
AbacUserIdentifiers,
|
||||
} from '@rocket.chat/core-typings';
|
||||
|
||||
export type AbacActor = Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
|
||||
export interface IAbacService {
|
||||
addAbacAttribute(attribute: IAbacAttributeDefinition, actor: AbacActor | undefined): Promise<void>;
|
||||
listAbacAttributes(
|
||||
filters?: {
|
||||
key?: string;
|
||||
values?: string;
|
||||
offset?: number;
|
||||
count?: number;
|
||||
},
|
||||
actor?: AbacActor,
|
||||
): Promise<{ attributes: Pick<IAbacAttribute, '_id' | 'key' | 'values'>[]; offset: number; count: number; total: number }>;
|
||||
listAbacRooms(
|
||||
filters?: {
|
||||
offset?: number;
|
||||
count?: number;
|
||||
filter?: string;
|
||||
filterType?: 'all' | 'roomName' | 'attribute' | 'value';
|
||||
},
|
||||
actor?: AbacActor,
|
||||
): Promise<{ rooms: Array<IRoom & IRoomAbacRedaction>; offset: number; count: number; total: number }>;
|
||||
scopeRoomsForAdmin<T extends Pick<IRoom, '_id' | 'abacAttributes'>>(rooms: T[], actor: AbacActor): Promise<Array<T & IRoomAbacRedaction>>;
|
||||
updateAbacAttributeById(_id: string, update: { key?: string; values?: string[] }, actor: AbacActor | undefined): Promise<void>;
|
||||
deleteAbacAttributeById(_id: string, actor: AbacActor | undefined): Promise<void>;
|
||||
getAbacAttributeById(_id: string, actor: AbacActor | undefined): Promise<{ key: string; values: string[] }>;
|
||||
isAbacAttributeInUseByKey(key: string): Promise<boolean>;
|
||||
setRoomAbacAttributes(rid: string, attributes: Record<string, string[]>, actor: AbacActor | undefined): Promise<void>;
|
||||
removeRoomAbacAttribute(rid: string, key: string, actor: AbacActor | undefined): Promise<void>;
|
||||
addRoomAbacAttributeByKey(rid: string, key: string, values: string[], actor: AbacActor | undefined): Promise<void>;
|
||||
replaceRoomAbacAttributeByKey(rid: string, key: string, values: string[], actor: AbacActor | undefined): Promise<void>;
|
||||
checkUsernamesMatchAttributes(usernames: string[], attributes: IAbacAttributeDefinition[], object: IRoom): Promise<void>;
|
||||
canAccessObject(
|
||||
room: Pick<IRoom, '_id' | 't' | 'teamId' | 'prid' | 'abacAttributes'>,
|
||||
user: Pick<IUser, '_id'>,
|
||||
action: AbacAccessOperation,
|
||||
objectType: AbacObjectType,
|
||||
): Promise<boolean>;
|
||||
addSubjectAttributes(user: IUser, ldapUser: ILDAPEntry, map: Record<string, string>, actor: AbacActor | undefined): Promise<void>;
|
||||
evaluateRoomMembership(): Promise<void>;
|
||||
reevaluateUsers(identifiers: AbacUserIdentifiers): Promise<void>;
|
||||
getPDPHealth(): Promise<void>;
|
||||
isExternalAttributeStore(): Promise<boolean>;
|
||||
}
|
||||
14
packages/core-services/src/types/IAccount.ts
Normal file
14
packages/core-services/src/types/IAccount.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface ILoginResult {
|
||||
uid: string;
|
||||
token: string;
|
||||
hashedToken: string;
|
||||
tokenExpires?: Date;
|
||||
type: 'resume';
|
||||
}
|
||||
|
||||
export interface IAccount extends IServiceClass {
|
||||
login({ resume }: { resume: string }): Promise<false | ILoginResult>;
|
||||
logout({ userId, token }: { userId: string; token: string }): Promise<void>;
|
||||
}
|
||||
7
packages/core-services/src/types/IAnalyticsService.ts
Normal file
7
packages/core-services/src/types/IAnalyticsService.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface IAnalyticsService extends IServiceClass {
|
||||
saveSeatRequest(): Promise<void>;
|
||||
getSeatRequestCount(): Promise<number>;
|
||||
resetSeatRequestCount(): Promise<void>;
|
||||
}
|
||||
25
packages/core-services/src/types/IApiService.ts
Normal file
25
packages/core-services/src/types/IApiService.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { CallingOptions, IBroker, IBrokerNode } from './IBroker';
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
import type { EventSignatures } from '../events/Events';
|
||||
|
||||
export interface IApiService {
|
||||
setBroker(broker: IBroker): void;
|
||||
|
||||
destroyService(instance: IServiceClass): Promise<void>;
|
||||
|
||||
registerService(instance: IServiceClass): void;
|
||||
|
||||
call(method: string, data?: unknown, options?: CallingOptions): Promise<any>;
|
||||
|
||||
broadcast<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void>;
|
||||
|
||||
broadcastToServices<T extends keyof EventSignatures>(
|
||||
services: string[],
|
||||
event: T,
|
||||
...args: Parameters<EventSignatures[T]>
|
||||
): Promise<void>;
|
||||
|
||||
broadcastLocal<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void>;
|
||||
|
||||
nodeList(): Promise<IBrokerNode[]>;
|
||||
}
|
||||
16
packages/core-services/src/types/IAppsEngineService.ts
Normal file
16
packages/core-services/src/types/IAppsEngineService.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { IGetAppsFilter } from '@rocket.chat/apps/dist/server/IGetAppsFilter';
|
||||
import type { IAppStorageItem } from '@rocket.chat/apps/dist/server/storage';
|
||||
import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
|
||||
import type { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
|
||||
|
||||
export type AppStatusReport = {
|
||||
[appId: string]: { instanceId: string; isLocal: boolean; status: AppStatus }[];
|
||||
};
|
||||
|
||||
export interface IAppsEngineService {
|
||||
isInitialized(): boolean;
|
||||
getApps(query: IGetAppsFilter): Promise<IAppInfo[] | undefined>;
|
||||
getAppStorageItemById(appId: string): Promise<IAppStorageItem | undefined>;
|
||||
getAppsStatusLocal(): Promise<{ appId: string; status: AppStatus }[]>;
|
||||
getAppsStatusInNodes(): Promise<AppStatusReport>;
|
||||
}
|
||||
22
packages/core-services/src/types/IAuthorization.ts
Normal file
22
packages/core-services/src/types/IAuthorization.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { IRoom, IUser, IRole } from '@rocket.chat/core-typings';
|
||||
|
||||
/** Minimal user shape a permission check needs. `getRoles` only reads `_id` and `roles`. */
|
||||
export type UserWithRoles = Pick<IUser, '_id' | 'roles'>;
|
||||
|
||||
export type RoomAccessValidator = (
|
||||
room?: Pick<IRoom, '_id' | 't' | 'teamId' | 'prid' | 'abacAttributes'>,
|
||||
user?: IUser | Pick<IUser, '_id'>,
|
||||
extraData?: Record<string, any>,
|
||||
) => Promise<boolean>;
|
||||
|
||||
export interface IAuthorization {
|
||||
hasAllPermission(user: string | UserWithRoles, permissions: string[], scope?: string): Promise<boolean>;
|
||||
hasPermission(user: string | UserWithRoles, permissionId: string, scope?: string): Promise<boolean>;
|
||||
hasAtLeastOnePermission(user: string | UserWithRoles, permissions: string[], scope?: string): Promise<boolean>;
|
||||
canAccessRoom: RoomAccessValidator;
|
||||
canReadRoom: RoomAccessValidator;
|
||||
canAccessRoomId(rid: IRoom['_id'], uid?: IUser['_id']): Promise<boolean>;
|
||||
canAccessRoomIds(rids: IRoom['_id'][], user: UserWithRoles): Promise<boolean>;
|
||||
getUsersFromPublicRoles(): Promise<Pick<Required<IUser>, '_id' | 'username' | 'roles'>[]>;
|
||||
hasAnyRole(userId: IUser['_id'], roleIds: IRole['_id'][], scope?: IRoom['_id']): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface IAuthorizationLivechat {
|
||||
canAccessRoom: (room: IOmnichannelRoom, user?: Pick<IUser, '_id'>, extraData?: Record<string, any>) => Promise<boolean>;
|
||||
}
|
||||
11
packages/core-services/src/types/IBannerService.ts
Normal file
11
packages/core-services/src/types/IBannerService.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { BannerPlatform, IBanner, Optional } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface IBannerService {
|
||||
getBannersForUser(userId: string, platform: BannerPlatform, bannerId?: string): Promise<IBanner[]>;
|
||||
create(banner: Optional<IBanner, '_id' | '_updatedAt'>): Promise<IBanner>;
|
||||
dismiss(userId: string, bannerId: string): Promise<boolean>;
|
||||
discardDismissal(bannerId: string): Promise<boolean>;
|
||||
getById(bannerId: string): Promise<null | IBanner>;
|
||||
disable(bannerId: string): Promise<boolean>;
|
||||
enable(bannerId: string, doc?: Partial<Omit<IBanner, '_id'>>): Promise<boolean>;
|
||||
}
|
||||
77
packages/core-services/src/types/IBroker.ts
Normal file
77
packages/core-services/src/types/IBroker.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
import type { EventSignatures } from '../events/Events';
|
||||
|
||||
export interface IBrokerNode {
|
||||
id: string;
|
||||
instanceID?: string;
|
||||
available: boolean;
|
||||
local?: boolean;
|
||||
// lastHeartbeatTime: 16,
|
||||
// config: {},
|
||||
// client: { type: 'nodejs', version: '0.14.10', langVersion: 'v12.18.3' },
|
||||
// metadata: {},
|
||||
// ipList: [ '192.168.0.100', '192.168.1.25' ],
|
||||
// port: 59989,
|
||||
// hostname: 'service.local-1',
|
||||
// udpAddress: null,
|
||||
// cpu: 25,
|
||||
// cpuSeq: 1,
|
||||
// seq: 3,
|
||||
// offlineSince: null
|
||||
}
|
||||
|
||||
export type BaseMetricOptions = {
|
||||
type: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
labelNames?: Array<string>;
|
||||
unit?: string;
|
||||
aggregator?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type CallingOptions = {
|
||||
nodeID?: string;
|
||||
// timeout?: number;
|
||||
// retries?: number;
|
||||
// fallbackResponse?: FallbackResponse | FallbackResponse[] | FallbackResponseHandler;
|
||||
// meta?: GenericObject;
|
||||
// parentSpan?: ContextParentSpan;
|
||||
// parentCtx?: Context;
|
||||
// requestID?: string;
|
||||
// tracking?: boolean;
|
||||
// paramsCloning?: boolean;
|
||||
// caller?: string;
|
||||
};
|
||||
|
||||
export interface IServiceMetrics {
|
||||
register(opts: BaseMetricOptions): void;
|
||||
|
||||
hasMetric(name: string): boolean;
|
||||
|
||||
increment(name: string, labels?: Record<string, any>, value?: number, timestamp?: number): void;
|
||||
decrement(name: string, labels?: Record<string, any>, value?: number, timestamp?: number): void;
|
||||
set(name: string, value: any | null, labels?: Record<string, any>, timestamp?: number): void;
|
||||
observe(name: string, value: number, labels?: Record<string, any>, timestamp?: number): void;
|
||||
|
||||
reset(name: string, labels?: Record<string, any>, timestamp?: number): void;
|
||||
resetAll(name: string, timestamp?: number): void;
|
||||
|
||||
timer(name: string, labels?: Record<string, any>, timestamp?: number): () => number;
|
||||
}
|
||||
|
||||
export interface IBroker {
|
||||
metrics?: IServiceMetrics;
|
||||
destroyService(service: IServiceClass): Promise<void>;
|
||||
createService(service: IServiceClass, serviceDependencies?: string[]): void;
|
||||
call(method: string, data: any, options?: CallingOptions): Promise<any>;
|
||||
broadcastToServices<T extends keyof EventSignatures>(
|
||||
services: string[],
|
||||
event: T,
|
||||
...args: Parameters<EventSignatures[T]>
|
||||
): Promise<void>;
|
||||
broadcast<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void>;
|
||||
broadcastLocal<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): Promise<void>;
|
||||
nodeList(): Promise<IBrokerNode[]>;
|
||||
start(): Promise<void>;
|
||||
}
|
||||
14
packages/core-services/src/types/ICalendarService.ts
Normal file
14
packages/core-services/src/types/ICalendarService.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { ICalendarEvent, IUser } from '@rocket.chat/core-typings';
|
||||
import type { InsertionModel } from '@rocket.chat/model-typings';
|
||||
import type { UpdateResult, DeleteResult } from 'mongodb';
|
||||
|
||||
export interface ICalendarService {
|
||||
create(data: Omit<InsertionModel<ICalendarEvent>, 'reminderTime' | 'notificationSent'>): Promise<ICalendarEvent['_id']>;
|
||||
import(data: Omit<InsertionModel<ICalendarEvent>, 'notificationSent'>): Promise<ICalendarEvent['_id']>;
|
||||
get(eventId: ICalendarEvent['_id']): Promise<ICalendarEvent | null>;
|
||||
list(uid: IUser['_id'], date: Date): Promise<ICalendarEvent[]>;
|
||||
update(eventId: ICalendarEvent['_id'], data: Partial<ICalendarEvent>): Promise<UpdateResult | null>;
|
||||
delete(eventId: ICalendarEvent['_id']): Promise<DeleteResult>;
|
||||
setupNextNotification(): Promise<void>;
|
||||
setupNextStatusChange(): Promise<void>;
|
||||
}
|
||||
17
packages/core-services/src/types/ICallHistoryService.ts
Normal file
17
packages/core-services/src/types/ICallHistoryService.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { CallHistoryItem, IUser } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface ICallHistoryService {
|
||||
search(
|
||||
uid: IUser['_id'],
|
||||
filters: {
|
||||
searchTerm?: string;
|
||||
direction?: CallHistoryItem['direction'];
|
||||
inStates?: CallHistoryItem['state'][];
|
||||
},
|
||||
pagination: {
|
||||
count: number;
|
||||
offset: number;
|
||||
sort?: Record<string, 1 | -1>;
|
||||
},
|
||||
): Promise<{ items: CallHistoryItem[]; total: number }>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export type IDeviceManagementService = IServiceClass;
|
||||
7
packages/core-services/src/types/IEnterpriseSettings.ts
Normal file
7
packages/core-services/src/types/IEnterpriseSettings.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface IEnterpriseSettings extends IServiceClass {
|
||||
changeSettingValue(record: ISetting): undefined | ISetting['value'];
|
||||
}
|
||||
39
packages/core-services/src/types/IFederationMatrixService.ts
Normal file
39
packages/core-services/src/types/IFederationMatrixService.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { IMessage, IRoomFederated, IRoomNativeFederated, ISubscription, IUser } from '@rocket.chat/core-typings';
|
||||
import type { EventID, EventStore, PduForType } from '@rocket.chat/federation-sdk';
|
||||
|
||||
export interface IFederationMatrixService {
|
||||
createRoom(room: IRoomFederated, owner: IUser): Promise<{ room_id: string; event_id: string }>;
|
||||
ensureFederatedUsersExistLocally(members: string[]): Promise<void>;
|
||||
createDirectMessageRoom(room: IRoomFederated, members: IUser[], creatorId: IUser['_id']): Promise<void>;
|
||||
sendMessage(message: IMessage, room: IRoomFederated, user: IUser): Promise<void>;
|
||||
deleteMessage(matrixRoomId: string, message: IMessage): Promise<void>;
|
||||
sendReaction(messageId: string, reaction: string, user: IUser): Promise<void>;
|
||||
removeReaction(messageId: string, reaction: string, user: IUser, oldMessage: IMessage): Promise<void>;
|
||||
getEventById(eventId: string): Promise<EventStore | null>;
|
||||
leaveRoom(rid: IRoomFederated['_id'], user: IUser, kicker?: IUser): Promise<void>;
|
||||
kickUser(room: IRoomNativeFederated, removedUser: IUser, userWhoRemoved: IUser): Promise<void>;
|
||||
banUser(room: IRoomNativeFederated, bannedUser: IUser, userWhoBanned: IUser): Promise<void>;
|
||||
unbanUser(room: IRoomNativeFederated, unbannedUser: IUser, userWhoUnbanned: IUser): Promise<void>;
|
||||
updateMessage(room: IRoomNativeFederated, message: IMessage): Promise<void>;
|
||||
updateRoomName(rid: string, displayName: string, user: IUser): Promise<void>;
|
||||
updateRoomTopic(
|
||||
room: IRoomNativeFederated,
|
||||
topic: string,
|
||||
user: Pick<IUser, '_id' | 'username' | 'federation' | 'federated'>,
|
||||
): Promise<void>;
|
||||
addUserRoleRoomScoped(
|
||||
room: IRoomNativeFederated,
|
||||
senderId: string,
|
||||
userId: string,
|
||||
role: 'moderator' | 'owner' | 'leader' | 'user',
|
||||
): Promise<void>;
|
||||
inviteUsersToRoom(room: IRoomFederated, usersUserName: string[], inviter: IUser): Promise<void>;
|
||||
notifyUserTyping(rid: string, user: string, isTyping: boolean): Promise<void>;
|
||||
verifyMatrixIds(matrixIds: string[]): Promise<{ [key: string]: string }>;
|
||||
handleInvite(subscriptionId: ISubscription['_id'], userId: IUser['_id'], action: 'accept' | 'reject'): Promise<void>;
|
||||
canUserAccessFederation(user: IUser): Promise<boolean>;
|
||||
notifyRoomRead(params: { room: IRoomNativeFederated; userId: string; threadId?: string }): Promise<void>;
|
||||
updateUserName(user: IUser): Promise<void>;
|
||||
joinAppServiceRoom(roomAlias: string, user: IUser): Promise<boolean>;
|
||||
saveFederationMessage(event: { event: PduForType<'m.room.message'>; event_id: EventID }): Promise<void>;
|
||||
}
|
||||
70
packages/core-services/src/types/IFederationService.ts
Normal file
70
packages/core-services/src/types/IFederationService.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
import type { FederationPaginatedResult, IFederationPublicRooms } from '@rocket.chat/rest-typings';
|
||||
|
||||
export type FederationConfigurationStatus = {
|
||||
appservice: {
|
||||
error?: string;
|
||||
ok: boolean;
|
||||
roundTrip: {
|
||||
durationMs: number;
|
||||
};
|
||||
};
|
||||
|
||||
externalReachability: {
|
||||
error?: string;
|
||||
ok: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
interface IFederationBaseService {
|
||||
verifyMatrixIds(matrixIds: string[]): Promise<Map<string, string>>;
|
||||
|
||||
configurationStatus(): Promise<FederationConfigurationStatus>;
|
||||
|
||||
markConfigurationValid(): Promise<void>;
|
||||
|
||||
markConfigurationInvalid(): Promise<void>;
|
||||
|
||||
beforeCreateRoom(room: Partial<IRoom>): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IFederationService extends IFederationBaseService {
|
||||
createDirectMessageRoomAndInviteUser(internalInviterId: string, internalRoomId: string, externalInviteeId: string): Promise<void>;
|
||||
deactivateRemoteUser(userId: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IFederationJoinExternalPublicRoomInput {
|
||||
internalUserId: string;
|
||||
externalRoomId: string;
|
||||
roomName?: string;
|
||||
pageToken?: string;
|
||||
}
|
||||
|
||||
export interface IFederationServiceEE extends IFederationBaseService {
|
||||
createDirectMessageRoom(internalUserId: string, invitees: string[]): Promise<void>;
|
||||
|
||||
searchPublicRooms(
|
||||
serverName?: string,
|
||||
roomName?: string,
|
||||
pageToken?: string,
|
||||
count?: number,
|
||||
): Promise<
|
||||
FederationPaginatedResult<{
|
||||
rooms: IFederationPublicRooms[];
|
||||
}>
|
||||
>;
|
||||
|
||||
getSearchedServerNamesByInternalUserId(internalUserId: string): Promise<{ name: string; default: boolean; local: boolean }[]>;
|
||||
|
||||
addSearchedServerNameByInternalUserId(internalUserId: string, serverName: string): Promise<void>;
|
||||
|
||||
removeSearchedServerNameByInternalUserId(internalUserId: string, serverName: string): Promise<void>;
|
||||
|
||||
scheduleJoinExternalPublicRoom(internalUserId: string, externalRoomId: string, roomName?: string, pageToken?: string): Promise<void>;
|
||||
|
||||
joinExternalPublicRoom(input: IFederationJoinExternalPublicRoomInput): Promise<void>;
|
||||
|
||||
verifyMatrixIds(matrixIds: string[]): Promise<Map<string, string>>;
|
||||
|
||||
deactivateRemoteUser(userId: string): Promise<void>;
|
||||
}
|
||||
9
packages/core-services/src/types/IImportService.ts
Normal file
9
packages/core-services/src/types/IImportService.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { IImport, IImportUser, ImportStatus } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface IImportService {
|
||||
clear(): Promise<void>;
|
||||
newOperation(userId: string, name: string, key: string): Promise<IImport>;
|
||||
status(): Promise<ImportStatus>;
|
||||
addUsers(users: Omit<IImportUser, '_id' | 'services' | 'customFields'>[]): Promise<void>;
|
||||
run(userId: string): Promise<void>;
|
||||
}
|
||||
12
packages/core-services/src/types/ILDAPEEService.ts
Normal file
12
packages/core-services/src/types/ILDAPEEService.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { IUser } from '@rocket.chat/core-typings';
|
||||
import type { FindCursor } from 'mongodb';
|
||||
|
||||
export interface ILDAPEEService {
|
||||
sync(): Promise<void>;
|
||||
syncAvatars(): Promise<void>;
|
||||
syncAvatarAndAbacAttributes(): Promise<void>;
|
||||
syncLogout(): Promise<void>;
|
||||
syncAbacAttributes(): Promise<void>;
|
||||
syncUsersAbacAttributes(users: FindCursor<IUser>): Promise<void>;
|
||||
syncUsersAbacAttributesByIds(userIds: string[]): Promise<void>;
|
||||
}
|
||||
8
packages/core-services/src/types/ILDAPService.ts
Normal file
8
packages/core-services/src/types/ILDAPService.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { LDAPLoginResult } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface ILDAPService {
|
||||
loginRequest(username: string, password: string): Promise<LDAPLoginResult>;
|
||||
loginAuthenticatedUserRequest(username: string): Promise<LDAPLoginResult>;
|
||||
testConnection(): Promise<void>;
|
||||
testSearch(username: string): Promise<void>;
|
||||
}
|
||||
11
packages/core-services/src/types/ILicense.ts
Normal file
11
packages/core-services/src/types/ILicense.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface ILicense extends IServiceClass {
|
||||
hasModule(feature: string): boolean;
|
||||
|
||||
hasValidLicense(): boolean;
|
||||
|
||||
getModules(): string[];
|
||||
|
||||
getGuestPermissions(): string[];
|
||||
}
|
||||
10
packages/core-services/src/types/IMediaCallService.ts
Normal file
10
packages/core-services/src/types/IMediaCallService.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { IMediaCall, IUser } from '@rocket.chat/core-typings';
|
||||
import type { ClientMediaSignal, ServerMediaCallSignal, ClientMediaSignalAnswer } from '@rocket.chat/media-signaling';
|
||||
|
||||
export interface IMediaCallService {
|
||||
answerCall(uid: IUser['_id'], params: Omit<ClientMediaSignalAnswer, 'type'>): Promise<IMediaCall>;
|
||||
processSignal(fromUid: IUser['_id'], signal: ClientMediaSignal): Promise<void>;
|
||||
processSerializedSignal(fromUid: IUser['_id'], signal: string): Promise<void>;
|
||||
hangupExpiredCalls(): Promise<void>;
|
||||
getUserStateSignals(uid: IUser['_id'], contractId: string): Promise<ServerMediaCallSignal[]>;
|
||||
}
|
||||
33
packages/core-services/src/types/IMediaService.ts
Normal file
33
packages/core-services/src/types/IMediaService.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Readable, Stream } from 'node:stream';
|
||||
|
||||
import type { FitEnum } from 'sharp';
|
||||
|
||||
export type ResizeResult = {
|
||||
data: Buffer;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export interface IMediaService {
|
||||
resizeFromBuffer(
|
||||
input: Buffer,
|
||||
width: number,
|
||||
height: number,
|
||||
keepType: boolean,
|
||||
blur: boolean,
|
||||
enlarge: boolean,
|
||||
fit?: keyof FitEnum | undefined,
|
||||
): Promise<ResizeResult>;
|
||||
resizeFromStream(
|
||||
input: Readable,
|
||||
width: number,
|
||||
height: number,
|
||||
keepType: boolean,
|
||||
blur: boolean,
|
||||
enlarge: boolean,
|
||||
fit?: keyof FitEnum | undefined,
|
||||
): Promise<ResizeResult>;
|
||||
isImage(buff: Buffer): Promise<boolean>;
|
||||
stripExifFromImageStream(stream: Stream): Readable;
|
||||
stripExifFromBuffer(buffer: Buffer): Promise<Buffer>;
|
||||
}
|
||||
5
packages/core-services/src/types/IMessageReadsService.ts
Normal file
5
packages/core-services/src/types/IMessageReadsService.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface IMessageReadsService extends IServiceClass {
|
||||
readThread(userId: string, threadId: string): Promise<void>;
|
||||
}
|
||||
57
packages/core-services/src/types/IMessageService.ts
Normal file
57
packages/core-services/src/types/IMessageService.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { IMessage, MessageTypesValues, IUser, IRoom, AtLeast, MessageUrl } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface IMessageService {
|
||||
sendMessage({ fromId, rid, msg }: { fromId: string; rid: string; msg: string }): Promise<IMessage>;
|
||||
saveSystemMessage<T = IMessage>(
|
||||
type: MessageTypesValues,
|
||||
rid: string,
|
||||
message: string,
|
||||
user: Pick<IUser, '_id' | 'username' | 'name'>,
|
||||
extraData?: Partial<T>,
|
||||
): Promise<IMessage>;
|
||||
saveMessageFromFederation({
|
||||
fromId,
|
||||
rid,
|
||||
federation_event_id,
|
||||
msg,
|
||||
e2e_content,
|
||||
file,
|
||||
files,
|
||||
attachments,
|
||||
thread,
|
||||
ts,
|
||||
}: {
|
||||
fromId: string;
|
||||
rid: string;
|
||||
federation_event_id: string;
|
||||
msg?: string;
|
||||
e2e_content?: {
|
||||
algorithm: string;
|
||||
ciphertext: string;
|
||||
};
|
||||
file?: IMessage['file'];
|
||||
files?: IMessage['files'];
|
||||
attachments?: IMessage['attachments'];
|
||||
thread?: { tmid: string; tshow: boolean };
|
||||
ts: Date;
|
||||
}): Promise<IMessage>;
|
||||
saveSystemMessageAndNotifyUser<T = IMessage>(
|
||||
type: MessageTypesValues,
|
||||
rid: string,
|
||||
message: string,
|
||||
user: Pick<IUser, '_id' | 'username' | 'name'>,
|
||||
extraData?: Partial<T>,
|
||||
): Promise<IMessage>;
|
||||
beforeSave(param: { message: IMessage; room: IRoom; user: IUser; previewUrls?: string[]; parseUrls?: boolean }): Promise<IMessage>;
|
||||
sendMessageWithValidation(user: IUser, message: Partial<IMessage>, room: Partial<IRoom>, upsert?: boolean): Promise<IMessage>;
|
||||
deleteMessage(user: IUser, message: IMessage): Promise<void>;
|
||||
updateMessage(message: IMessage, user: IUser, originalMsg?: IMessage): Promise<void>;
|
||||
reactToMessage(userId: string, reaction: string, messageId: IMessage['_id'], shouldReact?: boolean): Promise<void>;
|
||||
beforeReacted(message: IMessage, room: AtLeast<IRoom, 'federated'>): Promise<void>;
|
||||
beforeDelete(message: IMessage, room: IRoom): Promise<void>;
|
||||
afterSave(param: { message: IMessage }): Promise<void>;
|
||||
parseOEmbedUrl(url: string): Promise<{
|
||||
urlPreview: MessageUrl;
|
||||
foundMeta: boolean;
|
||||
}>;
|
||||
}
|
||||
31
packages/core-services/src/types/IMeteor.ts
Normal file
31
packages/core-services/src/types/IMeteor.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { LoginServiceConfiguration } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export type AutoUpdateRecord = {
|
||||
_id: string;
|
||||
version: string;
|
||||
versionRefreshable?: string;
|
||||
versionNonRefreshable?: string;
|
||||
versionHmr: number;
|
||||
assets?: [
|
||||
{
|
||||
url: string;
|
||||
},
|
||||
];
|
||||
};
|
||||
export interface IMeteor extends IServiceClass {
|
||||
getAutoUpdateClientVersions(): Promise<Record<string, AutoUpdateRecord>>;
|
||||
getLoginServiceConfiguration(): Promise<LoginServiceConfiguration[]>;
|
||||
callMethodWithToken(
|
||||
userId: string | undefined,
|
||||
token: string | undefined,
|
||||
method: string,
|
||||
args: any[],
|
||||
): Promise<{
|
||||
result: unknown;
|
||||
}>;
|
||||
notifyGuestStatusChanged(token: string, status: string): Promise<void>;
|
||||
getURL(path: string, params?: Record<string, any>, cloudDeepLinkUrl?: string): Promise<string>;
|
||||
getMessageURLToReplyTo(roomType: string, roomId: string, messageIdToReplyTo: string): Promise<string>;
|
||||
}
|
||||
22
packages/core-services/src/types/INPSService.ts
Normal file
22
packages/core-services/src/types/INPSService.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { IUser, IRole } from '@rocket.chat/core-typings';
|
||||
|
||||
export type NPSVotePayload = {
|
||||
userId: string | undefined;
|
||||
npsId: string;
|
||||
roles?: IRole['_id'][];
|
||||
score: number;
|
||||
comment: string;
|
||||
};
|
||||
|
||||
export type NPSCreatePayload = {
|
||||
npsId: string;
|
||||
startAt: Date;
|
||||
expireAt: Date;
|
||||
createdBy: Pick<IUser, '_id' | 'username'>;
|
||||
};
|
||||
export interface INPSService {
|
||||
create(nps: NPSCreatePayload): Promise<boolean>;
|
||||
vote(vote: NPSVotePayload): Promise<void>;
|
||||
sendResults(): Promise<void>;
|
||||
closeOpenSurveys(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export type ConversationData = {
|
||||
head: { name: string }[];
|
||||
data: { name: string; value: string }[];
|
||||
};
|
||||
|
||||
export type AgentOverviewDataOptions = {
|
||||
departmentId?: string;
|
||||
utcOffset?: number;
|
||||
daterange?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
chartOptions: {
|
||||
name: string;
|
||||
};
|
||||
executedBy: string;
|
||||
};
|
||||
|
||||
export type ChartDataOptions = {
|
||||
departmentId?: string;
|
||||
utcOffset?: number;
|
||||
daterange?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
chartOptions: {
|
||||
name: string;
|
||||
};
|
||||
executedBy: string;
|
||||
};
|
||||
|
||||
export type AnalyticsOverviewDataOptions = {
|
||||
departmentId?: string;
|
||||
utcOffset?: number;
|
||||
language: string;
|
||||
daterange?: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
analyticsOptions: {
|
||||
name: string;
|
||||
};
|
||||
executedBy: string;
|
||||
};
|
||||
|
||||
export type ChartDataResult = {
|
||||
chartLabel: string;
|
||||
dataLabels: string[];
|
||||
dataPoints: number[];
|
||||
};
|
||||
|
||||
export type AnalyticsOverviewDataResult = {
|
||||
title: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
export interface IOmnichannelAnalyticsService extends IServiceClass {
|
||||
getAgentOverviewData(options: AgentOverviewDataOptions): Promise<ConversationData | void>;
|
||||
getAnalyticsChartData(options: ChartDataOptions): Promise<ChartDataResult | void>;
|
||||
getAnalyticsOverviewData(options: AnalyticsOverviewDataOptions): Promise<AnalyticsOverviewDataResult[] | void>;
|
||||
}
|
||||
18
packages/core-services/src/types/IOmnichannelEEService.ts
Normal file
18
packages/core-services/src/types/IOmnichannelEEService.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { IOmnichannelRoom, IUser } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface IOmnichannelEEService extends IServiceClass {
|
||||
placeRoomOnHold(
|
||||
room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'u' | 'lastMessage'>,
|
||||
comment: string,
|
||||
onHoldBy: Pick<IUser, '_id' | 'username' | 'name'>,
|
||||
): Promise<void>;
|
||||
|
||||
resumeRoomOnHold(
|
||||
room: Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'servedBy' | 'u' | 'lastMessage'>,
|
||||
comment: string,
|
||||
resumeBy: Pick<IUser, '_id' | 'username' | 'name'>,
|
||||
clientAction?: boolean,
|
||||
): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { ISMSProviderConstructor, ISMSProvider } from '@rocket.chat/core-typings';
|
||||
|
||||
export type IOmnichannelIntegrationService = {
|
||||
getSmsService(name: string): Promise<ISMSProvider>;
|
||||
registerSmsService(name: string, service: ISMSProviderConstructor): void;
|
||||
isConfiguredSmsService(name: string): Promise<boolean>;
|
||||
};
|
||||
7
packages/core-services/src/types/IOmnichannelService.ts
Normal file
7
packages/core-services/src/types/IOmnichannelService.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { AtLeast, IOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface IOmnichannelService extends IServiceClass {
|
||||
isWithinMACLimit(_room: AtLeast<IOmnichannelRoom, 'v'>): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { IUser, IRoom } from '@rocket.chat/core-typings';
|
||||
|
||||
type WorkDetails = {
|
||||
rid: IRoom['_id'];
|
||||
userId: IUser['_id'];
|
||||
};
|
||||
|
||||
type WorkDetailsWithSource = WorkDetails & {
|
||||
from: string;
|
||||
};
|
||||
|
||||
export interface IOmnichannelTranscriptService {
|
||||
workOnPdf({ details }: { details: WorkDetailsWithSource }): Promise<void>;
|
||||
}
|
||||
30
packages/core-services/src/types/IPresence.ts
Normal file
30
packages/core-services/src/types/IPresence.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { IUser, UserStatus } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface IPresence extends IServiceClass {
|
||||
newConnection(
|
||||
uid: string | undefined,
|
||||
session: string | undefined,
|
||||
nodeId: string,
|
||||
): Promise<{ uid: string; connectionId: string } | undefined>;
|
||||
removeConnection(
|
||||
uid: string | undefined,
|
||||
session: string | undefined,
|
||||
nodeId: string,
|
||||
): Promise<{ uid: string; session: string } | undefined>;
|
||||
updateConnection(uid: string, connectionId: string): Promise<{ uid: string; connectionId: string } | undefined>;
|
||||
removeLostConnections(nodeID: string): Promise<string[]>;
|
||||
setStatus(userId: string, status: UserStatus, statusText?: string, statusExpiresAt?: Date): Promise<boolean>;
|
||||
setActiveState(
|
||||
userId: string,
|
||||
newState: Pick<IUser, 'statusDefault' | 'statusSource' | 'statusText' | 'statusExpiresAt' | 'statusId'>,
|
||||
): Promise<boolean>;
|
||||
endActiveState(userId: string, statusId?: string): Promise<boolean>;
|
||||
clearActiveState(userId: string): Promise<boolean>;
|
||||
setConnectionStatus(uid: string, status: UserStatus, session: string): Promise<boolean>;
|
||||
toggleBroadcast(enabled: boolean): void;
|
||||
getConnectionCount(): { current: number; max: number };
|
||||
getPeakConnections(reset?: boolean): number;
|
||||
resetPeakConnections(): void;
|
||||
}
|
||||
9
packages/core-services/src/types/IPushService.ts
Normal file
9
packages/core-services/src/types/IPushService.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { IPushToken, Optional } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export interface IPushService extends IServiceClass {
|
||||
registerPushToken(
|
||||
data: Optional<Pick<IPushToken, '_id' | 'token' | 'authToken' | 'appName' | 'userId' | 'metadata'>, '_id' | 'metadata'>,
|
||||
): Promise<Omit<IPushToken, 'authToken'>>;
|
||||
}
|
||||
12
packages/core-services/src/types/IQueueWorkerService.ts
Normal file
12
packages/core-services/src/types/IQueueWorkerService.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export type HealthAggResult = {
|
||||
total: number;
|
||||
type: string;
|
||||
status: 'Rejected' | 'In progress';
|
||||
};
|
||||
|
||||
type Actions = 'work' | 'workComplete';
|
||||
|
||||
export interface IQueueWorkerService {
|
||||
queueWork<T extends Record<string, unknown>>(queue: Actions, to: string, data: T): Promise<void>;
|
||||
queueInfo(): Promise<HealthAggResult[]>;
|
||||
}
|
||||
87
packages/core-services/src/types/IRoomService.ts
Normal file
87
packages/core-services/src/types/IRoomService.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { AtLeast, IRoom, ISubscription, IUser, MessageTypesValues } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface ISubscriptionExtraData {
|
||||
open: boolean;
|
||||
ls?: Date;
|
||||
prid?: string;
|
||||
roles?: string[];
|
||||
}
|
||||
|
||||
export interface ICreateRoomOptions extends Partial<Record<string, boolean | string | ISubscriptionExtraData>> {
|
||||
forceNew?: boolean;
|
||||
creator: string;
|
||||
subscriptionExtra?: ISubscriptionExtraData;
|
||||
}
|
||||
|
||||
export interface ICreateRoomParams<T extends IRoom = IRoom> {
|
||||
type: IRoom['t'];
|
||||
name: IRoom['name'];
|
||||
members?: Array<string>; // member's usernames
|
||||
readOnly?: boolean;
|
||||
extraData?: Partial<T>;
|
||||
options?: ICreateRoomOptions;
|
||||
}
|
||||
export interface IRoomService {
|
||||
addMember(uid: string, rid: string): Promise<boolean>;
|
||||
create<T extends IRoom = IRoom>(uid: string, params: ICreateRoomParams<T>): Promise<IRoom>;
|
||||
createDirectMessage(data: { to: string; from: string }): Promise<{ rid: string }>;
|
||||
createDirectMessageWithMultipleUsers(members: string[], creatorId: string): Promise<{ rid: string }>;
|
||||
addUserToRoom(
|
||||
roomId: string,
|
||||
user: Pick<IUser, '_id'> | string,
|
||||
inviter?: Pick<IUser, '_id' | 'username'>,
|
||||
options?: {
|
||||
skipSystemMessage?: boolean;
|
||||
skipAlertSound?: boolean;
|
||||
createAsHidden?: boolean;
|
||||
},
|
||||
): Promise<boolean | undefined>;
|
||||
performUserRemoval(room: IRoom, user: IUser, options?: { byUser?: IUser }): Promise<void>;
|
||||
performUserBan(room: IRoom, user: IUser, byUser: IUser): Promise<void>;
|
||||
performUserUnban(room: IRoom, user: IUser, byUser: IUser): Promise<void>;
|
||||
performAcceptRoomInvite(room: IRoom, subscription: ISubscription, user: IUser): Promise<void>;
|
||||
revokeInvite(room: IRoom, user: IUser): Promise<void>;
|
||||
removeUserFromRoom(
|
||||
roomId: string,
|
||||
user: IUser,
|
||||
options?: { byUser?: Pick<IUser, '_id' | 'username'>; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
|
||||
): Promise<void>;
|
||||
getValidRoomName(displayName: string, roomId?: string, options?: { allowDuplicates?: boolean }): Promise<string>;
|
||||
saveRoomTopic(
|
||||
roomId: string,
|
||||
roomTopic: string | undefined,
|
||||
user: Pick<IUser, 'username' | '_id' | 'federation' | 'federated'>,
|
||||
sendMessage?: boolean,
|
||||
): Promise<void>;
|
||||
getRouteLink(room: AtLeast<IRoom, '_id' | 't' | 'name'>): Promise<string | boolean>;
|
||||
join(param: { room: IRoom; user: Pick<IUser, '_id' | 'federated' | 'federation'>; joinCode?: string }): Promise<boolean | undefined>;
|
||||
beforeLeave(room: IRoom): Promise<void>;
|
||||
beforeUserRemoved(room: IRoom): Promise<void>;
|
||||
beforeNameChange(room: IRoom): Promise<void>;
|
||||
beforeTopicChange(room: IRoom): Promise<void>;
|
||||
saveRoomName(roomId: string, userId: string, name: string): Promise<void>;
|
||||
addUserRoleRoomScoped(fromUserId: string, userId: string, roomId: string, role: 'moderator' | 'owner' | 'leader' | 'user'): Promise<void>;
|
||||
createUserSubscription(params: {
|
||||
room: IRoom;
|
||||
ts: Date;
|
||||
userToBeAdded: IUser;
|
||||
inviter?: Pick<IUser, '_id' | 'username'>;
|
||||
createAsHidden?: boolean;
|
||||
skipAlertSound?: boolean;
|
||||
skipSystemMessage?: boolean;
|
||||
status?: 'INVITED';
|
||||
roles?: ISubscription['roles'];
|
||||
}): Promise<string | undefined>;
|
||||
updateDirectMessageRoomName(
|
||||
room: IRoom,
|
||||
ignoreStatusFromSubs?: string[],
|
||||
updatedNames?: AtLeast<IUser, '_id' | 'name' | 'username'>[],
|
||||
): Promise<boolean>;
|
||||
markAsRead(room: IRoom, userId: string, readThreads?: boolean): Promise<void>;
|
||||
readThread(params: { user: IUser; room: IRoom; tmid: string }): Promise<void>;
|
||||
unbanAndInviteUser(
|
||||
subscription: ISubscription,
|
||||
inviteeUser: Pick<IUser, '_id' | 'username' | 'name'>,
|
||||
inviterUser: Required<Pick<IUser, '_id' | 'username'>> & Pick<IUser, 'name'>,
|
||||
): Promise<void>;
|
||||
}
|
||||
3
packages/core-services/src/types/ISAUMonitorService.ts
Normal file
3
packages/core-services/src/types/ISAUMonitorService.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export type ISAUMonitorService = IServiceClass;
|
||||
6
packages/core-services/src/types/ISettingsService.ts
Normal file
6
packages/core-services/src/types/ISettingsService.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { SettingValue } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface ISettingsService {
|
||||
get<T extends SettingValue>(settingId: string): Promise<T>;
|
||||
set<T extends SettingValue>(settingId: string, value: T): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { IUser, UserPresence } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface IStatusVisibilityService {
|
||||
getHiddenFrom(viewerId: IUser['_id'] | null | undefined): Promise<IUser['_id'][]>;
|
||||
hasRestrictions(targetId: IUser['_id']): Promise<boolean>;
|
||||
getRestrictedUsers(): Promise<IUser['_id'][]>;
|
||||
refresh(targets?: IUser['_id'][]): Promise<UserPresence[]>;
|
||||
invalidate(targets?: IUser['_id'][]): Promise<UserPresence[]>;
|
||||
}
|
||||
141
packages/core-services/src/types/ITeamService.ts
Normal file
141
packages/core-services/src/types/ITeamService.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import type {
|
||||
IPaginationOptions,
|
||||
IQueryOptions,
|
||||
IRecordsWithTotal,
|
||||
ITeam,
|
||||
ITeamMember,
|
||||
ITeamStats,
|
||||
TeamType,
|
||||
IRoom,
|
||||
IUser,
|
||||
IRole,
|
||||
AtLeast,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import type { Document, Filter, FindOptions } from 'mongodb';
|
||||
|
||||
import type { ICreateRoomParams } from './IRoomService';
|
||||
|
||||
export interface ITeamCreateRoom extends Omit<ICreateRoomParams, 'type'> {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface ITeamCreateParams {
|
||||
team: Pick<ITeam, 'name' | 'type'>;
|
||||
room: ITeamCreateRoom;
|
||||
members?: Array<string> | null; // list of user _ids
|
||||
owner?: string | null; // the team owner. If not present, owner = requester
|
||||
}
|
||||
|
||||
export interface ITeamMemberParams {
|
||||
userId: string;
|
||||
roles?: Array<IRole['_id']> | null;
|
||||
}
|
||||
|
||||
export interface IUserInfo {
|
||||
_id: string;
|
||||
username?: string;
|
||||
name?: string;
|
||||
status?: string;
|
||||
settings?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface ITeamMemberInfo {
|
||||
user: IUserInfo;
|
||||
roles?: IRole['_id'][] | null;
|
||||
createdBy: Omit<IUserInfo, 'name' | 'status'>;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface ITeamInfo extends ITeam {
|
||||
rooms: number;
|
||||
numberOfUsers: number;
|
||||
}
|
||||
|
||||
export interface IListRoomsFilter {
|
||||
name?: string;
|
||||
isDefault: boolean;
|
||||
getAllRooms: boolean;
|
||||
allowPrivateTeam: boolean;
|
||||
}
|
||||
|
||||
export type ITeamUpdateData = { updateRoom?: boolean } & (
|
||||
| {
|
||||
name: string;
|
||||
type?: TeamType;
|
||||
}
|
||||
| {
|
||||
name?: string;
|
||||
type: TeamType;
|
||||
}
|
||||
);
|
||||
|
||||
export type ITeamAutocompleteResult = Pick<IRoom, '_id' | 'fname' | 'teamId' | 'name' | 't' | 'avatarETag'>;
|
||||
|
||||
export interface ITeamService {
|
||||
create(uid: string, params: ITeamCreateParams): Promise<ITeam>;
|
||||
addRooms(uid: string, rooms: Array<string>, teamId: string): Promise<Array<IRoom>>;
|
||||
removeRoom(uid: string, rid: string, teamId: string, canRemoveAnyRoom: boolean): Promise<IRoom>;
|
||||
listRooms(uid: string, teamId: string, filter: IListRoomsFilter, pagination: IPaginationOptions): Promise<IRecordsWithTotal<IRoom>>;
|
||||
listRoomsOfUser(
|
||||
uid: string,
|
||||
teamId: string,
|
||||
userId: string,
|
||||
allowPrivateTeam: boolean,
|
||||
showCanDeleteOnly: boolean,
|
||||
pagination: IPaginationOptions,
|
||||
): Promise<IRecordsWithTotal<IRoom>>;
|
||||
updateRoom(uid: string, rid: string, isDefault: boolean, canUpdateAnyRoom: boolean): Promise<IRoom>;
|
||||
list(uid: string, paginationOptions?: IPaginationOptions, queryOptions?: IQueryOptions<ITeam>): Promise<IRecordsWithTotal<ITeam>>;
|
||||
listAll(options?: IPaginationOptions): Promise<IRecordsWithTotal<ITeam>>;
|
||||
listByNames(names: Array<string>, options?: FindOptions<ITeam>): Promise<Array<ITeam>>;
|
||||
listByIds(ids: Array<string>, options?: FindOptions<ITeam>): Promise<ITeam[]>;
|
||||
search(userId: string, term: string | RegExp, options?: FindOptions<ITeam>): Promise<ITeam[]>;
|
||||
members(
|
||||
uid: string,
|
||||
teamId: string,
|
||||
canSeeAll: boolean,
|
||||
options?: IPaginationOptions,
|
||||
queryOptions?: Filter<IUser>,
|
||||
): Promise<IRecordsWithTotal<ITeamMemberInfo>>;
|
||||
addMember(inviter: Pick<IUser, '_id' | 'username'>, userId: string, teamId: string): Promise<boolean>;
|
||||
addMembers(uid: string, teamId: string, members: Array<ITeamMemberParams>): Promise<void>;
|
||||
updateMember(teamId: string, members: ITeamMemberParams): Promise<void>;
|
||||
removeMember(teamId: string, userId: string): Promise<void>;
|
||||
removeMembers(uid: string, teamId: string, members: Array<ITeamMemberParams>): Promise<boolean>;
|
||||
getInfoByName(teamName: string): Promise<Partial<ITeam> | null>;
|
||||
getInfoById(teamId: string): Promise<Partial<ITeam> | null>;
|
||||
deleteById(teamId: string): Promise<boolean>;
|
||||
deleteByName(teamName: string): Promise<boolean>;
|
||||
unsetTeamIdOfRooms(user: AtLeast<IUser, '_id' | 'username' | 'name'>, team: AtLeast<ITeam, '_id' | 'roomId'>): Promise<void>;
|
||||
getOneById(teamId: string, options?: FindOptions<ITeam>): Promise<ITeam | null>;
|
||||
getOneById<P extends Document>(teamId: string, options?: FindOptions<P extends ITeam ? ITeam : P>): Promise<ITeam | P | null>;
|
||||
getOneByName(teamName: string | RegExp, options?: FindOptions<ITeam>): Promise<ITeam | null>;
|
||||
getOneByMainRoomId(teamId: string): Promise<Pick<ITeam, '_id'> | null>;
|
||||
getOneByRoomId(teamId: string, options?: FindOptions<ITeam>): Promise<ITeam | null>;
|
||||
getMatchingTeamRooms(teamId: string, rids: Array<string>): Promise<Array<string>>;
|
||||
autocomplete(uid: string, name: string): Promise<ITeamAutocompleteResult[]>;
|
||||
getAllPublicTeams(options?: FindOptions<ITeam>): Promise<Array<ITeam>>;
|
||||
getMembersByTeamIds(teamIds: Array<string>, options: FindOptions<ITeamMember>): Promise<Array<ITeamMember>>;
|
||||
update(uid: string, teamId: string, updateData: ITeamUpdateData): Promise<void>;
|
||||
listTeamsBySubscriberUserId(uid: string, options?: FindOptions<ITeamMember>): Promise<Array<ITeamMember> | null>;
|
||||
insertMemberOnTeams(userId: string, teamIds: Array<string>): Promise<void>;
|
||||
removeMemberFromTeams(userId: string, teamIds: Array<string>): Promise<void>;
|
||||
removeAllMembersFromTeam(teamId: string): Promise<void>;
|
||||
removeRolesFromMember(teamId: string, userId: string, roles: Array<IRole['_id']>): Promise<boolean>;
|
||||
getStatistics(): Promise<ITeamStats>;
|
||||
findBySubscribedUserIds(userId: string, callerId?: string): Promise<ITeam[]>;
|
||||
addRolesToMember(teamId: string, userId: string, roles: Array<string>): Promise<boolean>;
|
||||
addRolesToSubscription(roomId: string, userId: string, roles: Array<string>): Promise<boolean>;
|
||||
getRoomInfo(
|
||||
room: AtLeast<IRoom, 'teamId' | 'teamMain' | '_id'>,
|
||||
): Promise<{ team?: Pick<ITeam, 'name' | 'roomId' | 'type'>; parentRoom?: Pick<IRoom, 'name' | 'fname' | 't' | '_id'> }>;
|
||||
listChildren(
|
||||
userId: string,
|
||||
team: AtLeast<ITeam, '_id' | 'roomId' | 'type'>,
|
||||
filter?: string,
|
||||
type?: 'channels' | 'discussions',
|
||||
sort?: Record<string, 1 | -1>,
|
||||
skip?: number,
|
||||
limit?: number,
|
||||
): Promise<{ total: number; data: IRoom[] }>;
|
||||
}
|
||||
11
packages/core-services/src/types/ITelemetryEvent.ts
Normal file
11
packages/core-services/src/types/ITelemetryEvent.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
type updateCounterDataType = { settingsId: string };
|
||||
type slashCommandsDataType = { command: string };
|
||||
|
||||
// TODO this is duplicated from /packages/rest-typings/src/v1/statistics.ts
|
||||
export type TelemetryMap = { slashCommandsStats: slashCommandsDataType; updateCounter: updateCounterDataType };
|
||||
export type TelemetryEvents = keyof TelemetryMap;
|
||||
|
||||
export interface ITelemetryEvent {
|
||||
register: (name: TelemetryEvents, fn: () => Promise<any> | void) => void;
|
||||
call: <T extends TelemetryEvents>(eventName: T, data: TelemetryMap[T]) => Promise<any> | void;
|
||||
}
|
||||
61
packages/core-services/src/types/IUiKitCoreApp.ts
Normal file
61
packages/core-services/src/types/IUiKitCoreApp.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
import type { IServiceClass } from './ServiceClass';
|
||||
|
||||
export type UiKitCoreAppBlockActionPayload = Pick<
|
||||
UiKit.ViewBlockActionUserInteraction | UiKit.MessageBlockActionUserInteraction,
|
||||
'type' | 'actionId' | 'triggerId' | 'container' | 'payload'
|
||||
> & {
|
||||
appId: string;
|
||||
message: IMessage['_id'] | undefined;
|
||||
user: IUser | undefined;
|
||||
visitor:
|
||||
| {
|
||||
id: string;
|
||||
username: string;
|
||||
name?: string;
|
||||
department?: string;
|
||||
updatedAt?: Date;
|
||||
token: string;
|
||||
phone?: { phoneNumber: string }[] | null;
|
||||
visitorEmails?: { address: string }[];
|
||||
livechatData?: Record<string, unknown>;
|
||||
status?: 'online' | 'away' | 'offline' | 'busy' | 'disabled';
|
||||
}
|
||||
| undefined;
|
||||
room: IRoom['_id'] | undefined;
|
||||
};
|
||||
|
||||
export type UiKitCoreAppViewClosedPayload = Pick<UiKit.ViewClosedUserInteraction, 'type' | 'triggerId'> & {
|
||||
appId: string;
|
||||
user: IUser | undefined;
|
||||
payload: {
|
||||
view: UiKit.View & {
|
||||
viewId?: string;
|
||||
id?: string;
|
||||
state: { [blockId: string]: { [key: string]: unknown } };
|
||||
};
|
||||
isCleared?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type UiKitCoreAppViewSubmitPayload = Pick<UiKit.ViewSubmitUserInteraction, 'type' | 'actionId' | 'triggerId' | 'payload'> & {
|
||||
appId: string;
|
||||
user: IUser | undefined;
|
||||
};
|
||||
|
||||
export interface IUiKitCoreApp {
|
||||
appId: string;
|
||||
|
||||
blockAction?(payload: UiKitCoreAppBlockActionPayload): Promise<UiKit.ServerInteraction | undefined>;
|
||||
viewClosed?(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction | undefined>;
|
||||
viewSubmit?(payload: UiKitCoreAppViewSubmitPayload): Promise<UiKit.ServerInteraction | undefined>;
|
||||
}
|
||||
|
||||
export interface IUiKitCoreAppService extends IServiceClass {
|
||||
isRegistered(appId: string): Promise<boolean>;
|
||||
blockAction(payload: UiKitCoreAppBlockActionPayload): Promise<UiKit.ServerInteraction | undefined>;
|
||||
viewClosed(payload: UiKitCoreAppViewClosedPayload): Promise<UiKit.ServerInteraction | undefined>;
|
||||
viewSubmit(payload: UiKitCoreAppViewSubmitPayload): Promise<UiKit.ServerInteraction | undefined>;
|
||||
}
|
||||
49
packages/core-services/src/types/IUploadService.ts
Normal file
49
packages/core-services/src/types/IUploadService.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type Stream from 'node:stream';
|
||||
|
||||
import type { IUploadDetails } from '@rocket.chat/apps-engine/definition/uploads/IUploadDetails';
|
||||
import type { IMessage, IUpload, IUser, FilesAndAttachments, AtLeast } from '@rocket.chat/core-typings';
|
||||
|
||||
export interface IUploadFileParams {
|
||||
userId: string;
|
||||
buffer: Buffer;
|
||||
details: IUploadDetails;
|
||||
federation?: Required<IUpload>['federation'];
|
||||
}
|
||||
export interface ISendFileMessageParams {
|
||||
roomId: string;
|
||||
userId: string;
|
||||
file: IUpload;
|
||||
message?: Partial<IMessage>;
|
||||
}
|
||||
|
||||
export interface ISendFileLivechatMessageParams {
|
||||
roomId: string;
|
||||
visitorToken: string;
|
||||
file: IUpload;
|
||||
message?: Partial<IMessage>;
|
||||
}
|
||||
|
||||
export interface IUploadService {
|
||||
uploadFile(params: IUploadFileParams): Promise<IUpload>;
|
||||
sendFileMessage(params: ISendFileMessageParams): Promise<boolean | undefined>;
|
||||
sendFileLivechatMessage(params: ISendFileLivechatMessageParams): Promise<boolean | undefined>;
|
||||
getFileBuffer({ file }: { file: IUpload }): Promise<Buffer>;
|
||||
extractMetadata(file: IUpload): Promise<{ height?: number; width?: number; format?: string }>;
|
||||
parseFileIntoMessageAttachments(file: Partial<IUpload>, roomId: string, user: IUser): Promise<FilesAndAttachments>;
|
||||
canDeleteFile(
|
||||
user: Pick<IUser, '_id' | 'username'>,
|
||||
file: Pick<IUpload, '_id' | 'userId' | 'rid' | 'expiresAt' | 'uploadedAt'>,
|
||||
msg: IMessage | null,
|
||||
): Promise<boolean>;
|
||||
deleteFile(user: IUser, fileId: IUpload['_id'], msg: IMessage | null): Promise<{ deletedFiles: IUpload['_id'][] }>;
|
||||
streamUploadedFile({
|
||||
file,
|
||||
imageResizeOpts,
|
||||
}: {
|
||||
file: IUpload;
|
||||
imageResizeOpts?: { width: number; height: number };
|
||||
}): Promise<Stream.Readable>;
|
||||
uploadFileFromStream({ streamParam, details }: { streamParam: Stream.Readable; details: Omit<IUploadDetails, 'size'> }): Promise<IUpload>;
|
||||
setUserAvatar(user: Pick<IUser, '_id' | 'username'>, buffer: Buffer, contentType: string, service: 'rest'): Promise<void>;
|
||||
resetUserAvatar(user: AtLeast<IUser, '_id' | 'username'>): Promise<void>;
|
||||
}
|
||||
3
packages/core-services/src/types/IUserService.ts
Normal file
3
packages/core-services/src/types/IUserService.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface IUserService {
|
||||
ensureLoginTokensLimit(uid: string): Promise<void>;
|
||||
}
|
||||
47
packages/core-services/src/types/IVideoConfService.ts
Normal file
47
packages/core-services/src/types/IVideoConfService.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type {
|
||||
IRoom,
|
||||
IStats,
|
||||
IUser,
|
||||
IVoIPVideoConference,
|
||||
VideoConference,
|
||||
VideoConferenceCapabilities,
|
||||
VideoConferenceCreateData,
|
||||
VideoConferenceInstructions,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import type { InsertionModel } from '@rocket.chat/model-typings';
|
||||
import type { PaginatedResult } from '@rocket.chat/rest-typings';
|
||||
import type * as UiKit from '@rocket.chat/ui-kit';
|
||||
|
||||
export type VideoConferenceJoinOptions = {
|
||||
mic?: boolean;
|
||||
cam?: boolean;
|
||||
};
|
||||
|
||||
export interface IVideoConfService {
|
||||
create(data: VideoConferenceCreateData, useAppUser?: boolean): Promise<VideoConferenceInstructions>;
|
||||
start(caller: IUser['_id'], rid: string, options: { title?: string; allowRinging?: boolean }): Promise<VideoConferenceInstructions>;
|
||||
join(uid: IUser['_id'] | undefined, callId: VideoConference['_id'], options: VideoConferenceJoinOptions): Promise<string>;
|
||||
getInfo(callId: VideoConference['_id'], uid: IUser['_id'] | undefined): Promise<UiKit.ModalSurfaceLayout>;
|
||||
cancel(uid: IUser['_id'], callId: VideoConference['_id']): Promise<void>;
|
||||
get(callId: VideoConference['_id']): Promise<Omit<VideoConference, 'providerData'> | null>;
|
||||
getUnfiltered(callId: VideoConference['_id']): Promise<VideoConference | null>;
|
||||
list(roomId: IRoom['_id'], pagination?: { offset?: number; count?: number }): Promise<PaginatedResult<{ data: VideoConference[] }>>;
|
||||
setProviderData(callId: VideoConference['_id'], data: VideoConference['providerData'] | undefined): Promise<void>;
|
||||
setEndedBy(callId: VideoConference['_id'], endedBy: IUser['_id']): Promise<void>;
|
||||
setEndedAt(callId: VideoConference['_id'], endedAt: Date): Promise<void>;
|
||||
setStatus(callId: VideoConference['_id'], status: VideoConference['status']): Promise<void>;
|
||||
addUser(callId: VideoConference['_id'], userId?: IUser['_id'], ts?: Date): Promise<void>;
|
||||
listProviders(): Promise<{ key: string; label: string }[]>;
|
||||
listCapabilities(): Promise<{ providerName: string; capabilities: VideoConferenceCapabilities }>;
|
||||
listProviderCapabilities(providerName: string): Promise<VideoConferenceCapabilities>;
|
||||
declineLivechatCall(callId: VideoConference['_id']): Promise<boolean>;
|
||||
diagnoseProvider(uid: string, rid: string, providerName?: string): Promise<string | undefined>;
|
||||
getStatistics(): Promise<IStats['videoConf']>;
|
||||
validateAction(
|
||||
event: string,
|
||||
caller: IUser['_id'],
|
||||
params: { callId: VideoConference['_id']; uid: IUser['_id']; rid: IRoom['_id'] },
|
||||
): Promise<boolean>;
|
||||
assignDiscussionToConference(callId: VideoConference['_id'], rid: IRoom['_id'] | undefined): Promise<void>;
|
||||
createVoIP(data: InsertionModel<IVoIPVideoConference>): Promise<IVoIPVideoConference['_id'] | undefined>;
|
||||
}
|
||||
161
packages/core-services/src/types/ServiceClass.ts
Normal file
161
packages/core-services/src/types/ServiceClass.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
|
||||
import type { ISetting } from '@rocket.chat/core-typings';
|
||||
|
||||
import type { IApiService } from './IApiService';
|
||||
import type { IBroker, IBrokerNode } from './IBroker';
|
||||
import type { ClientAction, EventSignatures } from '../events/Events';
|
||||
import { asyncLocalStorage } from '../lib/asyncLocalStorage';
|
||||
|
||||
export interface IServiceContext {
|
||||
id: string; // Context ID
|
||||
broker: IBroker; // Instance of the broker.
|
||||
nodeID: string | null; // The caller or target Node ID.
|
||||
// action: Object; // Instance of action definition.
|
||||
// event: Object; // Instance of event definition.
|
||||
// eventName: Object; // The emitted event name.
|
||||
// eventType: String; // Type of event (“emit” or “broadcast”).
|
||||
// eventGroups: Array; // String> Groups of event.
|
||||
// caller: String; // Service full name of the caller. E.g.: v3.myService
|
||||
requestID: string | null; // Request ID. If you make nested-calls, it will be the same ID.
|
||||
// parentID: String; // Parent context ID (in nested-calls).
|
||||
// params: Any; // Request params. Second argument from broker.call.
|
||||
// meta: Any; // Request metadata. It will be also transferred to nested-calls.
|
||||
// locals: any; // Local data.
|
||||
// level: Number; // Request level (in nested-calls). The first level is 1.
|
||||
// span: Span; // Current active span.
|
||||
ctx?: any;
|
||||
}
|
||||
|
||||
export interface IServiceClass {
|
||||
getName(): string;
|
||||
onNodeConnected?({ node, reconnected }: { node: IBrokerNode; reconnected: boolean }): void;
|
||||
onNodeUpdated?({ node }: { node: IBrokerNode }): void;
|
||||
onNodeDisconnected?({ node, unexpected }: { node: IBrokerNode; unexpected: boolean }): Promise<void>;
|
||||
getEvents(): { eventName: keyof EventSignatures; listeners: { (...args: any[]): void }[] }[];
|
||||
removeAllListeners(): void;
|
||||
|
||||
setApi(api: IApiService): void;
|
||||
|
||||
onEvent<T extends keyof EventSignatures>(event: T, handler: EventSignatures[T]): void;
|
||||
emit<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): void;
|
||||
onSettingChanged(
|
||||
settingId: ISetting['_id'],
|
||||
cb: (data: { clientAction: ClientAction; setting: ISetting }) => Promise<void>,
|
||||
ignoreActions?: ClientAction[],
|
||||
): void;
|
||||
|
||||
isInternal(): boolean;
|
||||
|
||||
created(): Promise<void>;
|
||||
started(): Promise<void>;
|
||||
stopped(): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class ServiceClass implements IServiceClass {
|
||||
protected abstract name: string;
|
||||
|
||||
protected events = new EventEmitter();
|
||||
|
||||
protected settings = new EventEmitter();
|
||||
|
||||
protected internal = false;
|
||||
|
||||
protected api?: IApiService;
|
||||
|
||||
protected settingListenerActive = false;
|
||||
|
||||
constructor() {
|
||||
this.emit = this.emit.bind(this);
|
||||
}
|
||||
|
||||
setApi(api: IApiService): void {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
getEvents(): { eventName: keyof EventSignatures; listeners: { (...args: any[]): void }[] }[] {
|
||||
return this.events.eventNames().map((eventName) => ({
|
||||
eventName: eventName as unknown as keyof EventSignatures,
|
||||
listeners: this.events.rawListeners(eventName) as { (...args: any[]): void }[],
|
||||
}));
|
||||
}
|
||||
|
||||
removeAllListeners(): void {
|
||||
this.events.removeAllListeners();
|
||||
this.settings.removeAllListeners();
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
isInternal(): boolean {
|
||||
return this.internal;
|
||||
}
|
||||
|
||||
get context(): IServiceContext | undefined {
|
||||
return asyncLocalStorage.getStore();
|
||||
}
|
||||
|
||||
public onEvent<T extends keyof EventSignatures>(event: T, handler: EventSignatures[T]): void {
|
||||
this.events.on(event, handler);
|
||||
}
|
||||
|
||||
public emit<T extends keyof EventSignatures>(event: T, ...args: Parameters<EventSignatures[T]>): void {
|
||||
this.events.emit(event, ...args);
|
||||
}
|
||||
|
||||
private registerEventListener() {
|
||||
if (this.settingListenerActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.settingListenerActive = true;
|
||||
|
||||
this.onEvent('watch.settings', async ({ clientAction, setting }): Promise<void> => {
|
||||
const { _id } = setting;
|
||||
|
||||
this.settings.emit(_id, { clientAction, setting });
|
||||
});
|
||||
}
|
||||
|
||||
public onSettingChanged(
|
||||
settingId: ISetting['_id'],
|
||||
cb: (data: { clientAction: ClientAction; setting: ISetting }) => Promise<void>,
|
||||
ignoreActions: ClientAction[] = ['removed'],
|
||||
): void {
|
||||
this.registerEventListener();
|
||||
|
||||
this.settings.on(settingId, async ({ clientAction, setting }: { clientAction: ClientAction; setting: ISetting }): Promise<void> => {
|
||||
if (ignoreActions?.includes(clientAction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await cb({ clientAction, setting });
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async created(): Promise<void> {
|
||||
// noop
|
||||
}
|
||||
|
||||
async started(): Promise<void> {
|
||||
// noop
|
||||
}
|
||||
|
||||
async stopped(): Promise<void> {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal service is a service that is registered only on monolith node.
|
||||
* Services that run on their own node should use @ServiceClass instead.
|
||||
*/
|
||||
export abstract class ServiceClassInternal extends ServiceClass {
|
||||
protected override internal = true;
|
||||
}
|
||||
90
packages/core-services/tests/LocalBroker.test.ts
Normal file
90
packages/core-services/tests/LocalBroker.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { ServiceClass } from '../src';
|
||||
import { LocalBroker } from '../src/LocalBroker';
|
||||
|
||||
describe('LocalBroker', () => {
|
||||
describe('#createService()', () => {
|
||||
it('should call all the expected lifecycle hooks when creating a service', () => {
|
||||
const createdStub = jest.fn();
|
||||
const instance = new (class extends ServiceClass {
|
||||
name = 'test';
|
||||
|
||||
async created() {
|
||||
createdStub();
|
||||
}
|
||||
})();
|
||||
|
||||
const broker = new LocalBroker();
|
||||
broker.createService(instance);
|
||||
|
||||
expect(createdStub).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#destroyService()', () => {
|
||||
it('should call all the expected lifecycle hooks when destroying a service', () => {
|
||||
const removeAllListenersStub = jest.fn();
|
||||
const stoppedStub = jest.fn();
|
||||
const instance = new (class extends ServiceClass {
|
||||
name = 'test';
|
||||
|
||||
removeAllListeners() {
|
||||
removeAllListenersStub();
|
||||
}
|
||||
|
||||
async stopped() {
|
||||
stoppedStub();
|
||||
}
|
||||
})();
|
||||
|
||||
const broker = new LocalBroker();
|
||||
broker.createService(instance);
|
||||
broker.destroyService(instance);
|
||||
|
||||
expect(removeAllListenersStub).toHaveBeenCalled();
|
||||
expect(stoppedStub).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#broadcast()', () => {
|
||||
it('should call all the ServiceClass instance registered events', () => {
|
||||
const instance = new (class extends ServiceClass {
|
||||
name = 'test';
|
||||
})();
|
||||
const testListener = jest.fn();
|
||||
const testListener2 = jest.fn();
|
||||
const test2Listener = jest.fn();
|
||||
instance.onEvent('test' as any, testListener);
|
||||
instance.onEvent('test' as any, testListener2);
|
||||
instance.onEvent('test2' as any, test2Listener);
|
||||
|
||||
const broker = new LocalBroker();
|
||||
broker.createService(instance);
|
||||
broker.broadcast('test' as any, 'test');
|
||||
broker.broadcast('test2' as any, 'test2');
|
||||
|
||||
expect(testListener).toHaveBeenCalledWith('test');
|
||||
expect(testListener2).toHaveBeenCalledWith('test');
|
||||
expect(test2Listener).toHaveBeenCalledWith('test2');
|
||||
});
|
||||
|
||||
it('should NOT call any instance event anymore after the service being destroyed', () => {
|
||||
const instance = new (class extends ServiceClass {
|
||||
name = 'test';
|
||||
})();
|
||||
const testListener = jest.fn();
|
||||
const test2Listener = jest.fn();
|
||||
instance.onEvent('test' as any, testListener);
|
||||
instance.onEvent('test2' as any, test2Listener);
|
||||
|
||||
const broker = new LocalBroker();
|
||||
broker.createService(instance);
|
||||
broker.destroyService(instance);
|
||||
|
||||
broker.broadcast('test' as any, 'test');
|
||||
broker.broadcast('test2' as any, 'test2');
|
||||
|
||||
expect(testListener).not.toHaveBeenCalled();
|
||||
expect(test2Listener).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
60
packages/core-services/tests/ServiceClass.test.ts
Normal file
60
packages/core-services/tests/ServiceClass.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { ServiceClass } from '../src/types/ServiceClass';
|
||||
|
||||
describe('ServiceClass', () => {
|
||||
describe('#onEvent()', () => {
|
||||
it('should add one event to the internal event emitter instance', () => {
|
||||
const instance = new (class extends ServiceClass {})();
|
||||
|
||||
instance.onEvent('test' as any, () => {});
|
||||
|
||||
expect(instance.getEvents()).toHaveLength(1);
|
||||
expect(instance.getEvents()[0].eventName).toBe('test');
|
||||
});
|
||||
});
|
||||
describe('#getEvents()', () => {
|
||||
it('should return an array of events with all of their listeners', () => {
|
||||
const instance = new (class extends ServiceClass {})();
|
||||
const listener1 = () => {};
|
||||
const listener2 = () => {};
|
||||
const listener3 = () => {};
|
||||
const listeners = [listener1, listener2, listener3];
|
||||
|
||||
instance.onEvent('test' as any, listener1);
|
||||
instance.onEvent('test' as any, listener2);
|
||||
instance.onEvent('test' as any, listener3);
|
||||
|
||||
expect(instance.getEvents()).toHaveLength(1);
|
||||
expect(instance.getEvents()[0].listeners).toEqual(listeners);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#removeAllListeners()', () => {
|
||||
it('should remove all listeners', () => {
|
||||
const instance = new (class extends ServiceClass {})();
|
||||
const listener1 = () => {};
|
||||
const listener2 = () => {};
|
||||
const listener3 = () => {};
|
||||
|
||||
instance.onEvent('test' as any, listener1);
|
||||
instance.onEvent('test' as any, listener2);
|
||||
instance.onEvent('test' as any, listener3);
|
||||
|
||||
expect(instance.getEvents()).toHaveLength(1);
|
||||
instance.removeAllListeners();
|
||||
expect(instance.getEvents()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not be possible to receive any event after remove them all', () => {
|
||||
const instance = new (class extends ServiceClass {})();
|
||||
const listener1 = jest.fn();
|
||||
|
||||
instance.onEvent('test' as any, listener1);
|
||||
instance.emit('test' as any, 'test');
|
||||
instance.removeAllListeners();
|
||||
instance.emit('test' as any, 'test');
|
||||
|
||||
expect(listener1).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
91
packages/core-services/tests/ServiceStarter.test.ts
Normal file
91
packages/core-services/tests/ServiceStarter.test.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { ServiceStarter } from '../src/lib/ServiceStarter';
|
||||
|
||||
const wait = (time: number) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(undefined), time);
|
||||
});
|
||||
};
|
||||
|
||||
describe('ServiceStarter', () => {
|
||||
it('should call the starterFn and stopperFn when calling .start and .stop', async () => {
|
||||
const start = jest.fn();
|
||||
const stop = jest.fn();
|
||||
|
||||
const instance = new ServiceStarter(start, stop);
|
||||
|
||||
expect(start).not.toHaveBeenCalled();
|
||||
expect(stop).not.toHaveBeenCalled();
|
||||
|
||||
await instance.start();
|
||||
|
||||
expect(start).toHaveBeenCalled();
|
||||
expect(stop).not.toHaveBeenCalled();
|
||||
|
||||
start.mockReset();
|
||||
|
||||
await instance.stop();
|
||||
|
||||
expect(start).not.toHaveBeenCalled();
|
||||
expect(stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should only call .start for the second time after the initial call has finished running', async () => {
|
||||
let running = false;
|
||||
const start = jest.fn(async () => {
|
||||
expect(running).toBe(false);
|
||||
|
||||
running = true;
|
||||
await wait(100);
|
||||
running = false;
|
||||
});
|
||||
const stop = jest.fn();
|
||||
|
||||
const instance = new ServiceStarter(start, stop);
|
||||
|
||||
void instance.start();
|
||||
void instance.start();
|
||||
|
||||
await instance.wait();
|
||||
|
||||
expect(start).toHaveBeenCalledTimes(2);
|
||||
expect(stop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should chain up to two calls to .start', async () => {
|
||||
const start = jest.fn(async () => {
|
||||
await wait(100);
|
||||
});
|
||||
const stop = jest.fn();
|
||||
|
||||
const instance = new ServiceStarter(start, stop);
|
||||
|
||||
void instance.start();
|
||||
void instance.start();
|
||||
void instance.start();
|
||||
void instance.start();
|
||||
|
||||
await instance.wait();
|
||||
|
||||
expect(start).toHaveBeenCalledTimes(2);
|
||||
expect(stop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should skip the chained calls to .start if .stop is called', async () => {
|
||||
const start = jest.fn(async () => {
|
||||
await wait(100);
|
||||
});
|
||||
const stop = jest.fn();
|
||||
|
||||
const instance = new ServiceStarter(start, stop);
|
||||
|
||||
void instance.start();
|
||||
void instance.start();
|
||||
void instance.start();
|
||||
void instance.stop();
|
||||
|
||||
await instance.wait();
|
||||
|
||||
expect(start).toHaveBeenCalledTimes(1);
|
||||
expect(stop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
9
packages/core-services/tsconfig.json
Normal file
9
packages/core-services/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@rocket.chat/tsconfig/server.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user