Automated Action 545563e776 Implement comprehensive real-time chat API with NestJS
- 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.
2025-06-21 17:13:05 +00:00

322 lines
7.0 KiB
JavaScript

const objectToString = Object.prototype.toString;
const uint8ArrayStringified = '[object Uint8Array]';
const arrayBufferStringified = '[object ArrayBuffer]';
function isType(value, typeConstructor, typeStringified) {
if (!value) {
return false;
}
if (value.constructor === typeConstructor) {
return true;
}
return objectToString.call(value) === typeStringified;
}
export function isUint8Array(value) {
return isType(value, Uint8Array, uint8ArrayStringified);
}
function isArrayBuffer(value) {
return isType(value, ArrayBuffer, arrayBufferStringified);
}
function isUint8ArrayOrArrayBuffer(value) {
return isUint8Array(value) || isArrayBuffer(value);
}
export function assertUint8Array(value) {
if (!isUint8Array(value)) {
throw new TypeError(`Expected \`Uint8Array\`, got \`${typeof value}\``);
}
}
export function assertUint8ArrayOrArrayBuffer(value) {
if (!isUint8ArrayOrArrayBuffer(value)) {
throw new TypeError(`Expected \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof value}\``);
}
}
export function toUint8Array(value) {
if (value instanceof ArrayBuffer) {
return new Uint8Array(value);
}
if (ArrayBuffer.isView(value)) {
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
}
throw new TypeError(`Unsupported value, got \`${typeof value}\`.`);
}
export function concatUint8Arrays(arrays, totalLength) {
if (arrays.length === 0) {
return new Uint8Array(0);
}
totalLength ??= arrays.reduce((accumulator, currentValue) => accumulator + currentValue.length, 0);
const returnValue = new Uint8Array(totalLength);
let offset = 0;
for (const array of arrays) {
assertUint8Array(array);
returnValue.set(array, offset);
offset += array.length;
}
return returnValue;
}
export function areUint8ArraysEqual(a, b) {
assertUint8Array(a);
assertUint8Array(b);
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
// eslint-disable-next-line unicorn/no-for-loop
for (let index = 0; index < a.length; index++) {
if (a[index] !== b[index]) {
return false;
}
}
return true;
}
export function compareUint8Arrays(a, b) {
assertUint8Array(a);
assertUint8Array(b);
const length = Math.min(a.length, b.length);
for (let index = 0; index < length; index++) {
const diff = a[index] - b[index];
if (diff !== 0) {
return Math.sign(diff);
}
}
// At this point, all the compared elements are equal.
// The shorter array should come first if the arrays are of different lengths.
return Math.sign(a.length - b.length);
}
const cachedDecoders = {
utf8: new globalThis.TextDecoder('utf8'),
};
export function uint8ArrayToString(array, encoding = 'utf8') {
assertUint8ArrayOrArrayBuffer(array);
cachedDecoders[encoding] ??= new globalThis.TextDecoder(encoding);
return cachedDecoders[encoding].decode(array);
}
function assertString(value) {
if (typeof value !== 'string') {
throw new TypeError(`Expected \`string\`, got \`${typeof value}\``);
}
}
const cachedEncoder = new globalThis.TextEncoder();
export function stringToUint8Array(string) {
assertString(string);
return cachedEncoder.encode(string);
}
function base64ToBase64Url(base64) {
return base64.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
}
function base64UrlToBase64(base64url) {
return base64url.replaceAll('-', '+').replaceAll('_', '/');
}
// Reference: https://phuoc.ng/collection/this-vs-that/concat-vs-push/
const MAX_BLOCK_SIZE = 65_535;
export function uint8ArrayToBase64(array, {urlSafe = false} = {}) {
assertUint8Array(array);
let base64;
if (array.length < MAX_BLOCK_SIZE) {
// Required as `btoa` and `atob` don't properly support Unicode: https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
base64 = globalThis.btoa(String.fromCodePoint.apply(this, array));
} else {
base64 = '';
for (const value of array) {
base64 += String.fromCodePoint(value);
}
base64 = globalThis.btoa(base64);
}
return urlSafe ? base64ToBase64Url(base64) : base64;
}
export function base64ToUint8Array(base64String) {
assertString(base64String);
return Uint8Array.from(globalThis.atob(base64UrlToBase64(base64String)), x => x.codePointAt(0));
}
export function stringToBase64(string, {urlSafe = false} = {}) {
assertString(string);
return uint8ArrayToBase64(stringToUint8Array(string), {urlSafe});
}
export function base64ToString(base64String) {
assertString(base64String);
return uint8ArrayToString(base64ToUint8Array(base64String));
}
const byteToHexLookupTable = Array.from({length: 256}, (_, index) => index.toString(16).padStart(2, '0'));
export function uint8ArrayToHex(array) {
assertUint8Array(array);
// Concatenating a string is faster than using an array.
let hexString = '';
// eslint-disable-next-line unicorn/no-for-loop -- Max performance is critical.
for (let index = 0; index < array.length; index++) {
hexString += byteToHexLookupTable[array[index]];
}
return hexString;
}
const hexToDecimalLookupTable = {
0: 0,
1: 1,
2: 2,
3: 3,
4: 4,
5: 5,
6: 6,
7: 7,
8: 8,
9: 9,
a: 10,
b: 11,
c: 12,
d: 13,
e: 14,
f: 15,
A: 10,
B: 11,
C: 12,
D: 13,
E: 14,
F: 15,
};
export function hexToUint8Array(hexString) {
assertString(hexString);
if (hexString.length % 2 !== 0) {
throw new Error('Invalid Hex string length.');
}
const resultLength = hexString.length / 2;
const bytes = new Uint8Array(resultLength);
for (let index = 0; index < resultLength; index++) {
const highNibble = hexToDecimalLookupTable[hexString[index * 2]];
const lowNibble = hexToDecimalLookupTable[hexString[(index * 2) + 1]];
if (highNibble === undefined || lowNibble === undefined) {
throw new Error(`Invalid Hex character encountered at position ${index * 2}`);
}
bytes[index] = (highNibble << 4) | lowNibble; // eslint-disable-line no-bitwise
}
return bytes;
}
/**
@param {DataView} view
@returns {number}
*/
export function getUintBE(view) {
const {byteLength} = view;
if (byteLength === 6) {
return (view.getUint16(0) * (2 ** 32)) + view.getUint32(2);
}
if (byteLength === 5) {
return (view.getUint8(0) * (2 ** 32)) + view.getUint32(1);
}
if (byteLength === 4) {
return view.getUint32(0);
}
if (byteLength === 3) {
return (view.getUint8(0) * (2 ** 16)) + view.getUint16(1);
}
if (byteLength === 2) {
return view.getUint16(0);
}
if (byteLength === 1) {
return view.getUint8(0);
}
}
/**
@param {Uint8Array} array
@param {Uint8Array} value
@returns {number}
*/
export function indexOf(array, value) {
const arrayLength = array.length;
const valueLength = value.length;
if (valueLength === 0) {
return -1;
}
if (valueLength > arrayLength) {
return -1;
}
const validOffsetLength = arrayLength - valueLength;
for (let index = 0; index <= validOffsetLength; index++) {
let isMatch = true;
for (let index2 = 0; index2 < valueLength; index2++) {
if (array[index + index2] !== value[index2]) {
isMatch = false;
break;
}
}
if (isMatch) {
return index;
}
}
return -1;
}
/**
@param {Uint8Array} array
@param {Uint8Array} value
@returns {boolean}
*/
export function includes(array, value) {
return indexOf(array, value) !== -1;
}