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

454 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
var crypto = require('crypto');
/**
* Exported function
*
* Options:
*
* - `algorithm` hash algo to be used by this instance: *'sha1', 'md5'
* - `excludeValues` {true|*false} hash object keys, values ignored
* - `encoding` hash encoding, supports 'buffer', '*hex', 'binary', 'base64'
* - `ignoreUnknown` {true|*false} ignore unknown object types
* - `replacer` optional function that replaces values before hashing
* - `respectFunctionProperties` {*true|false} consider function properties when hashing
* - `respectFunctionNames` {*true|false} consider 'name' property of functions for hashing
* - `respectType` {*true|false} Respect special properties (prototype, constructor)
* when hashing to distinguish between types
* - `unorderedArrays` {true|*false} Sort all arrays before hashing
* - `unorderedSets` {*true|false} Sort `Set` and `Map` instances before hashing
* * = default
*
* @param {object} object value to hash
* @param {object} options hashing options
* @return {string} hash value
* @api public
*/
exports = module.exports = objectHash;
function objectHash(object, options){
options = applyDefaults(object, options);
return hash(object, options);
}
/**
* Exported sugar methods
*
* @param {object} object value to hash
* @return {string} hash value
* @api public
*/
exports.sha1 = function(object){
return objectHash(object);
};
exports.keys = function(object){
return objectHash(object, {excludeValues: true, algorithm: 'sha1', encoding: 'hex'});
};
exports.MD5 = function(object){
return objectHash(object, {algorithm: 'md5', encoding: 'hex'});
};
exports.keysMD5 = function(object){
return objectHash(object, {algorithm: 'md5', encoding: 'hex', excludeValues: true});
};
// Internals
var hashes = crypto.getHashes ? crypto.getHashes().slice() : ['sha1', 'md5'];
hashes.push('passthrough');
var encodings = ['buffer', 'hex', 'binary', 'base64'];
function applyDefaults(object, sourceOptions){
sourceOptions = sourceOptions || {};
// create a copy rather than mutating
var options = {};
options.algorithm = sourceOptions.algorithm || 'sha1';
options.encoding = sourceOptions.encoding || 'hex';
options.excludeValues = sourceOptions.excludeValues ? true : false;
options.algorithm = options.algorithm.toLowerCase();
options.encoding = options.encoding.toLowerCase();
options.ignoreUnknown = sourceOptions.ignoreUnknown !== true ? false : true; // default to false
options.respectType = sourceOptions.respectType === false ? false : true; // default to true
options.respectFunctionNames = sourceOptions.respectFunctionNames === false ? false : true;
options.respectFunctionProperties = sourceOptions.respectFunctionProperties === false ? false : true;
options.unorderedArrays = sourceOptions.unorderedArrays !== true ? false : true; // default to false
options.unorderedSets = sourceOptions.unorderedSets === false ? false : true; // default to false
options.unorderedObjects = sourceOptions.unorderedObjects === false ? false : true; // default to true
options.replacer = sourceOptions.replacer || undefined;
options.excludeKeys = sourceOptions.excludeKeys || undefined;
if(typeof object === 'undefined') {
throw new Error('Object argument required.');
}
// if there is a case-insensitive match in the hashes list, accept it
// (i.e. SHA256 for sha256)
for (var i = 0; i < hashes.length; ++i) {
if (hashes[i].toLowerCase() === options.algorithm.toLowerCase()) {
options.algorithm = hashes[i];
}
}
if(hashes.indexOf(options.algorithm) === -1){
throw new Error('Algorithm "' + options.algorithm + '" not supported. ' +
'supported values: ' + hashes.join(', '));
}
if(encodings.indexOf(options.encoding) === -1 &&
options.algorithm !== 'passthrough'){
throw new Error('Encoding "' + options.encoding + '" not supported. ' +
'supported values: ' + encodings.join(', '));
}
return options;
}
/** Check if the given function is a native function */
function isNativeFunction(f) {
if ((typeof f) !== 'function') {
return false;
}
var exp = /^function\s+\w*\s*\(\s*\)\s*{\s+\[native code\]\s+}$/i;
return exp.exec(Function.prototype.toString.call(f)) != null;
}
function hash(object, options) {
var hashingStream;
if (options.algorithm !== 'passthrough') {
hashingStream = crypto.createHash(options.algorithm);
} else {
hashingStream = new PassThrough();
}
if (typeof hashingStream.write === 'undefined') {
hashingStream.write = hashingStream.update;
hashingStream.end = hashingStream.update;
}
var hasher = typeHasher(options, hashingStream);
hasher.dispatch(object);
if (!hashingStream.update) {
hashingStream.end('');
}
if (hashingStream.digest) {
return hashingStream.digest(options.encoding === 'buffer' ? undefined : options.encoding);
}
var buf = hashingStream.read();
if (options.encoding === 'buffer') {
return buf;
}
return buf.toString(options.encoding);
}
/**
* Expose streaming API
*
* @param {object} object Value to serialize
* @param {object} options Options, as for hash()
* @param {object} stream A stream to write the serializiation to
* @api public
*/
exports.writeToStream = function(object, options, stream) {
if (typeof stream === 'undefined') {
stream = options;
options = {};
}
options = applyDefaults(object, options);
return typeHasher(options, stream).dispatch(object);
};
function typeHasher(options, writeTo, context){
context = context || [];
var write = function(str) {
if (writeTo.update) {
return writeTo.update(str, 'utf8');
} else {
return writeTo.write(str, 'utf8');
}
};
return {
dispatch: function(value){
if (options.replacer) {
value = options.replacer(value);
}
var type = typeof value;
if (value === null) {
type = 'null';
}
//console.log("[DEBUG] Dispatch: ", value, "->", type, " -> ", "_" + type);
return this['_' + type](value);
},
_object: function(object) {
var pattern = (/\[object (.*)\]/i);
var objString = Object.prototype.toString.call(object);
var objType = pattern.exec(objString);
if (!objType) { // object type did not match [object ...]
objType = 'unknown:[' + objString + ']';
} else {
objType = objType[1]; // take only the class name
}
objType = objType.toLowerCase();
var objectNumber = null;
if ((objectNumber = context.indexOf(object)) >= 0) {
return this.dispatch('[CIRCULAR:' + objectNumber + ']');
} else {
context.push(object);
}
if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(object)) {
write('buffer:');
return write(object);
}
if(objType !== 'object' && objType !== 'function' && objType !== 'asyncfunction') {
if(this['_' + objType]) {
this['_' + objType](object);
} else if (options.ignoreUnknown) {
return write('[' + objType + ']');
} else {
throw new Error('Unknown object type "' + objType + '"');
}
}else{
var keys = Object.keys(object);
if (options.unorderedObjects) {
keys = keys.sort();
}
// Make sure to incorporate special properties, so
// Types with different prototypes will produce
// a different hash and objects derived from
// different functions (`new Foo`, `new Bar`) will
// produce different hashes.
// We never do this for native functions since some
// seem to break because of that.
if (options.respectType !== false && !isNativeFunction(object)) {
keys.splice(0, 0, 'prototype', '__proto__', 'constructor');
}
if (options.excludeKeys) {
keys = keys.filter(function(key) { return !options.excludeKeys(key); });
}
write('object:' + keys.length + ':');
var self = this;
return keys.forEach(function(key){
self.dispatch(key);
write(':');
if(!options.excludeValues) {
self.dispatch(object[key]);
}
write(',');
});
}
},
_array: function(arr, unordered){
unordered = typeof unordered !== 'undefined' ? unordered :
options.unorderedArrays !== false; // default to options.unorderedArrays
var self = this;
write('array:' + arr.length + ':');
if (!unordered || arr.length <= 1) {
return arr.forEach(function(entry) {
return self.dispatch(entry);
});
}
// the unordered case is a little more complicated:
// since there is no canonical ordering on objects,
// i.e. {a:1} < {a:2} and {a:1} > {a:2} are both false,
// we first serialize each entry using a PassThrough stream
// before sorting.
// also: we cant use the same context array for all entries
// since the order of hashing should *not* matter. instead,
// we keep track of the additions to a copy of the context array
// and add all of them to the global context array when were done
var contextAdditions = [];
var entries = arr.map(function(entry) {
var strm = new PassThrough();
var localContext = context.slice(); // make copy
var hasher = typeHasher(options, strm, localContext);
hasher.dispatch(entry);
// take only what was added to localContext and append it to contextAdditions
contextAdditions = contextAdditions.concat(localContext.slice(context.length));
return strm.read().toString();
});
context = context.concat(contextAdditions);
entries.sort();
return this._array(entries, false);
},
_date: function(date){
return write('date:' + date.toJSON());
},
_symbol: function(sym){
return write('symbol:' + sym.toString());
},
_error: function(err){
return write('error:' + err.toString());
},
_boolean: function(bool){
return write('bool:' + bool.toString());
},
_string: function(string){
write('string:' + string.length + ':');
write(string.toString());
},
_function: function(fn){
write('fn:');
if (isNativeFunction(fn)) {
this.dispatch('[native]');
} else {
this.dispatch(fn.toString());
}
if (options.respectFunctionNames !== false) {
// Make sure we can still distinguish native functions
// by their name, otherwise String and Function will
// have the same hash
this.dispatch("function-name:" + String(fn.name));
}
if (options.respectFunctionProperties) {
this._object(fn);
}
},
_number: function(number){
return write('number:' + number.toString());
},
_xml: function(xml){
return write('xml:' + xml.toString());
},
_null: function() {
return write('Null');
},
_undefined: function() {
return write('Undefined');
},
_regexp: function(regex){
return write('regex:' + regex.toString());
},
_uint8array: function(arr){
write('uint8array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_uint8clampedarray: function(arr){
write('uint8clampedarray:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_int8array: function(arr){
write('int8array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_uint16array: function(arr){
write('uint16array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_int16array: function(arr){
write('int16array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_uint32array: function(arr){
write('uint32array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_int32array: function(arr){
write('int32array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_float32array: function(arr){
write('float32array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_float64array: function(arr){
write('float64array:');
return this.dispatch(Array.prototype.slice.call(arr));
},
_arraybuffer: function(arr){
write('arraybuffer:');
return this.dispatch(new Uint8Array(arr));
},
_url: function(url) {
return write('url:' + url.toString(), 'utf8');
},
_map: function(map) {
write('map:');
var arr = Array.from(map);
return this._array(arr, options.unorderedSets !== false);
},
_set: function(set) {
write('set:');
var arr = Array.from(set);
return this._array(arr, options.unorderedSets !== false);
},
_file: function(file) {
write('file:');
return this.dispatch([file.name, file.size, file.type, file.lastModfied]);
},
_blob: function() {
if (options.ignoreUnknown) {
return write('[blob]');
}
throw Error('Hashing Blob objects is currently not supported\n' +
'(see https://github.com/puleos/object-hash/issues/26)\n' +
'Use "options.replacer" or "options.ignoreUnknown"\n');
},
_domwindow: function() { return write('domwindow'); },
_bigint: function(number){
return write('bigint:' + number.toString());
},
/* Node.js standard native objects */
_process: function() { return write('process'); },
_timer: function() { return write('timer'); },
_pipe: function() { return write('pipe'); },
_tcp: function() { return write('tcp'); },
_udp: function() { return write('udp'); },
_tty: function() { return write('tty'); },
_statwatcher: function() { return write('statwatcher'); },
_securecontext: function() { return write('securecontext'); },
_connection: function() { return write('connection'); },
_zlib: function() { return write('zlib'); },
_context: function() { return write('context'); },
_nodescript: function() { return write('nodescript'); },
_httpparser: function() { return write('httpparser'); },
_dataview: function() { return write('dataview'); },
_signal: function() { return write('signal'); },
_fsevent: function() { return write('fsevent'); },
_tlswrap: function() { return write('tlswrap'); },
};
}
// Mini-implementation of stream.PassThrough
// We are far from having need for the full implementation, and we can
// make assumptions like "many writes, then only one final read"
// and we can ignore encoding specifics
function PassThrough() {
return {
buf: '',
write: function(b) {
this.buf += b;
},
end: function(b) {
this.buf += b;
},
read: function() {
return this.buf;
}
};
}