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

191 lines
6.0 KiB
JavaScript

// This is a legacy function.
// Use `findNumbers()` instead.
import {
PLUS_CHARS,
VALID_PUNCTUATION,
VALID_DIGITS,
WHITESPACE
} from '../constants.js'
import parse from '../parse.js'
import { VALID_PHONE_NUMBER_WITH_EXTENSION } from '../helpers/isViablePhoneNumber.js'
import createExtensionPattern from '../helpers/extension/createExtensionPattern.js'
import parsePreCandidate from '../findNumbers/parsePreCandidate.js'
import isValidPreCandidate from '../findNumbers/isValidPreCandidate.js'
import isValidCandidate from '../findNumbers/isValidCandidate.js'
/**
* Regexp of all possible ways to write extensions, for use when parsing. This
* will be run as a case-insensitive regexp match. Wide character versions are
* also provided after each ASCII version. There are three regular expressions
* here. The first covers RFC 3966 format, where the extension is added using
* ';ext='. The second more generic one starts with optional white space and
* ends with an optional full stop (.), followed by zero or more spaces/tabs
* /commas and then the numbers themselves. The other one covers the special
* case of American numbers where the extension is written with a hash at the
* end, such as '- 503#'. Note that the only capturing groups should be around
* the digits that you want to capture as part of the extension, or else parsing
* will fail! We allow two options for representing the accented o - the
* character itself, and one in the unicode decomposed form with the combining
* acute accent.
*/
export const EXTN_PATTERNS_FOR_PARSING = createExtensionPattern('parsing')
const WHITESPACE_IN_THE_BEGINNING_PATTERN = new RegExp('^[' + WHITESPACE + ']+')
const PUNCTUATION_IN_THE_END_PATTERN = new RegExp('[' + VALID_PUNCTUATION + ']+$')
// // Regular expression for getting opening brackets for a valid number
// // found using `PHONE_NUMBER_START_PATTERN` for prepending those brackets to the number.
// const BEFORE_NUMBER_DIGITS_PUNCTUATION = new RegExp('[' + OPENING_BRACKETS + ']+' + '[' + WHITESPACE + ']*' + '$')
const VALID_PRECEDING_CHARACTER_PATTERN = /[^a-zA-Z0-9]/
export default function findPhoneNumbers(text, options, metadata) {
/* istanbul ignore if */
if (options === undefined) {
options = {}
}
const search = new PhoneNumberSearch(text, options, metadata)
const phones = []
while (search.hasNext()) {
phones.push(search.next())
}
return phones
}
/**
* @return ES6 `for ... of` iterator.
*/
export function searchPhoneNumbers(text, options, metadata) {
/* istanbul ignore if */
if (options === undefined) {
options = {}
}
const search = new PhoneNumberSearch(text, options, metadata)
return {
[Symbol.iterator]() {
return {
next: () => {
if (search.hasNext()) {
return {
done: false,
value: search.next()
}
}
return {
done: true
}
}
}
}
}
}
/**
* Extracts a parseable phone number including any opening brackets, etc.
* @param {string} text - Input.
* @return {object} `{ ?number, ?startsAt, ?endsAt }`.
*/
export class PhoneNumberSearch {
constructor(text, options, metadata) {
this.text = text
// If assigning the `{}` default value is moved to the arguments above,
// code coverage would decrease for some weird reason.
this.options = options || {}
this.metadata = metadata
// Iteration tristate.
this.state = 'NOT_READY'
this.regexp = new RegExp(VALID_PHONE_NUMBER_WITH_EXTENSION, 'ig')
}
find() {
const matches = this.regexp.exec(this.text)
if (!matches) {
return
}
let number = matches[0]
let startsAt = matches.index
number = number.replace(WHITESPACE_IN_THE_BEGINNING_PATTERN, '')
startsAt += matches[0].length - number.length
// Fixes not parsing numbers with whitespace in the end.
// Also fixes not parsing numbers with opening parentheses in the end.
// https://github.com/catamphetamine/libphonenumber-js/issues/252
number = number.replace(PUNCTUATION_IN_THE_END_PATTERN, '')
number = parsePreCandidate(number)
const result = this.parseCandidate(number, startsAt)
if (result) {
return result
}
// Tail recursion.
// Try the next one if this one is not a valid phone number.
return this.find()
}
parseCandidate(number, startsAt) {
if (!isValidPreCandidate(number, startsAt, this.text)) {
return
}
// Don't parse phone numbers which are non-phone numbers
// due to being part of something else (e.g. a UUID).
// https://github.com/catamphetamine/libphonenumber-js/issues/213
// Copy-pasted from Google's `PhoneNumberMatcher.js` (`.parseAndValidate()`).
if (!isValidCandidate(number, startsAt, this.text, this.options.extended ? 'POSSIBLE' : 'VALID')) {
return
}
// // Prepend any opening brackets left behind by the
// // `PHONE_NUMBER_START_PATTERN` regexp.
// const text_before_number = text.slice(this.searching_from, startsAt)
// const full_number_starts_at = text_before_number.search(BEFORE_NUMBER_DIGITS_PUNCTUATION)
// if (full_number_starts_at >= 0)
// {
// number = text_before_number.slice(full_number_starts_at) + number
// startsAt = full_number_starts_at
// }
//
// this.searching_from = matches.lastIndex
const result = parse(number, this.options, this.metadata)
if (!result.phone) {
return
}
result.startsAt = startsAt
result.endsAt = startsAt + number.length
return result
}
hasNext() {
if (this.state === 'NOT_READY') {
this.last_match = this.find()
if (this.last_match) {
this.state = 'READY'
} else {
this.state = 'DONE'
}
}
return this.state === 'READY'
}
next() {
// Check the state and find the next match as a side-effect if necessary.
if (!this.hasNext()) {
throw new Error('No next element')
}
// Don't retain that memory any longer than necessary.
const result = this.last_match
this.last_match = null
this.state = 'NOT_READY'
return result
}
}