
- Complete NestJS TypeScript implementation with WebSocket support - Direct messaging (DM) and group chat functionality - End-to-end encryption with AES encryption and key pairs - Media file support (images, videos, audio, documents) up to 100MB - Push notifications with Firebase Cloud Messaging integration - Mention alerts and real-time typing indicators - User authentication with JWT and Passport - SQLite database with TypeORM entities and relationships - Comprehensive API documentation with Swagger/OpenAPI - File upload handling with secure access control - Online/offline status tracking and presence management - Message editing, deletion, and reply functionality - Notification management with automatic cleanup - Health check endpoint for monitoring - CORS configuration for cross-origin requests - Environment-based configuration management - Structured for Flutter SDK integration Features implemented: ✅ Real-time messaging with Socket.IO ✅ User registration and authentication ✅ Direct messages and group chats ✅ Media file uploads and management ✅ End-to-end encryption ✅ Push notifications ✅ Mention alerts ✅ Typing indicators ✅ Message read receipts ✅ Online status tracking ✅ File access control ✅ Comprehensive API documentation Ready for Flutter SDK development and production deployment.
123 lines
6.8 KiB
JavaScript
123 lines
6.8 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.WsContextCreator = void 0;
|
|
const constants_1 = require("@nestjs/common/constants");
|
|
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
|
const constants_2 = require("@nestjs/core/guards/constants");
|
|
const context_utils_1 = require("@nestjs/core/helpers/context-utils");
|
|
const handler_metadata_storage_1 = require("@nestjs/core/helpers/handler-metadata-storage");
|
|
const constants_3 = require("../constants");
|
|
const ws_exception_1 = require("../errors/ws-exception");
|
|
const ws_params_factory_1 = require("../factories/ws-params-factory");
|
|
const ws_metadata_constants_1 = require("./ws-metadata-constants");
|
|
class WsContextCreator {
|
|
constructor(wsProxy, exceptionFiltersContext, pipesContextCreator, pipesConsumer, guardsContextCreator, guardsConsumer, interceptorsContextCreator, interceptorsConsumer) {
|
|
this.wsProxy = wsProxy;
|
|
this.exceptionFiltersContext = exceptionFiltersContext;
|
|
this.pipesContextCreator = pipesContextCreator;
|
|
this.pipesConsumer = pipesConsumer;
|
|
this.guardsContextCreator = guardsContextCreator;
|
|
this.guardsConsumer = guardsConsumer;
|
|
this.interceptorsContextCreator = interceptorsContextCreator;
|
|
this.interceptorsConsumer = interceptorsConsumer;
|
|
this.contextUtils = new context_utils_1.ContextUtils();
|
|
this.wsParamsFactory = new ws_params_factory_1.WsParamsFactory();
|
|
this.handlerMetadataStorage = new handler_metadata_storage_1.HandlerMetadataStorage();
|
|
}
|
|
create(instance, callback, moduleKey, methodName) {
|
|
const contextType = 'ws';
|
|
const { argsLength, paramtypes, getParamsMetadata } = this.getMetadata(instance, methodName, contextType);
|
|
const exceptionHandler = this.exceptionFiltersContext.create(instance, callback, moduleKey);
|
|
const pipes = this.pipesContextCreator.create(instance, callback, moduleKey);
|
|
const guards = this.guardsContextCreator.create(instance, callback, moduleKey);
|
|
const interceptors = this.interceptorsContextCreator.create(instance, callback, moduleKey);
|
|
const paramsMetadata = getParamsMetadata(moduleKey);
|
|
const paramsOptions = paramsMetadata
|
|
? this.contextUtils.mergeParamsMetatypes(paramsMetadata, paramtypes)
|
|
: [];
|
|
const fnApplyPipes = this.createPipesFn(pipes, paramsOptions);
|
|
const fnCanActivate = this.createGuardsFn(guards, instance, callback, contextType);
|
|
const handler = (initialArgs, args) => async () => {
|
|
if (fnApplyPipes) {
|
|
await fnApplyPipes(initialArgs, ...args);
|
|
return callback.apply(instance, initialArgs);
|
|
}
|
|
return callback.apply(instance, args);
|
|
};
|
|
const targetPattern = this.reflectCallbackPattern(callback);
|
|
return this.wsProxy.create(async (...args) => {
|
|
args.push(targetPattern);
|
|
const initialArgs = this.contextUtils.createNullArray(argsLength);
|
|
fnCanActivate && (await fnCanActivate(args));
|
|
return this.interceptorsConsumer.intercept(interceptors, args, instance, callback, handler(initialArgs, args), contextType);
|
|
}, exceptionHandler, targetPattern);
|
|
}
|
|
reflectCallbackParamtypes(instance, callback) {
|
|
return Reflect.getMetadata(constants_1.PARAMTYPES_METADATA, instance, callback.name);
|
|
}
|
|
reflectCallbackPattern(callback) {
|
|
return Reflect.getMetadata(constants_3.MESSAGE_METADATA, callback);
|
|
}
|
|
createGuardsFn(guards, instance, callback, contextType) {
|
|
const canActivateFn = async (args) => {
|
|
const canActivate = await this.guardsConsumer.tryActivate(guards, args, instance, callback, contextType);
|
|
if (!canActivate) {
|
|
throw new ws_exception_1.WsException(constants_2.FORBIDDEN_MESSAGE);
|
|
}
|
|
};
|
|
return guards.length ? canActivateFn : null;
|
|
}
|
|
getMetadata(instance, methodName, contextType) {
|
|
const cacheMetadata = this.handlerMetadataStorage.get(instance, methodName);
|
|
if (cacheMetadata) {
|
|
return cacheMetadata;
|
|
}
|
|
const metadata = this.contextUtils.reflectCallbackMetadata(instance, methodName, constants_3.PARAM_ARGS_METADATA) || ws_metadata_constants_1.DEFAULT_CALLBACK_METADATA;
|
|
const keys = Object.keys(metadata);
|
|
const argsLength = this.contextUtils.getArgumentsLength(keys, metadata);
|
|
const paramtypes = this.contextUtils.reflectCallbackParamtypes(instance, methodName);
|
|
const contextFactory = this.contextUtils.getContextFactory(contextType, instance, instance[methodName]);
|
|
const getParamsMetadata = (moduleKey) => this.exchangeKeysForValues(keys, metadata, moduleKey, this.wsParamsFactory, contextFactory);
|
|
const handlerMetadata = {
|
|
argsLength,
|
|
paramtypes,
|
|
getParamsMetadata,
|
|
};
|
|
this.handlerMetadataStorage.set(instance, methodName, handlerMetadata);
|
|
return handlerMetadata;
|
|
}
|
|
exchangeKeysForValues(keys, metadata, moduleContext, paramsFactory, contextFactory) {
|
|
this.pipesContextCreator.setModuleContext(moduleContext);
|
|
return keys.map(key => {
|
|
const { index, data, pipes: pipesCollection } = metadata[key];
|
|
const pipes = this.pipesContextCreator.createConcreteContext(pipesCollection);
|
|
const type = this.contextUtils.mapParamType(key);
|
|
if (key.includes(constants_1.CUSTOM_ROUTE_ARGS_METADATA)) {
|
|
const { factory } = metadata[key];
|
|
const customExtractValue = this.contextUtils.getCustomFactory(factory, data, contextFactory);
|
|
return { index, extractValue: customExtractValue, type, data, pipes };
|
|
}
|
|
const numericType = Number(type);
|
|
const extractValue = (...args) => paramsFactory.exchangeKeyForValue(numericType, data, args);
|
|
return { index, extractValue, type: numericType, data, pipes };
|
|
});
|
|
}
|
|
createPipesFn(pipes, paramsOptions) {
|
|
const pipesFn = async (args, ...params) => {
|
|
const resolveParamValue = async (param) => {
|
|
const { index, extractValue, type, data, metatype, pipes: paramPipes, } = param;
|
|
const value = extractValue(...params);
|
|
args[index] = await this.getParamValue(value, { metatype, type, data }, pipes.concat(paramPipes));
|
|
};
|
|
await Promise.all(paramsOptions.map(resolveParamValue));
|
|
};
|
|
return paramsOptions.length ? pipesFn : null;
|
|
}
|
|
async getParamValue(value, { metatype, type, data }, pipes) {
|
|
return (0, shared_utils_1.isEmpty)(pipes)
|
|
? value
|
|
: this.pipesConsumer.apply(value, { metatype, type, data }, pipes);
|
|
}
|
|
}
|
|
exports.WsContextCreator = WsContextCreator;
|