♻️Removed unused websockets
This commit is contained in:
@@ -25,14 +25,6 @@
|
|||||||
"root": "STATIC_ROOT"
|
"root": "STATIC_ROOT"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"websocket": {
|
|
||||||
"auth": {
|
|
||||||
"jwt": {
|
|
||||||
"secret": "AUTH_JWT_SECRET",
|
|
||||||
"expiration": "AUTH_JWT_EXPIRATION"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tracker": {
|
"tracker": {
|
||||||
"segment": {
|
"segment": {
|
||||||
"key": "SEGMENT_SECRET_KEY"
|
"key": "SEGMENT_SECRET_KEY"
|
||||||
|
|||||||
@@ -55,21 +55,6 @@
|
|||||||
"root": "./public"
|
"root": "./public"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"websocket": {
|
|
||||||
"path": "/socket/",
|
|
||||||
"adapters": {
|
|
||||||
"types": [],
|
|
||||||
"redis": {
|
|
||||||
"host": "redis",
|
|
||||||
"port": 6379
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"auth": {
|
|
||||||
"jwt": {
|
|
||||||
"secret": "supersecret"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"auth": {
|
"auth": {
|
||||||
"jwt": {
|
"jwt": {
|
||||||
"secret": "supersecret",
|
"secret": "supersecret",
|
||||||
@@ -244,12 +229,10 @@
|
|||||||
"push",
|
"push",
|
||||||
"storage",
|
"storage",
|
||||||
"webserver",
|
"webserver",
|
||||||
"websocket",
|
|
||||||
"database",
|
"database",
|
||||||
"cron",
|
"cron",
|
||||||
"search",
|
"search",
|
||||||
"message-queue",
|
"message-queue",
|
||||||
"realtime",
|
|
||||||
"tracker",
|
"tracker",
|
||||||
"general",
|
"general",
|
||||||
"user",
|
"user",
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const services = [
|
|||||||
"applications",
|
"applications",
|
||||||
"statistics",
|
"statistics",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"push",
|
"push",
|
||||||
"platform-services",
|
"platform-services",
|
||||||
"user",
|
"user",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export * from "./consumes";
|
export * from "./consumes";
|
||||||
export * from "./prefix";
|
export * from "./prefix";
|
||||||
export * from "./service-name";
|
export * from "./service-name";
|
||||||
export * from "./realtime";
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import { CreateResult } from "../../api/crud-service";
|
|
||||||
import { RealtimeEntityEvent, RealtimeEntityActionType } from "../../../services/realtime/types";
|
|
||||||
import { websocketEventBus } from "../../../services/realtime/bus";
|
|
||||||
import { getRealtimeRecipients, getRoom, RealtimeRecipients } from ".";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param path the path to push the notification to
|
|
||||||
* @param resourcePath the path of the resource itself
|
|
||||||
*/
|
|
||||||
export function RealtimeCreated<T>(recipients: RealtimeRecipients<T>): MethodDecorator {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {
|
|
||||||
const originalMethod = descriptor.value;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
descriptor.value = async function (...args: any[]) {
|
|
||||||
const result: CreateResult<T> = await originalMethod.apply(this, args);
|
|
||||||
// context should always be the last arg
|
|
||||||
const context = args && args[args.length - 1];
|
|
||||||
|
|
||||||
if (!(result instanceof CreateResult)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRealtimeRecipients(recipients, result.entity, context).forEach(
|
|
||||||
({ room, path, resource }) => {
|
|
||||||
websocketEventBus.publish<T>(RealtimeEntityActionType.Created, {
|
|
||||||
type: result.type,
|
|
||||||
room: getRoom(room, result, context),
|
|
||||||
resourcePath: path || "/",
|
|
||||||
entity: resource,
|
|
||||||
result,
|
|
||||||
} as RealtimeEntityEvent<T>);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { getRealtimeRecipients, getRoom, RealtimeRecipients } from "..";
|
|
||||||
import { DeleteResult } from "../../api/crud-service";
|
|
||||||
import { RealtimeEntityEvent, RealtimeEntityActionType } from "../../../services/realtime/types";
|
|
||||||
import { websocketEventBus } from "../../../services/realtime/bus";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param path the path to push the notification to
|
|
||||||
* @param resourcePath the path of the resource itself
|
|
||||||
*/
|
|
||||||
export function RealtimeDeleted<T>(recipients: RealtimeRecipients<T>): MethodDecorator {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {
|
|
||||||
const originalMethod = descriptor.value;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
descriptor.value = async function (...args: any[]) {
|
|
||||||
const result: DeleteResult<T> = await originalMethod.apply(this, args);
|
|
||||||
const context = args && args[args.length - 1];
|
|
||||||
|
|
||||||
if (!(result instanceof DeleteResult)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.deleted)
|
|
||||||
getRealtimeRecipients(recipients, result.entity, context).forEach(
|
|
||||||
({ room, path, resource }) => {
|
|
||||||
websocketEventBus.publish<T>(RealtimeEntityActionType.Deleted, {
|
|
||||||
type: result.type,
|
|
||||||
room: getRoom(room, result, context),
|
|
||||||
resourcePath: path,
|
|
||||||
entity: resource,
|
|
||||||
result,
|
|
||||||
} as RealtimeEntityEvent<T>);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { ResourcePath } from "../../../../../core/platform/services/realtime/types";
|
|
||||||
import { EntityTarget, ExecutionContext } from "../../api/crud-service";
|
|
||||||
|
|
||||||
export * from "./created";
|
|
||||||
export * from "./deleted";
|
|
||||||
export * from "./updated";
|
|
||||||
export * from "./saved";
|
|
||||||
|
|
||||||
export type RealtimeRecipients<T> =
|
|
||||||
| RealtimeRecipient<T>
|
|
||||||
| RealtimeRecipient<T>[]
|
|
||||||
| ((type: T, context?: ExecutionContext) => RealtimeRecipient<T>[] | RealtimeRecipient<T>);
|
|
||||||
|
|
||||||
export type RealtimeRecipient<T> = {
|
|
||||||
room: RealtimePath<T>;
|
|
||||||
path?: string;
|
|
||||||
resource?: any | T;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getRealtimeRecipients<T>(
|
|
||||||
recipients: RealtimeRecipients<T>,
|
|
||||||
type: T,
|
|
||||||
context?: ExecutionContext,
|
|
||||||
): RealtimeRecipient<T>[] {
|
|
||||||
if (typeof recipients === "function") recipients = recipients(type, context);
|
|
||||||
if (!recipients) return [];
|
|
||||||
if (!(recipients as RealtimeRecipient<T>[]).length)
|
|
||||||
recipients = [recipients as RealtimeRecipient<T>];
|
|
||||||
|
|
||||||
return (recipients as RealtimeRecipient<T>[]).map(recipient => {
|
|
||||||
return {
|
|
||||||
resource: recipient.resource || type,
|
|
||||||
path: recipient.path || "/",
|
|
||||||
room: recipient.room,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RealtimePath<T> = string | ResourcePath | ResourcePathResolver<T>;
|
|
||||||
export interface ResourcePathResolver<T> {
|
|
||||||
(type: T, context?: ExecutionContext): ResourcePath;
|
|
||||||
}
|
|
||||||
export interface PathResolver<T> {
|
|
||||||
(type: T, context?: ExecutionContext): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RealtimeEntity<T> = null | ((type: T, context?: ExecutionContext) => T);
|
|
||||||
|
|
||||||
export function getRoom<T>(
|
|
||||||
path: RealtimePath<T> = ResourcePath.default(),
|
|
||||||
entity: EntityTarget<T>,
|
|
||||||
context: ExecutionContext,
|
|
||||||
): ResourcePath {
|
|
||||||
if (typeof path === "string") {
|
|
||||||
return ResourcePath.get(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return typeof path === "function" ? path(entity.entity, context) : path;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPath<T>(
|
|
||||||
path: string | PathResolver<T> = "/",
|
|
||||||
entity: EntityTarget<T>,
|
|
||||||
context: ExecutionContext,
|
|
||||||
): string {
|
|
||||||
return typeof path === "function" ? path(entity.entity, context) : path;
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { getRealtimeRecipients, getRoom, RealtimeRecipients } from "..";
|
|
||||||
import { SaveResult } from "../../api/crud-service";
|
|
||||||
import { RealtimeEntityEvent, RealtimeEntityActionType } from "../../../services/realtime/types";
|
|
||||||
import { websocketEventBus } from "../../../services/realtime/bus";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param path the path to push the notification to
|
|
||||||
* @param resourcePath the path of the resource itself
|
|
||||||
*/
|
|
||||||
export function RealtimeSaved<T>(recipients: RealtimeRecipients<T>): MethodDecorator {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {
|
|
||||||
const originalMethod = descriptor.value;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
descriptor.value = async function (...args: any[]) {
|
|
||||||
const result: SaveResult<T> = await originalMethod.apply(this, args);
|
|
||||||
// context should always be the last arg
|
|
||||||
const context = args && args[args.length - 1];
|
|
||||||
|
|
||||||
if (!(result instanceof SaveResult)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRealtimeRecipients(recipients, result.entity, context).forEach(
|
|
||||||
({ room, path, resource }) => {
|
|
||||||
websocketEventBus.publish<T>(RealtimeEntityActionType.Saved, {
|
|
||||||
type: result.type,
|
|
||||||
room: getRoom(room, result, context),
|
|
||||||
resourcePath: path,
|
|
||||||
entity: resource,
|
|
||||||
result,
|
|
||||||
} as RealtimeEntityEvent<T>);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { getRealtimeRecipients, getRoom, RealtimeRecipients } from "..";
|
|
||||||
import { UpdateResult } from "../../api/crud-service";
|
|
||||||
import { RealtimeEntityEvent, RealtimeEntityActionType } from "../../../services/realtime/types";
|
|
||||||
import { websocketEventBus } from "../../../services/realtime/bus";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param path the path to push the notification to
|
|
||||||
* @param resourcePath the path of the resource itself
|
|
||||||
*/
|
|
||||||
export function RealtimeUpdated<T>(recipients: RealtimeRecipients<T>): MethodDecorator {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor): void {
|
|
||||||
const originalMethod = descriptor.value;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
descriptor.value = async function (...args: any[]) {
|
|
||||||
const result: UpdateResult<T> = await originalMethod.apply(this, args);
|
|
||||||
const context = args && args[args.length - 1];
|
|
||||||
|
|
||||||
if (!(result instanceof UpdateResult)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRealtimeRecipients(recipients, result.entity, context).forEach(
|
|
||||||
({ room, path, resource }) => {
|
|
||||||
websocketEventBus.publish<T>(RealtimeEntityActionType.Updated, {
|
|
||||||
type: result.type,
|
|
||||||
room: getRoom(room, result, context),
|
|
||||||
resourcePath: path,
|
|
||||||
entity: resource,
|
|
||||||
result,
|
|
||||||
} as RealtimeEntityEvent<T>);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import { TdriveServiceProvider } from "../../framework";
|
|
||||||
import { WebSocketUser, WebSocket } from "../../services/websocket/types";
|
|
||||||
import RealtimeEntityManager from "./services/entity-manager";
|
|
||||||
import { RealtimeEntityEvent } from "./types";
|
|
||||||
|
|
||||||
export interface RealtimeServiceAPI extends TdriveServiceProvider {
|
|
||||||
/**
|
|
||||||
* Get the realtime event bus instance
|
|
||||||
*/
|
|
||||||
getBus(): RealtimeEventBus;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the room manager
|
|
||||||
*/
|
|
||||||
getRoomManager(): RealtimeRoomManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the entity manager
|
|
||||||
*/
|
|
||||||
getEntityManager(): RealtimeEntityManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a token for a room
|
|
||||||
*/
|
|
||||||
sign(items: { room: string }[], userId: string): { room: string; token: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RealtimeRoomManager {
|
|
||||||
/**
|
|
||||||
* Get the number of connected users in the given room
|
|
||||||
* @param room name of the room
|
|
||||||
*/
|
|
||||||
getConnectedUsers(room: string): number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the user to a room
|
|
||||||
*
|
|
||||||
* @param websocket
|
|
||||||
* @param room
|
|
||||||
* @param user
|
|
||||||
*/
|
|
||||||
join(websocket: WebSocket, room: string, user: WebSocketUser): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the user from a room
|
|
||||||
*
|
|
||||||
* @param websocket
|
|
||||||
* @param room
|
|
||||||
* @param user
|
|
||||||
*/
|
|
||||||
leave(websocket: WebSocket, room: string, user: WebSocketUser): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a user from all the room he joined
|
|
||||||
*
|
|
||||||
* @param websocket
|
|
||||||
* @param user
|
|
||||||
*/
|
|
||||||
leaveAll(websocket: WebSocket, user: WebSocketUser): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RealtimeEventBus {
|
|
||||||
/**
|
|
||||||
* Subscribes to a topic
|
|
||||||
*
|
|
||||||
* @param topic Topic to subscribe to
|
|
||||||
* @param listener Listener to call when event is published in the given topic
|
|
||||||
*/
|
|
||||||
subscribe<Entity>(topic: string, listener: (event: RealtimeEntityEvent<Entity>) => void): this;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publish event in a topic
|
|
||||||
*
|
|
||||||
* @param topic Topic to publish event to
|
|
||||||
* @param event Event to publish
|
|
||||||
*/
|
|
||||||
publish<Entity>(topic: string, event: RealtimeEntityEvent<Entity>): boolean;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* EventBus is used internally to exchange events between decorators and realtime manager
|
|
||||||
*/
|
|
||||||
import { EventEmitter } from "events";
|
|
||||||
import { RealtimeEventBus } from "./api";
|
|
||||||
import { RealtimeEntityEvent } from "./types";
|
|
||||||
|
|
||||||
class WebsocketEventBus extends EventEmitter implements RealtimeEventBus {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe<Entity>(topic: string, listener: (event: RealtimeEntityEvent<Entity>) => void): this {
|
|
||||||
return this.on(topic, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
publish<Entity>(topic: string, event: RealtimeEntityEvent<Entity>): boolean {
|
|
||||||
return this.emit(topic, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const websocketEventBus = new WebsocketEventBus();
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { Consumes, ServiceName, TdriveService } from "../../framework";
|
|
||||||
import { SkipCLI } from "../../framework/decorators/skip";
|
|
||||||
import { localEventBus } from "../../framework/event-bus";
|
|
||||||
import WebSocketAPI from "../../services/websocket/provider";
|
|
||||||
import AuthService from "../auth/provider";
|
|
||||||
import { RealtimeEventBus, RealtimeRoomManager, RealtimeServiceAPI } from "./api";
|
|
||||||
import { websocketEventBus } from "./bus";
|
|
||||||
import RealtimeEntityManager from "./services/entity-manager";
|
|
||||||
import RoomManagerImpl from "./services/room-manager";
|
|
||||||
import { RealtimeBaseBusEvent, RealtimeLocalBusEvent } from "./types";
|
|
||||||
|
|
||||||
@Consumes(["websocket", "auth"])
|
|
||||||
@ServiceName("realtime")
|
|
||||||
export default class RealtimeService
|
|
||||||
extends TdriveService<RealtimeServiceAPI>
|
|
||||||
implements RealtimeServiceAPI
|
|
||||||
{
|
|
||||||
private roomManager: RoomManagerImpl;
|
|
||||||
private entityManager: RealtimeEntityManager;
|
|
||||||
private auth: AuthService;
|
|
||||||
version = "1";
|
|
||||||
|
|
||||||
api(): RealtimeServiceAPI {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SkipCLI()
|
|
||||||
async doStart(): Promise<this> {
|
|
||||||
const ws = this.context.getProvider<WebSocketAPI>("websocket");
|
|
||||||
this.auth = this.context.getProvider<AuthService>("auth");
|
|
||||||
|
|
||||||
this.roomManager = new RoomManagerImpl(ws, this.auth);
|
|
||||||
this.roomManager.init();
|
|
||||||
this.entityManager = new RealtimeEntityManager(ws);
|
|
||||||
this.entityManager.init();
|
|
||||||
|
|
||||||
localEventBus.subscribe("realtime:event", (event: RealtimeBaseBusEvent<any>) => {
|
|
||||||
event.data._type = event.type;
|
|
||||||
ws.getIo().to(event.room).emit("realtime:event", { name: event.room, data: event.data });
|
|
||||||
});
|
|
||||||
|
|
||||||
localEventBus.subscribe("realtime:publish", (data: RealtimeLocalBusEvent<any>) => {
|
|
||||||
this.getBus().publish(data.topic, data.event);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
getBus(): RealtimeEventBus {
|
|
||||||
return websocketEventBus;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRoomManager(): RealtimeRoomManager {
|
|
||||||
return this.roomManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEntityManager(): RealtimeEntityManager {
|
|
||||||
return this.entityManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
sign(items: { room: string }[], sub: string) {
|
|
||||||
return items.map(item => {
|
|
||||||
const token = this.auth.sign({
|
|
||||||
sub,
|
|
||||||
name: item.room,
|
|
||||||
//Fixme, reduce this number but also in frontend reload the websocket token after this ends
|
|
||||||
iat: Math.round(new Date().getTime() / 1000) + 60 * 60 * 24 * 31,
|
|
||||||
nbf: Math.round(new Date().getTime() / 1000) - 60,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
token,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { logger } from "../../../framework/logger";
|
|
||||||
import { RealtimeEntityActionType, RealtimeEntityEvent } from "../types";
|
|
||||||
import WebSocketAPI from "../../../services/websocket/provider";
|
|
||||||
import { websocketEventBus } from "../bus";
|
|
||||||
|
|
||||||
const REALTIME_RESOURCE = "realtime:resource";
|
|
||||||
|
|
||||||
export default class RealtimeEntityManager {
|
|
||||||
constructor(private ws: WebSocketAPI) {}
|
|
||||||
|
|
||||||
init(): void {
|
|
||||||
websocketEventBus.subscribe(RealtimeEntityActionType.Created, event => {
|
|
||||||
this.pushResourceEvent(event, RealtimeEntityActionType.Created);
|
|
||||||
});
|
|
||||||
|
|
||||||
websocketEventBus.subscribe(RealtimeEntityActionType.Updated, event => {
|
|
||||||
this.pushResourceEvent(event, RealtimeEntityActionType.Updated);
|
|
||||||
});
|
|
||||||
|
|
||||||
websocketEventBus.subscribe(RealtimeEntityActionType.Deleted, event => {
|
|
||||||
this.pushResourceEvent(event, RealtimeEntityActionType.Deleted);
|
|
||||||
});
|
|
||||||
|
|
||||||
websocketEventBus.subscribe(RealtimeEntityActionType.Saved, event => {
|
|
||||||
this.pushResourceEvent(event, RealtimeEntityActionType.Saved);
|
|
||||||
});
|
|
||||||
|
|
||||||
websocketEventBus.subscribe(RealtimeEntityActionType.Event, event => {
|
|
||||||
this.pushResourceEvent(event, RealtimeEntityActionType.Event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private pushResourceEvent(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
event: RealtimeEntityEvent<any>,
|
|
||||||
action: RealtimeEntityActionType,
|
|
||||||
): void {
|
|
||||||
event.room.path.forEach(path => {
|
|
||||||
logger.info(`Pushing ${action} entity to room ${path}`);
|
|
||||||
const message: unknown = {
|
|
||||||
action,
|
|
||||||
room: path,
|
|
||||||
type: event.type,
|
|
||||||
path: event.resourcePath,
|
|
||||||
resource: event.entity,
|
|
||||||
};
|
|
||||||
if (logger.isLevelEnabled("debug")) {
|
|
||||||
logger.debug(`Entity to push to room ${path}: %o`, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.getIo().to(path).emit(REALTIME_RESOURCE, message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./entity-manager";
|
|
||||||
export * from "./room-manager";
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
import { logger } from "../../../framework/logger";
|
|
||||||
import {
|
|
||||||
JoinLeaveRoomError,
|
|
||||||
JoinLeaveRoomSuccess,
|
|
||||||
JoinRoomEvent,
|
|
||||||
LeaveRoomEvent,
|
|
||||||
ClientEvent,
|
|
||||||
WebsocketRoomSignature,
|
|
||||||
} from "../types";
|
|
||||||
import { RealtimeRoomManager } from "../api";
|
|
||||||
import WebSocketAPI from "../../../services/websocket/provider";
|
|
||||||
import { WebSocketUser, WebSocket } from "../../../services/websocket/types";
|
|
||||||
import AuthService from "../../auth/provider";
|
|
||||||
|
|
||||||
export default class RoomManager implements RealtimeRoomManager {
|
|
||||||
constructor(private ws: WebSocketAPI, private auth: AuthService) {}
|
|
||||||
|
|
||||||
init(): void {
|
|
||||||
this.ws.onUserConnected(event => {
|
|
||||||
logger.info(`User ${event.user.id} is connected`);
|
|
||||||
|
|
||||||
event.socket.on("realtime:join", async (joinEvent: JoinRoomEvent) => {
|
|
||||||
const canJoin =
|
|
||||||
joinEvent.name.indexOf("previous::") === 0 || //Compatibility with old collections
|
|
||||||
(await this.userCanJoinRoom(event.user, joinEvent));
|
|
||||||
|
|
||||||
if (canJoin) {
|
|
||||||
this.join(event.socket, joinEvent.name, event.user);
|
|
||||||
} else {
|
|
||||||
this.sendError("join", event.socket, {
|
|
||||||
name: joinEvent.name,
|
|
||||||
message: "User is not authorized to join room",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
event.socket.on("realtime:leave", (leaveEvent: LeaveRoomEvent) => {
|
|
||||||
this.leave(event.socket, leaveEvent.name, event.user);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.socket.on("realtime:event", async (clientEvent: ClientEvent) => {
|
|
||||||
const canEmit =
|
|
||||||
clientEvent.name.indexOf("previous::") === 0 || //Compatibility with old collections
|
|
||||||
(await this.userCanEmitInRoom(event.user, clientEvent));
|
|
||||||
if (canEmit) {
|
|
||||||
this.sendEvent(clientEvent.name, clientEvent.data);
|
|
||||||
} else {
|
|
||||||
this.sendError("event", event.socket, {
|
|
||||||
name: clientEvent.name,
|
|
||||||
message: "User is not authorized to emit in this room",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.ws.onUserDisconnected(event => {
|
|
||||||
logger.info(`User ${event.user.id} is disconnected`);
|
|
||||||
this.leaveAll(event.socket, event.user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getConnectedUsers(room: string): number {
|
|
||||||
if (this.ws.getIo().sockets.adapter.rooms.has(room)) {
|
|
||||||
return this.ws.getIo().sockets.adapter.rooms.get(room).entries.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check of the user can join room
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* @param joinEvent
|
|
||||||
* @return Promise<boolean> true if can join, false otherwise. Never rejects.
|
|
||||||
*/
|
|
||||||
async userCanJoinRoom(user: WebSocketUser, joinEvent: JoinRoomEvent): Promise<boolean> {
|
|
||||||
logger.info(`Checking if user ${user.id} can join room ${joinEvent.name} with token`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
//Public rooms we just check the user is logged in
|
|
||||||
if (
|
|
||||||
joinEvent.name.startsWith("/users/") || //User update room
|
|
||||||
joinEvent.name === "/ping"
|
|
||||||
) {
|
|
||||||
return !!this.auth.verifyToken(joinEvent.token)?.sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Retro-compatibility for mobile up to february 2021 (to remove after this date)
|
|
||||||
if (joinEvent.token === "tdrive") return true;
|
|
||||||
|
|
||||||
const signature = this.auth.verifyTokenObject<WebsocketRoomSignature>(joinEvent.token);
|
|
||||||
|
|
||||||
return (
|
|
||||||
signature &&
|
|
||||||
signature.name === joinEvent.name &&
|
|
||||||
signature.sub === user.id &&
|
|
||||||
signature.iat > Math.round(new Date().getTime() / 1000)
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userCanEmitInRoom = this.userCanJoinRoom;
|
|
||||||
|
|
||||||
async join(websocket: WebSocket, room: string, user: WebSocketUser): Promise<void> {
|
|
||||||
logger.info(`User ${user.id} is joining room ${room}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await websocket.join(room);
|
|
||||||
} catch (err) {
|
|
||||||
if (err) {
|
|
||||||
logger.error(`Error while joining room ${room}`, err);
|
|
||||||
this.sendError("join", websocket, {
|
|
||||||
name: room,
|
|
||||||
message: "Error while joining room",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendSuccess("join", websocket, { name: room });
|
|
||||||
logger.info(`User ${user.id} joined room ${room}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async leave(websocket: WebSocket, room: string, user: WebSocketUser): Promise<void> {
|
|
||||||
logger.info(`User ${user.id} is leaving room ${room}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await websocket.leave(room);
|
|
||||||
} catch (err) {
|
|
||||||
if (err) {
|
|
||||||
logger.error(`Error while leaving room ${room}`, err);
|
|
||||||
this.sendError("leave", websocket, {
|
|
||||||
name: room,
|
|
||||||
message: "Error while leaving room",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendSuccess("leave", websocket, { name: room });
|
|
||||||
logger.info(`User ${user.id} left room ${room}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
leaveAll(websocket: WebSocket, user: WebSocketUser): void {
|
|
||||||
logger.info(`Leaving rooms for user ${user.id}`);
|
|
||||||
websocket.rooms.forEach(room => websocket.leave(room));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendError(event: string, websocket: WebSocket, error: JoinLeaveRoomError): void {
|
|
||||||
websocket.emit(`realtime:${event}:error`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendSuccess(event: string, websocket: WebSocket, success: JoinLeaveRoomSuccess): void {
|
|
||||||
websocket.emit(`realtime:${event}:success`, success);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent(path: string, data: any): void {
|
|
||||||
this.ws.getIo().to(path).emit("realtime:event", { name: path, data: data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { EntityOperationResult } from "../../framework/api/crud-service";
|
|
||||||
|
|
||||||
export enum RealtimeEntityActionType {
|
|
||||||
Created = "created",
|
|
||||||
Saved = "saved",
|
|
||||||
Updated = "updated",
|
|
||||||
Deleted = "deleted",
|
|
||||||
Event = "event",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebsocketRoomSignature {
|
|
||||||
name: string; //Ws room name
|
|
||||||
sub: string; //User id
|
|
||||||
iat: number; //Deadline
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ResourcePath {
|
|
||||||
constructor(readonly path: string[] = ["/"], readonly name: string = "default") {}
|
|
||||||
|
|
||||||
static default(): ResourcePath {
|
|
||||||
return new ResourcePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get(path: string | string[]): ResourcePath {
|
|
||||||
return new ResourcePath(typeof path === "string" ? [path] : path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class RealtimeEntityEvent<Entity> {
|
|
||||||
// the type of the resource
|
|
||||||
type: string;
|
|
||||||
// the room to push the resource to
|
|
||||||
room: ResourcePath;
|
|
||||||
// the unique full path of the resource, ie where we can get it
|
|
||||||
resourcePath: string | null;
|
|
||||||
// the input resource
|
|
||||||
entity: Entity;
|
|
||||||
// the action result which fired this event
|
|
||||||
result: EntityOperationResult<Entity> | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RealtimeApplicationEvent {
|
|
||||||
action: "configure" | "close_configure";
|
|
||||||
application: unknown;
|
|
||||||
form: unknown;
|
|
||||||
hidden_data: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RealtimeBaseBusEvent<T> {
|
|
||||||
room: string;
|
|
||||||
type: string;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RealtimeLocalBusEvent<Entity> {
|
|
||||||
event: RealtimeEntityEvent<Entity>;
|
|
||||||
topic: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class JoinRoomEvent {
|
|
||||||
name: string;
|
|
||||||
token?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LeaveRoomEvent {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ClientEvent {
|
|
||||||
name: string;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JoinLeaveRoomError {
|
|
||||||
name: string;
|
|
||||||
message?: string;
|
|
||||||
type?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JoinLeaveRoomSuccess {
|
|
||||||
name: string;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { Consumes, ServiceName, TdriveService } from "../../framework";
|
|
||||||
import WebServerAPI from "../webserver/provider";
|
|
||||||
import WebSocketAPI from "./provider";
|
|
||||||
import { WebSocketService } from "./services";
|
|
||||||
import { AdaptersConfiguration } from "./types";
|
|
||||||
import FastifyIO from "fastify-socket.io";
|
|
||||||
|
|
||||||
@Consumes(["webserver"])
|
|
||||||
@ServiceName("websocket")
|
|
||||||
export default class WebSocket extends TdriveService<WebSocketAPI> {
|
|
||||||
private service: WebSocketService;
|
|
||||||
name = "websocket";
|
|
||||||
version = "1";
|
|
||||||
|
|
||||||
api(): WebSocketAPI {
|
|
||||||
return this.service;
|
|
||||||
}
|
|
||||||
|
|
||||||
async doInit(): Promise<this> {
|
|
||||||
const webserver = this.context.getProvider<WebServerAPI>("webserver");
|
|
||||||
const fastify = webserver.getServer();
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
path: this.configuration.get<string>("path", "/socket"),
|
|
||||||
};
|
|
||||||
|
|
||||||
fastify.register(FastifyIO, {
|
|
||||||
...options,
|
|
||||||
allowEIO3: true,
|
|
||||||
cors: {
|
|
||||||
//Allow all origins
|
|
||||||
origin: (origin, callback) => callback(null, origin),
|
|
||||||
credentials: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.service = new WebSocketService({
|
|
||||||
server: fastify,
|
|
||||||
options,
|
|
||||||
ready: webserver.onReady.bind(webserver),
|
|
||||||
adapters: this.configuration.get<AdaptersConfiguration>("adapters"),
|
|
||||||
auth: this.configuration.get<{ secret: string }>("auth.jwt"),
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { EventEmitter } from "events";
|
|
||||||
import socketIO from "socket.io";
|
|
||||||
import { TdriveServiceProvider } from "../../framework";
|
|
||||||
import { User } from "../../../../utils/types";
|
|
||||||
import { WebsocketUserEvent, WebSocket, WebSocketUser } from "./types";
|
|
||||||
|
|
||||||
export default interface WebSocketAPI extends TdriveServiceProvider, EventEmitter {
|
|
||||||
getIo(): socketIO.Server;
|
|
||||||
|
|
||||||
isConnected(user: User): boolean;
|
|
||||||
|
|
||||||
onUserConnected(listener: (event: WebsocketUserEvent) => void): this;
|
|
||||||
|
|
||||||
onUserDisconnected(listener: (event: WebsocketUserEvent) => void): this;
|
|
||||||
|
|
||||||
getUser(websocket: WebSocket): WebSocketUser;
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import socketIO from "socket.io";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const { createClient } = require("redis");
|
|
||||||
import SocketIORedis from "@socket.io/redis-adapter";
|
|
||||||
import jwt from "jsonwebtoken";
|
|
||||||
import WebSocketAPI from "../provider";
|
|
||||||
import {
|
|
||||||
WebSocket,
|
|
||||||
WebSocketServiceConfiguration,
|
|
||||||
WebSocketUser,
|
|
||||||
WebsocketUserEvent,
|
|
||||||
} from "../types";
|
|
||||||
import { EventEmitter } from "events";
|
|
||||||
import { User } from "../../../../../utils/types";
|
|
||||||
import { JwtType } from "../../types";
|
|
||||||
|
|
||||||
export class WebSocketService extends EventEmitter implements WebSocketAPI {
|
|
||||||
version: "1";
|
|
||||||
private io: socketIO.Server;
|
|
||||||
|
|
||||||
constructor(serviceConfiguration: WebSocketServiceConfiguration) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
serviceConfiguration.ready(() => {
|
|
||||||
this.io = serviceConfiguration.server.io;
|
|
||||||
|
|
||||||
if (serviceConfiguration.adapters?.types?.includes("redis")) {
|
|
||||||
const pubClient = createClient(serviceConfiguration.adapters.redis);
|
|
||||||
const subClient = pubClient.duplicate();
|
|
||||||
this.io.adapter(SocketIORedis.createAdapter(pubClient, subClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.io.on("connection", socket => {
|
|
||||||
socket.on("authenticate", message => {
|
|
||||||
if (message.token) {
|
|
||||||
jwt.verify(
|
|
||||||
message.token as string,
|
|
||||||
serviceConfiguration.auth.secret as string,
|
|
||||||
(err, decoded) => {
|
|
||||||
if (err) {
|
|
||||||
socket.emit("unauthorized", { err });
|
|
||||||
socket.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
(socket as unknown as WebSocket).decoded_token = decoded as JwtType;
|
|
||||||
const user = this.getUser(socket as WebSocket);
|
|
||||||
|
|
||||||
socket.emit("authenticated");
|
|
||||||
|
|
||||||
socket.on("message", message => {
|
|
||||||
this.io.emit("message", message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.emit("user:connected", {
|
|
||||||
user,
|
|
||||||
socket,
|
|
||||||
event: "user:connected",
|
|
||||||
} as WebsocketUserEvent);
|
|
||||||
|
|
||||||
socket.on("disconnect", () =>
|
|
||||||
this.emit("user:disconnected", {
|
|
||||||
user,
|
|
||||||
socket,
|
|
||||||
event: "user:disconnected",
|
|
||||||
} as WebsocketUserEvent),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
socket.emit("unauthorized", { err: "No token provided" });
|
|
||||||
socket.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onUserConnected(listener: (event: WebsocketUserEvent) => void): this {
|
|
||||||
return this.on("user:connected", listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
onUserDisconnected(listener: (event: WebsocketUserEvent) => void): this {
|
|
||||||
return this.on("user:disconnected", listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser(socket: WebSocket): WebSocketUser {
|
|
||||||
return {
|
|
||||||
id: socket.decoded_token.sub,
|
|
||||||
identity_provider_id: socket.decoded_token.provider_id,
|
|
||||||
email: socket.decoded_token.email,
|
|
||||||
token: socket.decoded_token,
|
|
||||||
allow_tracking: socket.decoded_token.track,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getIo(): socketIO.Server {
|
|
||||||
return this.io;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
isConnected(user: User): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import socketIO, { Socket } from "socket.io";
|
|
||||||
import { RedisAdapterOptions } from "@socket.io/redis-adapter";
|
|
||||||
import { User } from "../../../../utils/types";
|
|
||||||
import { JwtType } from "../types";
|
|
||||||
import { FastifyInstance } from "fastify";
|
|
||||||
|
|
||||||
export interface AdaptersConfiguration {
|
|
||||||
types: Array<string>;
|
|
||||||
redis: RedisAdapterOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebSocketServiceConfiguration {
|
|
||||||
server: FastifyInstance;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
ready: Function;
|
|
||||||
options?: socketIO.ServerOptions | { path: string };
|
|
||||||
adapters?: AdaptersConfiguration;
|
|
||||||
auth?: { secret: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebSocketUser extends User {
|
|
||||||
token?: DecodedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebSockets {
|
|
||||||
[index: string]: WebSocket[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebSocket extends Socket {
|
|
||||||
decoded_token: DecodedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebsocketUserEvent {
|
|
||||||
event: string;
|
|
||||||
socket: WebSocket;
|
|
||||||
user: WebSocketUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
type DecodedToken = JwtType;
|
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { CrudException } from "../../../../core/platform/framework/api/crud-service";
|
import { CrudException } from "../../../../core/platform/framework/api/crud-service";
|
||||||
import { localEventBus } from "../../../../core/platform/framework/event-bus";
|
|
||||||
import {
|
|
||||||
RealtimeApplicationEvent,
|
|
||||||
RealtimeBaseBusEvent,
|
|
||||||
} from "../../../../core/platform/services/realtime/types";
|
|
||||||
import { ResourceGetResponse } from "../../../../utils/types";
|
import { ResourceGetResponse } from "../../../../utils/types";
|
||||||
import {
|
import {
|
||||||
ApplicationObject,
|
ApplicationObject,
|
||||||
@@ -84,26 +78,6 @@ export class ApplicationsApiController {
|
|||||||
throw CrudException.forbidden("Application not found");
|
throw CrudException.forbidden("Application not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = request.body;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
action: "configure",
|
|
||||||
application: {
|
|
||||||
id: app_id,
|
|
||||||
identity: application.identity,
|
|
||||||
},
|
|
||||||
form: body.form,
|
|
||||||
connection_id: body.connection_id,
|
|
||||||
hidden_data: {},
|
|
||||||
configurator_id: v4(),
|
|
||||||
};
|
|
||||||
|
|
||||||
localEventBus.publish("realtime:event", {
|
|
||||||
room: "/me/" + body.user_id,
|
|
||||||
type: "application",
|
|
||||||
data,
|
|
||||||
} as RealtimeBaseBusEvent<RealtimeApplicationEvent>);
|
|
||||||
|
|
||||||
return { status: "ok" };
|
return { status: "ok" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { WebsocketMetadata } from "../../utils/types";
|
|
||||||
|
|
||||||
export function getCompanyApplicationRooms(companyId: string): WebsocketMetadata[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getCompanyApplicationRoom(companyId),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCompanyApplicationRoom(companyApplicationId: string): string {
|
|
||||||
return `/company-application/${companyApplicationId}`;
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import { PublicApplicationObject } from "../../entities/application";
|
import { PublicApplicationObject } from "../../entities/application";
|
||||||
import { CompanyExecutionContext } from "../types";
|
import { CompanyExecutionContext } from "../types";
|
||||||
import { CrudController } from "../../../../core/platform/services/webserver/types";
|
import { CrudController } from "../../../../core/platform/services/webserver/types";
|
||||||
import { getCompanyApplicationRooms } from "../../realtime";
|
|
||||||
import gr from "../../../global-resolver";
|
import gr from "../../../global-resolver";
|
||||||
|
|
||||||
export class CompanyApplicationController
|
export class CompanyApplicationController
|
||||||
@@ -55,11 +54,6 @@ export class CompanyApplicationController
|
|||||||
return {
|
return {
|
||||||
resources: resources.getEntities().map(ca => ca.application),
|
resources: resources.getEntities().map(ca => ca.application),
|
||||||
next_page_token: resources.nextPage?.page_token,
|
next_page_token: resources.nextPage?.page_token,
|
||||||
websockets:
|
|
||||||
gr.platformServices.realtime.sign(
|
|
||||||
getCompanyApplicationRooms(request.params.company_id),
|
|
||||||
context.user.id,
|
|
||||||
) || [],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,8 @@ import {
|
|||||||
makeStandaloneAccessLevel,
|
makeStandaloneAccessLevel,
|
||||||
getItemScope,
|
getItemScope,
|
||||||
} from "./access-check";
|
} from "./access-check";
|
||||||
import { websocketEventBus } from "../../../core/platform/services/realtime/bus";
|
|
||||||
import archiver from "archiver";
|
import archiver from "archiver";
|
||||||
import internal from "stream";
|
import internal from "stream";
|
||||||
import {
|
|
||||||
RealtimeEntityActionType,
|
|
||||||
ResourcePath,
|
|
||||||
} from "../../../core/platform/services/realtime/types";
|
|
||||||
import config from "config";
|
import config from "config";
|
||||||
export class DocumentsService {
|
export class DocumentsService {
|
||||||
version: "1";
|
version: "1";
|
||||||
@@ -410,9 +405,6 @@ export class DocumentsService {
|
|||||||
//TODO[ASH] update item size only for files, there is not need to do during direcotry creation
|
//TODO[ASH] update item size only for files, there is not need to do during direcotry creation
|
||||||
await updateItemSize(driveItem.parent_id, this.repository, context);
|
await updateItemSize(driveItem.parent_id, this.repository, context);
|
||||||
|
|
||||||
//TODO[ASH] there is no need to notify websocket, until we implement user notification
|
|
||||||
await this.notifyWebsocket(driveItem.parent_id, context);
|
|
||||||
|
|
||||||
await globalResolver.platformServices.messageQueue.publish<DocumentsMessageQueueRequest>(
|
await globalResolver.platformServices.messageQueue.publish<DocumentsMessageQueueRequest>(
|
||||||
"services:documents:process",
|
"services:documents:process",
|
||||||
{
|
{
|
||||||
@@ -681,11 +673,7 @@ export class DocumentsService {
|
|||||||
await this.update(item.id, item, context);
|
await this.update(item.id, item, context);
|
||||||
}
|
}
|
||||||
await updateItemSize(previousParentId, this.repository, context);
|
await updateItemSize(previousParentId, this.repository, context);
|
||||||
|
|
||||||
await this.notifyWebsocket(previousParentId, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.notifyWebsocket("trash", context);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -963,19 +951,6 @@ export class DocumentsService {
|
|||||||
return archive;
|
return archive;
|
||||||
};
|
};
|
||||||
|
|
||||||
notifyWebsocket = async (id: string, context: DriveExecutionContext) => {
|
|
||||||
websocketEventBus.publish(RealtimeEntityActionType.Event, {
|
|
||||||
type: "documents:updated",
|
|
||||||
room: ResourcePath.get(`/companies/${context.company.id}/documents/item/${id}`),
|
|
||||||
entity: {
|
|
||||||
companyId: context.company.id,
|
|
||||||
id: id,
|
|
||||||
},
|
|
||||||
resourcePath: null,
|
|
||||||
result: null,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for Drive items.
|
* Search for Drive items.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { CrudException, ListResult } from "../../../../core/platform/framework/a
|
|||||||
import { File } from "../../../../services/files/entities/file";
|
import { File } from "../../../../services/files/entities/file";
|
||||||
import { UploadOptions } from "../../../../services/files/types";
|
import { UploadOptions } from "../../../../services/files/types";
|
||||||
import globalResolver from "../../../../services/global-resolver";
|
import globalResolver from "../../../../services/global-resolver";
|
||||||
import {
|
import { CompanyUserRole, PaginationQueryParameters } from "../../../../utils/types";
|
||||||
CompanyUserRole,
|
|
||||||
PaginationQueryParameters,
|
|
||||||
ResourceWebsocket,
|
|
||||||
} from "../../../../utils/types";
|
|
||||||
import { DriveFile } from "../../entities/drive-file";
|
import { DriveFile } from "../../entities/drive-file";
|
||||||
import { FileVersion } from "../../entities/file-version";
|
import { FileVersion } from "../../entities/file-version";
|
||||||
import {
|
import {
|
||||||
@@ -160,18 +156,12 @@ export class DocumentsController {
|
|||||||
Params: ItemRequestParams;
|
Params: ItemRequestParams;
|
||||||
Querystring: PaginationQueryParameters & { public_token?: string };
|
Querystring: PaginationQueryParameters & { public_token?: string };
|
||||||
}>,
|
}>,
|
||||||
): Promise<DriveItemDetails & { websockets: ResourceWebsocket[] }> => {
|
): Promise<DriveItemDetails> => {
|
||||||
const context = getDriveExecutionContext(request);
|
const context = getDriveExecutionContext(request);
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...(await globalResolver.services.documents.documents.get(id, context)),
|
...(await globalResolver.services.documents.documents.get(id, context)),
|
||||||
websockets: request.currentUser?.id
|
|
||||||
? globalResolver.platformServices.realtime.sign(
|
|
||||||
[{ room: `/companies/${context.company.id}/documents/item/${id}` }],
|
|
||||||
request.currentUser?.id,
|
|
||||||
)
|
|
||||||
: [],
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,7 +178,7 @@ export class DocumentsController {
|
|||||||
Body: SearchDocumentsBody;
|
Body: SearchDocumentsBody;
|
||||||
Querystring: PaginationQueryParameters & { public_token?: string };
|
Querystring: PaginationQueryParameters & { public_token?: string };
|
||||||
}>,
|
}>,
|
||||||
): Promise<DriveItemDetails & { websockets: ResourceWebsocket[] }> => {
|
): Promise<DriveItemDetails> => {
|
||||||
const context = getDriveExecutionContext(request);
|
const context = getDriveExecutionContext(request);
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
|
||||||
@@ -202,7 +192,6 @@ export class DocumentsController {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...(await globalResolver.services.documents.documents.browse(id, options, context)),
|
...(await globalResolver.services.documents.documents.browse(id, options, context)),
|
||||||
websockets: [],
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ import { DatabaseServiceAPI } from "../core/platform/services/database/api";
|
|||||||
import EmailPusherAPI from "../core/platform/services/email-pusher/provider";
|
import EmailPusherAPI from "../core/platform/services/email-pusher/provider";
|
||||||
import { MessageQueueServiceAPI } from "../core/platform/services/message-queue/api";
|
import { MessageQueueServiceAPI } from "../core/platform/services/message-queue/api";
|
||||||
import { PushServiceAPI } from "../core/platform/services/push/api";
|
import { PushServiceAPI } from "../core/platform/services/push/api";
|
||||||
import { RealtimeServiceAPI } from "../core/platform/services/realtime/api";
|
|
||||||
import { SearchServiceAPI } from "../core/platform/services/search/api";
|
import { SearchServiceAPI } from "../core/platform/services/search/api";
|
||||||
import StorageAPI from "../core/platform/services/storage/provider";
|
import StorageAPI from "../core/platform/services/storage/provider";
|
||||||
import TrackerAPI from "../core/platform/services/tracker/provider";
|
import TrackerAPI from "../core/platform/services/tracker/provider";
|
||||||
import WebServerAPI from "../core/platform/services/webserver/provider";
|
import WebServerAPI from "../core/platform/services/webserver/provider";
|
||||||
import WebSocketAPI from "../core/platform/services/websocket/provider";
|
|
||||||
|
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import { logger } from "../core/platform/framework";
|
import { logger } from "../core/platform/framework";
|
||||||
@@ -42,12 +40,10 @@ type PlatformServices = {
|
|||||||
cron: CronAPI;
|
cron: CronAPI;
|
||||||
messageQueue: MessageQueueServiceAPI;
|
messageQueue: MessageQueueServiceAPI;
|
||||||
push: PushServiceAPI;
|
push: PushServiceAPI;
|
||||||
realtime: RealtimeServiceAPI;
|
|
||||||
search: SearchServiceAPI;
|
search: SearchServiceAPI;
|
||||||
storage: StorageAPI;
|
storage: StorageAPI;
|
||||||
tracker: TrackerAPI;
|
tracker: TrackerAPI;
|
||||||
webserver: WebServerAPI;
|
webserver: WebServerAPI;
|
||||||
websocket: WebSocketAPI;
|
|
||||||
emailPusher: EmailPusherAPI;
|
emailPusher: EmailPusherAPI;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,12 +92,10 @@ class GlobalResolver {
|
|||||||
cron: platform.getProvider<CronAPI>("cron"),
|
cron: platform.getProvider<CronAPI>("cron"),
|
||||||
messageQueue: platform.getProvider<MessageQueueServiceAPI>("message-queue"),
|
messageQueue: platform.getProvider<MessageQueueServiceAPI>("message-queue"),
|
||||||
push: platform.getProvider<PushServiceAPI>("push"),
|
push: platform.getProvider<PushServiceAPI>("push"),
|
||||||
realtime: platform.getProvider<RealtimeServiceAPI>("realtime"),
|
|
||||||
search: platform.getProvider<SearchServiceAPI>("search"),
|
search: platform.getProvider<SearchServiceAPI>("search"),
|
||||||
storage: platform.getProvider<StorageAPI>("storage"),
|
storage: platform.getProvider<StorageAPI>("storage"),
|
||||||
tracker: platform.getProvider<TrackerAPI>("tracker"),
|
tracker: platform.getProvider<TrackerAPI>("tracker"),
|
||||||
webserver: platform.getProvider<WebServerAPI>("webserver"),
|
webserver: platform.getProvider<WebServerAPI>("webserver"),
|
||||||
websocket: platform.getProvider<WebSocketAPI>("websocket"),
|
|
||||||
emailPusher: platform.getProvider<EmailPusherAPI>("email-pusher"),
|
emailPusher: platform.getProvider<EmailPusherAPI>("email-pusher"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import { WebsocketMetadata } from "../../utils/types";
|
|
||||||
import CompanyEntity from "./entities/company";
|
|
||||||
import UserEntity from "./entities/user";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User Rooms
|
|
||||||
*/
|
|
||||||
export function getUserRooms(user: UserEntity): WebsocketMetadata[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getUserRoom(user.id),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUserRoom(userId: string): string {
|
|
||||||
return `/me/${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPublicUserRoom(userId: string): string {
|
|
||||||
return `/users/${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUserName(userId: string): string {
|
|
||||||
return `user-room-${userId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Company Rooms
|
|
||||||
*/
|
|
||||||
export function getCompanyRooms(company: CompanyEntity): WebsocketMetadata[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getCompanyRoom(company.id),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCompanyRoom(companyId: string): string {
|
|
||||||
return `/company/${companyId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCompanyName(companyId: string): string {
|
|
||||||
return `company-room-${companyId}`;
|
|
||||||
}
|
|
||||||
@@ -30,8 +30,7 @@ import ExternalGroup, {
|
|||||||
ExternalGroupPrimaryKey,
|
ExternalGroupPrimaryKey,
|
||||||
getInstance as getExternalGroupInstance,
|
getInstance as getExternalGroupInstance,
|
||||||
} from "../entities/external_company";
|
} from "../entities/external_company";
|
||||||
import { logger, RealtimeSaved } from "../../../core/platform/framework";
|
import { logger } from "../../../core/platform/framework";
|
||||||
import { getCompanyRoom, getUserRoom } from "../realtime";
|
|
||||||
import gr from "../../global-resolver";
|
import gr from "../../global-resolver";
|
||||||
import { localEventBus } from "../../../core/platform/framework/event-bus";
|
import { localEventBus } from "../../../core/platform/framework/event-bus";
|
||||||
|
|
||||||
@@ -59,15 +58,6 @@ export class CompanyServiceImpl {
|
|||||||
return this.externalCompanyRepository.findOne(pk, {}, context);
|
return this.externalCompanyRepository.findOne(pk, {}, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
@RealtimeSaved<Company>((company, _context) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getCompanyRoom(company.id),
|
|
||||||
resource: company,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})
|
|
||||||
async updateCompany<SaveOptions>(
|
async updateCompany<SaveOptions>(
|
||||||
company: Company,
|
company: Company,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@@ -186,14 +176,6 @@ export class CompanyServiceImpl {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RealtimeSaved<CompanyUser>((companyUser, _) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getUserRoom(companyUser?.user_id),
|
|
||||||
resource: companyUser,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})
|
|
||||||
async removeUserFromCompany(
|
async removeUserFromCompany(
|
||||||
companyPk: CompanyPrimaryKey,
|
companyPk: CompanyPrimaryKey,
|
||||||
userPk: UserPrimaryKey,
|
userPk: UserPrimaryKey,
|
||||||
@@ -249,14 +231,6 @@ export class CompanyServiceImpl {
|
|||||||
return new DeleteResult<Company>("company", instance, !!instance);
|
return new DeleteResult<Company>("company", instance, !!instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RealtimeSaved<CompanyUser>((companyUser, _) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getUserRoom(companyUser?.user_id),
|
|
||||||
resource: companyUser,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})
|
|
||||||
async setUserRole(
|
async setUserRole(
|
||||||
companyId: uuid,
|
companyId: uuid,
|
||||||
userId: uuid,
|
userId: uuid,
|
||||||
|
|||||||
@@ -28,11 +28,8 @@ import assert from "assert";
|
|||||||
import { localEventBus } from "../../../../core/platform/framework/event-bus";
|
import { localEventBus } from "../../../../core/platform/framework/event-bus";
|
||||||
import { ResourceEventsPayload } from "../../../../utils/types";
|
import { ResourceEventsPayload } from "../../../../utils/types";
|
||||||
import { isNumber, isString } from "lodash";
|
import { isNumber, isString } from "lodash";
|
||||||
import { RealtimeSaved } from "../../../../core/platform/framework";
|
|
||||||
import { getPublicUserRoom, getUserRoom } from "../../realtime";
|
|
||||||
import NodeCache from "node-cache";
|
import NodeCache from "node-cache";
|
||||||
import gr from "../../../global-resolver";
|
import gr from "../../../global-resolver";
|
||||||
import { formatUser } from "../../../../utils/users";
|
|
||||||
import { TYPE as DriveFileType, DriveFile } from "../../../documents/entities/drive-file";
|
import { TYPE as DriveFileType, DriveFile } from "../../../documents/entities/drive-file";
|
||||||
|
|
||||||
export class UserServiceImpl {
|
export class UserServiceImpl {
|
||||||
@@ -99,34 +96,11 @@ export class UserServiceImpl {
|
|||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@RealtimeSaved<User>((user, _context) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getPublicUserRoom(user.id),
|
|
||||||
resource: user,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})
|
|
||||||
async publishPublicUserRealtime(userId: string): Promise<void> {
|
|
||||||
const user = await this.get({ id: userId });
|
|
||||||
new SaveResult("user", formatUser(user, { includeCompanies: true }), OperationType.UPDATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RealtimeSaved<User>((user, _context) => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
room: getUserRoom(user.id),
|
|
||||||
resource: formatUser(user), // FIX ME we should formatUser here
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})
|
|
||||||
async save(user: User, context?: ExecutionContext): Promise<SaveResult<User>> {
|
async save(user: User, context?: ExecutionContext): Promise<SaveResult<User>> {
|
||||||
this.assignDefaults(user);
|
this.assignDefaults(user);
|
||||||
await this.repository.save(user, context);
|
await this.repository.save(user, context);
|
||||||
await this.updateExtRepository(user);
|
await this.updateExtRepository(user);
|
||||||
|
|
||||||
await this.publishPublicUserRealtime(user.id);
|
|
||||||
|
|
||||||
return new SaveResult("user", user, OperationType.UPDATE);
|
return new SaveResult("user", user, OperationType.UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,19 +20,18 @@ import {
|
|||||||
CompanyParameters,
|
CompanyParameters,
|
||||||
CompanyStatsObject,
|
CompanyStatsObject,
|
||||||
CompanyUserObject,
|
CompanyUserObject,
|
||||||
|
CompanyUsersParameters,
|
||||||
DeregisterDeviceParams,
|
DeregisterDeviceParams,
|
||||||
RegisterDeviceBody,
|
RegisterDeviceBody,
|
||||||
RegisterDeviceParams,
|
RegisterDeviceParams,
|
||||||
UserListQueryParameters,
|
UserListQueryParameters,
|
||||||
UserObject,
|
UserObject,
|
||||||
UserParameters,
|
UserParameters,
|
||||||
CompanyUsersParameters,
|
|
||||||
UserQuota,
|
UserQuota,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import Company from "../entities/company";
|
import Company from "../entities/company";
|
||||||
import CompanyUser from "../entities/company_user";
|
import CompanyUser from "../entities/company_user";
|
||||||
import coalesce from "../../../utils/coalesce";
|
import coalesce from "../../../utils/coalesce";
|
||||||
import { getCompanyRooms, getUserRooms } from "../realtime";
|
|
||||||
import { formatCompany, getCompanyStats } from "../utils";
|
import { formatCompany, getCompanyStats } from "../utils";
|
||||||
import { formatUser } from "../../../utils/users";
|
import { formatUser } from "../../../utils/users";
|
||||||
import gr from "../../global-resolver";
|
import gr from "../../global-resolver";
|
||||||
@@ -70,9 +69,6 @@ export class UsersCrudController
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
resource: userObject,
|
resource: userObject,
|
||||||
websocket: context.user.id
|
|
||||||
? gr.platformServices.realtime.sign(getUserRooms(user), context.user.id)[0]
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +96,7 @@ export class UsersCrudController
|
|||||||
async setPreferences(
|
async setPreferences(
|
||||||
request: FastifyRequest<{ Body: User["preferences"] }>,
|
request: FastifyRequest<{ Body: User["preferences"] }>,
|
||||||
): Promise<User["preferences"]> {
|
): Promise<User["preferences"]> {
|
||||||
const preferences = await gr.services.users.setPreferences(
|
return await gr.services.users.setPreferences({ id: request.currentUser.id }, request.body);
|
||||||
{ id: request.currentUser.id },
|
|
||||||
request.body,
|
|
||||||
);
|
|
||||||
return preferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(
|
async list(
|
||||||
@@ -143,7 +135,6 @@ export class UsersCrudController
|
|||||||
// return users;
|
// return users;
|
||||||
return {
|
return {
|
||||||
resources: resUsers,
|
resources: resUsers,
|
||||||
websockets: gr.platformServices.realtime.sign([], context.user.id),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +211,6 @@ export class UsersCrudController
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
resources: combos.map(combo => formatCompany(...combo)),
|
resources: combos.map(combo => formatCompany(...combo)),
|
||||||
websockets: gr.platformServices.realtime.sign([], context.user.id),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +243,6 @@ export class UsersCrudController
|
|||||||
companyUserObj,
|
companyUserObj,
|
||||||
getCompanyStats(company, await gr.services.statistics.get(company.id, "messages")),
|
getCompanyStats(company, await gr.services.statistics.get(company.id, "messages")),
|
||||||
),
|
),
|
||||||
websocket: context.user?.id
|
|
||||||
? gr.platformServices.realtime.sign(getCompanyRooms(company), context.user.id)[0]
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { webSocketSchema } from "../../../utils/types";
|
|
||||||
import { CompanyFeaturesEnum, CompanyLimitsEnum } from "./types";
|
import { CompanyFeaturesEnum, CompanyLimitsEnum } from "./types";
|
||||||
|
|
||||||
export const userObjectSchema = {
|
export const userObjectSchema = {
|
||||||
@@ -126,7 +125,6 @@ export const getUserSchema = {
|
|||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
resource: userObjectSchema,
|
resource: userObjectSchema,
|
||||||
websocket: webSocketSchema,
|
|
||||||
},
|
},
|
||||||
required: ["resource"],
|
required: ["resource"],
|
||||||
},
|
},
|
||||||
@@ -231,7 +229,6 @@ export const getCompanySchema = {
|
|||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
resource: companyObjectSchema,
|
resource: companyObjectSchema,
|
||||||
websocket: webSocketSchema,
|
|
||||||
},
|
},
|
||||||
required: ["resource"],
|
required: ["resource"],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { WebsocketMetadata } from "../../utils/types";
|
|
||||||
import Workspace from "./entities/workspace";
|
|
||||||
import { WorkspaceExecutionContext } from "./types";
|
|
||||||
|
|
||||||
export function getWebsocketInformation(workspace: Workspace): WebsocketMetadata {
|
|
||||||
return {
|
|
||||||
name: workspace.name,
|
|
||||||
room: getWorkspacePath(workspace),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getWorkspaceRooms(context: WorkspaceExecutionContext): WebsocketMetadata[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: "workspaces",
|
|
||||||
room: getRoomName(context),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRoomName(context: Pick<Workspace, "company_id">): string {
|
|
||||||
return `/companies/${context.company_id}/workspaces`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getWorkspacePath(
|
|
||||||
workspace: Pick<Workspace, "id">,
|
|
||||||
context?: WorkspaceExecutionContext,
|
|
||||||
): string {
|
|
||||||
return `/companies/${context?.company_id}/workspaces/${workspace.id}`;
|
|
||||||
}
|
|
||||||
@@ -41,13 +41,7 @@ import {
|
|||||||
WorkspaceCounterPrimaryKey,
|
WorkspaceCounterPrimaryKey,
|
||||||
} from "../entities/workspace_counters";
|
} from "../entities/workspace_counters";
|
||||||
import { countRepositoryItems } from "../../../utils/counters";
|
import { countRepositoryItems } from "../../../utils/counters";
|
||||||
import {
|
import { Initializable, TdriveServiceProvider } from "../../../core/platform/framework";
|
||||||
Initializable,
|
|
||||||
RealtimeSaved,
|
|
||||||
TdriveServiceProvider,
|
|
||||||
} from "../../../core/platform/framework";
|
|
||||||
import { ResourcePath } from "../../../core/platform/services/realtime/types";
|
|
||||||
import { getRoomName, getWorkspacePath } from "../realtime";
|
|
||||||
import { InviteTokenObject, WorkspaceInviteTokenObject } from "../web/types";
|
import { InviteTokenObject, WorkspaceInviteTokenObject } from "../web/types";
|
||||||
import WorkspaceInviteTokens, {
|
import WorkspaceInviteTokens, {
|
||||||
getInstance as getWorkspaceInviteTokensInstance,
|
getInstance as getWorkspaceInviteTokensInstance,
|
||||||
@@ -149,15 +143,6 @@ export class WorkspaceServiceImpl implements TdriveServiceProvider, Initializabl
|
|||||||
return new CreateResult<Workspace>(TYPE, created.entity);
|
return new CreateResult<Workspace>(TYPE, created.entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove logic from context
|
|
||||||
@RealtimeSaved<Workspace>((workspace, context) => [
|
|
||||||
{
|
|
||||||
// FIXME: For now the room is defined at the company level
|
|
||||||
// It meay be good to have a special room where just some users are receiving this event
|
|
||||||
room: ResourcePath.get(getRoomName(workspace)),
|
|
||||||
path: getWorkspacePath(workspace, context as WorkspaceExecutionContext),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
async save<SaveOptions>(
|
async save<SaveOptions>(
|
||||||
item: Partial<Workspace>,
|
item: Partial<Workspace>,
|
||||||
options?: SaveOptions & { logo_b64?: string },
|
options?: SaveOptions & { logo_b64?: string },
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { WorkspaceExecutionContext } from "../../types";
|
|||||||
import { plainToClass } from "class-transformer";
|
import { plainToClass } from "class-transformer";
|
||||||
import { hasCompanyAdminLevel, hasCompanyMemberLevel } from "../../../../utils/company";
|
import { hasCompanyAdminLevel, hasCompanyMemberLevel } from "../../../../utils/company";
|
||||||
import { hasWorkspaceAdminLevel } from "../../../../utils/workspace";
|
import { hasWorkspaceAdminLevel } from "../../../../utils/workspace";
|
||||||
import { getWorkspaceRooms } from "../../realtime";
|
|
||||||
import {
|
import {
|
||||||
CrudException,
|
CrudException,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
@@ -166,7 +165,6 @@ export class WorkspacesCrudController
|
|||||||
this.formatWorkspace(ws, await this.getWorkspaceUsersCount(ws.id), context.user.id),
|
this.formatWorkspace(ws, await this.getWorkspaceUsersCount(ws.id), context.user.id),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
websockets: gr.platformServices.realtime.sign(getWorkspaceRooms(context), context.user.id),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { webSocketSchema } from "../../../utils/types";
|
|
||||||
import { companyObjectSchema, userObjectSchema } from "../../user/web/schemas";
|
import { companyObjectSchema, userObjectSchema } from "../../user/web/schemas";
|
||||||
|
|
||||||
const workspaceObjectSchema = {
|
const workspaceObjectSchema = {
|
||||||
@@ -35,7 +34,6 @@ export const getWorkspacesSchema = {
|
|||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
resources: { type: "array", items: workspaceObjectSchema },
|
resources: { type: "array", items: workspaceObjectSchema },
|
||||||
websockets: { type: "array", items: webSocketSchema },
|
|
||||||
},
|
},
|
||||||
required: ["resources"],
|
required: ["resources"],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,51 +32,28 @@ export interface User {
|
|||||||
public_token_document_id?: string;
|
public_token_document_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const webSocketSchema = {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
name: { type: "string" },
|
|
||||||
room: { type: "string" },
|
|
||||||
token: { type: "string" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface Channel extends Workspace {
|
export interface Channel extends Workspace {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChannelType {
|
|
||||||
DIRECT = "direct",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Workspace {
|
export interface Workspace {
|
||||||
company_id: string;
|
company_id: string;
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebsocketMetadata {
|
|
||||||
room: string;
|
|
||||||
name?: string;
|
|
||||||
token?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ResourceListResponse<T> {
|
export class ResourceListResponse<T> {
|
||||||
resources: T[];
|
resources: T[];
|
||||||
websockets?: ResourceWebsocket[];
|
|
||||||
next_page_token?: string;
|
next_page_token?: string;
|
||||||
total?: number;
|
total?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ResourceGetResponse<T> {
|
export class ResourceGetResponse<T> {
|
||||||
websocket?: ResourceWebsocket;
|
|
||||||
resource: T;
|
resource: T;
|
||||||
}
|
}
|
||||||
export class ResourceUpdateResponse<T> {
|
export class ResourceUpdateResponse<T> {
|
||||||
websocket?: ResourceWebsocket;
|
|
||||||
resource: T;
|
resource: T;
|
||||||
}
|
}
|
||||||
export class ResourceCreateResponse<T> {
|
export class ResourceCreateResponse<T> {
|
||||||
websocket?: ResourceWebsocket;
|
|
||||||
resource: T;
|
resource: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,16 +61,7 @@ export class ResourceDeleteResponse {
|
|||||||
status: DeleteStatus;
|
status: DeleteStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResourceListQueryParameters extends PaginationQueryParameters {
|
|
||||||
search_query?: string;
|
|
||||||
mine?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare type DeleteStatus = "success" | "error";
|
export declare type DeleteStatus = "success" | "error";
|
||||||
export interface ResourceWebsocket {
|
|
||||||
room: string;
|
|
||||||
token?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResourceEventsPayload {
|
export interface ResourceEventsPayload {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -104,21 +72,11 @@ export interface ResourceEventsPayload {
|
|||||||
workspace?: WorkspacePrimaryKey;
|
workspace?: WorkspacePrimaryKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentEventsPayload {
|
|
||||||
user: User;
|
|
||||||
actor?: User;
|
|
||||||
document?: {
|
|
||||||
sender: string;
|
|
||||||
};
|
|
||||||
company?: { id: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PaginationQueryParameters {
|
export interface PaginationQueryParameters {
|
||||||
// It is offset for MongoDB and pointer to the next page for ScyllaDB
|
// It is offset for MongoDB and pointer to the next page for ScyllaDB
|
||||||
page_token?: string;
|
page_token?: string;
|
||||||
// Page size
|
// Page size
|
||||||
limit?: string;
|
limit?: string;
|
||||||
websockets?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessToken {
|
export interface AccessToken {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ describe("The Documents Browser Window and API", () => {
|
|||||||
"user",
|
"user",
|
||||||
"files",
|
"files",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"statistics",
|
"statistics",
|
||||||
"platform-services",
|
"platform-services",
|
||||||
"documents",
|
"documents",
|
||||||
|
|||||||
@@ -28,10 +28,8 @@ describe("the Drive feature", () => {
|
|||||||
"user",
|
"user",
|
||||||
"search",
|
"search",
|
||||||
"files",
|
"files",
|
||||||
"websocket",
|
|
||||||
"messages",
|
"messages",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"channels",
|
"channels",
|
||||||
"counter",
|
"counter",
|
||||||
"statistics",
|
"statistics",
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ describe("the Drive feature", () => {
|
|||||||
"user",
|
"user",
|
||||||
"search",
|
"search",
|
||||||
"files",
|
"files",
|
||||||
"websocket",
|
|
||||||
"messages",
|
"messages",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"channels",
|
"channels",
|
||||||
"counter",
|
"counter",
|
||||||
"statistics",
|
"statistics",
|
||||||
|
|||||||
@@ -25,10 +25,8 @@ describe("the Drive Search feature", () => {
|
|||||||
"user",
|
"user",
|
||||||
"search",
|
"search",
|
||||||
"files",
|
"files",
|
||||||
"websocket",
|
|
||||||
"messages",
|
"messages",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"channels",
|
"channels",
|
||||||
"statistics",
|
"statistics",
|
||||||
"platform-services",
|
"platform-services",
|
||||||
|
|||||||
@@ -39,10 +39,8 @@ describe("the Drive Tdrive tabs feature", () => {
|
|||||||
"user",
|
"user",
|
||||||
"search",
|
"search",
|
||||||
"files",
|
"files",
|
||||||
"websocket",
|
|
||||||
"messages",
|
"messages",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"channels",
|
"channels",
|
||||||
"counter",
|
"counter",
|
||||||
"statistics",
|
"statistics",
|
||||||
|
|||||||
@@ -32,10 +32,8 @@ describe("the Drive feature", () => {
|
|||||||
"user",
|
"user",
|
||||||
"search",
|
"search",
|
||||||
"files",
|
"files",
|
||||||
"websocket",
|
|
||||||
"messages",
|
"messages",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"channels",
|
"channels",
|
||||||
"counter",
|
"counter",
|
||||||
"statistics",
|
"statistics",
|
||||||
|
|||||||
@@ -44,10 +44,8 @@ describe("the My Drive feature", () => {
|
|||||||
"user",
|
"user",
|
||||||
"search",
|
"search",
|
||||||
"files",
|
"files",
|
||||||
"websocket",
|
|
||||||
"messages",
|
"messages",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"channels",
|
"channels",
|
||||||
"counter",
|
"counter",
|
||||||
"statistics",
|
"statistics",
|
||||||
|
|||||||
@@ -44,10 +44,8 @@ describe("the public links feature", () => {
|
|||||||
"user",
|
"user",
|
||||||
"search",
|
"search",
|
||||||
"files",
|
"files",
|
||||||
"websocket",
|
|
||||||
"messages",
|
"messages",
|
||||||
"auth",
|
"auth",
|
||||||
"realtime",
|
|
||||||
"channels",
|
"channels",
|
||||||
"counter",
|
"counter",
|
||||||
"statistics",
|
"statistics",
|
||||||
|
|||||||
@@ -12,16 +12,6 @@
|
|||||||
"endpoint": "http://localhost:9200"
|
"endpoint": "http://localhost:9200"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"websocket": {
|
|
||||||
"path": "/socket",
|
|
||||||
"adapters": {
|
|
||||||
"types": ["redis"],
|
|
||||||
"redis": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 6379
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"auth": {
|
"auth": {
|
||||||
"jwt": {
|
"jwt": {
|
||||||
"secret": "supersecret"
|
"secret": "supersecret"
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ describe("The /users/quota API", () => {
|
|||||||
"database",
|
"database",
|
||||||
"search",
|
"search",
|
||||||
"message-queue",
|
"message-queue",
|
||||||
"websocket",
|
|
||||||
"applications",
|
"applications",
|
||||||
"webserver",
|
"webserver",
|
||||||
"user",
|
"user",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ describe("The /users API", () => {
|
|||||||
"database",
|
"database",
|
||||||
"search",
|
"search",
|
||||||
"message-queue",
|
"message-queue",
|
||||||
"websocket",
|
|
||||||
"webserver",
|
"webserver",
|
||||||
"user",
|
"user",
|
||||||
"auth",
|
"auth",
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ describe("The /users API", () => {
|
|||||||
"database",
|
"database",
|
||||||
"search",
|
"search",
|
||||||
"message-queue",
|
"message-queue",
|
||||||
"websocket",
|
|
||||||
"applications",
|
"applications",
|
||||||
"webserver",
|
"webserver",
|
||||||
"user",
|
"user",
|
||||||
|
|||||||
-116
@@ -1,116 +0,0 @@
|
|||||||
import { describe, expect, it, jest } from "@jest/globals";
|
|
||||||
import { CreateResult } from "../../../../../../../src/core/platform/framework/api/crud-service";
|
|
||||||
import { RealtimeCreated } from "../../../../../../../src/core/platform/framework/decorators";
|
|
||||||
import { websocketEventBus } from "../../../../../../../src/core/platform/services/realtime/bus";
|
|
||||||
import { ResourcePath } from "../../../../../../../src/core/platform/services/realtime/types";
|
|
||||||
|
|
||||||
describe("The RealtimeCreated decorator", () => {
|
|
||||||
it("should call the original method send back original result but do not emit event if result type is wrong", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeCreated({ room: "/foo/bar" })
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
reverseMeBaby(input: string): Promise<string> {
|
|
||||||
return Promise.resolve(input.split("").reverse().join(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the original method send back original result and emit event", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeCreated({ room: "/foo/bar", path: "/foo/bar/baz" })
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
async reverseMeBaby(input: string): Promise<CreateResult<string>> {
|
|
||||||
return new CreateResult<string>("string", input.split("").reverse().join(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result.entity).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(emitSpy).toHaveBeenCalledWith("created", {
|
|
||||||
room: {
|
|
||||||
name: "default",
|
|
||||||
path: ["/foo/bar"],
|
|
||||||
} as ResourcePath,
|
|
||||||
resourcePath: "/foo/bar/baz",
|
|
||||||
entity: "oloy",
|
|
||||||
type: "string",
|
|
||||||
result: {
|
|
||||||
entity: "oloy",
|
|
||||||
type: "string",
|
|
||||||
context: undefined,
|
|
||||||
operation: "create",
|
|
||||||
raw: undefined,
|
|
||||||
} as CreateResult<string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should emit event with path computed from function", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeCreated<string>(input => [
|
|
||||||
{ room: ResourcePath.get(`/foo/bar/${input}`), path: "/foo/bar/baz" },
|
|
||||||
])
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
async reverseMeBaby(input: string): Promise<CreateResult<string>> {
|
|
||||||
return new CreateResult<string>("string", input.split("").reverse().join(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result.entity).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(emitSpy).toHaveBeenCalledWith("created", {
|
|
||||||
room: {
|
|
||||||
name: "default",
|
|
||||||
path: ["/foo/bar/oloy"],
|
|
||||||
} as ResourcePath,
|
|
||||||
resourcePath: "/foo/bar/baz",
|
|
||||||
entity: "oloy",
|
|
||||||
type: "string",
|
|
||||||
result: {
|
|
||||||
entity: "oloy",
|
|
||||||
context: undefined,
|
|
||||||
operation: "create",
|
|
||||||
type: "string",
|
|
||||||
raw: undefined,
|
|
||||||
} as CreateResult<string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
-116
@@ -1,116 +0,0 @@
|
|||||||
import { describe, expect, it, jest } from "@jest/globals";
|
|
||||||
import { DeleteResult } from "../../../../../../../src/core/platform/framework/api/crud-service";
|
|
||||||
import { RealtimeDeleted } from "../../../../../../../src/core/platform/framework/decorators";
|
|
||||||
import { websocketEventBus } from "../../../../../../../src/core/platform/services/realtime/bus";
|
|
||||||
import { ResourcePath } from "../../../../../../../src/core/platform/services/realtime/types";
|
|
||||||
|
|
||||||
describe("The RealtimeDeleted decorator", () => {
|
|
||||||
it("should call the original method send back original result but do not emit event if result type is wrong", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeDeleted({ room: "/foo/bar" })
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
reverseMeBaby(input: string): Promise<string> {
|
|
||||||
return Promise.resolve(input.split("").reverse().join(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the original method send back original result and emit event", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeDeleted({ room: "/foo/bar", path: "/foo/bar/baz" })
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
async reverseMeBaby(input: string): Promise<DeleteResult<string>> {
|
|
||||||
return new DeleteResult<string>("string", input.split("").reverse().join(""), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result.entity).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(emitSpy).toHaveBeenCalledWith("deleted", {
|
|
||||||
room: {
|
|
||||||
name: "default",
|
|
||||||
path: ["/foo/bar"],
|
|
||||||
} as ResourcePath,
|
|
||||||
resourcePath: "/foo/bar/baz",
|
|
||||||
entity: "oloy",
|
|
||||||
type: "string",
|
|
||||||
result: {
|
|
||||||
entity: "oloy",
|
|
||||||
context: undefined,
|
|
||||||
operation: "delete",
|
|
||||||
deleted: true,
|
|
||||||
type: "string",
|
|
||||||
} as DeleteResult<string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should emit event with path computed from function", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeDeleted(result => [
|
|
||||||
{ room: ResourcePath.get(`/foo/bar/${result}`), path: "/foo/bar/baz" },
|
|
||||||
])
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
async reverseMeBaby(input: string): Promise<DeleteResult<string>> {
|
|
||||||
return new DeleteResult<string>("string", input.split("").reverse().join(""), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result.entity).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(emitSpy).toHaveBeenCalledWith("deleted", {
|
|
||||||
room: {
|
|
||||||
name: "default",
|
|
||||||
path: ["/foo/bar/oloy"],
|
|
||||||
} as ResourcePath,
|
|
||||||
resourcePath: "/foo/bar/baz",
|
|
||||||
entity: "oloy",
|
|
||||||
type: "string",
|
|
||||||
result: {
|
|
||||||
entity: "oloy",
|
|
||||||
context: undefined,
|
|
||||||
operation: "delete",
|
|
||||||
deleted: true,
|
|
||||||
type: "string",
|
|
||||||
} as DeleteResult<string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
-118
@@ -1,118 +0,0 @@
|
|||||||
import { describe, expect, it, jest } from "@jest/globals";
|
|
||||||
import { UpdateResult } from "../../../../../../../src/core/platform/framework/api/crud-service";
|
|
||||||
import { RealtimeUpdated } from "../../../../../../../src/core/platform/framework/decorators";
|
|
||||||
import { websocketEventBus } from "../../../../../../../src/core/platform/services/realtime/bus";
|
|
||||||
import { ResourcePath } from "../../../../../../../src/core/platform/services/realtime/types";
|
|
||||||
|
|
||||||
describe("The RealtimeUpdated decorator", () => {
|
|
||||||
it("should call the original method send back original result but do not emit event if result type is wrong", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeUpdated({ room: "/foo/bar" })
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
reverseMeBaby(input: string): Promise<string> {
|
|
||||||
return Promise.resolve(input.split("").reverse().join(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call the original method send back original result and emit event", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeUpdated({ room: "/foo/bar", path: "/foo/bar/baz" })
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
async reverseMeBaby(input: string): Promise<UpdateResult<string>> {
|
|
||||||
return new UpdateResult<string>("string", input.split("").reverse().join(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result.entity).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(emitSpy).toHaveBeenCalledWith("updated", {
|
|
||||||
entity: "oloy",
|
|
||||||
room: {
|
|
||||||
name: "default",
|
|
||||||
path: ["/foo/bar"],
|
|
||||||
} as ResourcePath,
|
|
||||||
resourcePath: "/foo/bar/baz",
|
|
||||||
type: "string",
|
|
||||||
result: {
|
|
||||||
type: "string",
|
|
||||||
entity: "oloy",
|
|
||||||
affected: undefined,
|
|
||||||
context: undefined,
|
|
||||||
operation: "update",
|
|
||||||
raw: undefined,
|
|
||||||
} as UpdateResult<string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should emit event with path computed from function", async () => {
|
|
||||||
const emitSpy = jest.spyOn(websocketEventBus, "emit");
|
|
||||||
|
|
||||||
class TestMe {
|
|
||||||
@RealtimeUpdated(result => [
|
|
||||||
{ room: ResourcePath.get(`/foo/bar/${result}`), path: "/foo/bar/baz" },
|
|
||||||
])
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
async reverseMeBaby(input: string): Promise<UpdateResult<string>> {
|
|
||||||
return new UpdateResult<string>("string", input.split("").reverse().join(""));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const test = new TestMe();
|
|
||||||
const originalSpy = jest.spyOn(test, "reverseMeBaby");
|
|
||||||
const result = await test.reverseMeBaby("yolo");
|
|
||||||
|
|
||||||
expect(result.entity).toEqual("oloy");
|
|
||||||
expect(originalSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(originalSpy).toHaveBeenCalledWith("yolo");
|
|
||||||
expect(emitSpy).toHaveBeenCalledTimes(1);
|
|
||||||
expect(emitSpy).toHaveBeenCalledWith("updated", {
|
|
||||||
entity: "oloy",
|
|
||||||
room: {
|
|
||||||
name: "default",
|
|
||||||
path: ["/foo/bar/oloy"],
|
|
||||||
} as ResourcePath,
|
|
||||||
resourcePath: "/foo/bar/baz",
|
|
||||||
type: "string",
|
|
||||||
result: {
|
|
||||||
type: "string",
|
|
||||||
context: undefined,
|
|
||||||
operation: "update",
|
|
||||||
entity: "oloy",
|
|
||||||
affected: undefined,
|
|
||||||
raw: undefined,
|
|
||||||
} as UpdateResult<string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
emitSpy.mockRestore();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user