import { getTimestamp } from './date'; import { ChatMessage, ChatUserInfo, ChatAttachment } from '@apsel/apsel-api-ts-axios'; export const MESSAGE_TYPE = { text: 'text', image: 'image', document: 'document', audio: 'audio', }; export const MESSAGE_STATUS = { new: 'new', sent: 'sent', received: 'received', read: 'read', }; /** * A person reference */ export class Person implements ChatUserInfo { /** Build a single person from raw data (usually obtains from the server as JSON) */ public static fromJson(data: any) { return new this( data.id, data.name, ); } public id: string; public name: string; constructor(id: string, name: string) { this.id = id; this.name = name; } } /** * An attachement to a message */ export class Attachment implements ChatAttachment { /** Build a single attachment from raw data (usually obtains from the server as JSON) */ public static fromJson(data: any) { if (data == null) { return undefined; } return new this( data.id, data.key, data.type, data.name, data.path, ); } /** Convert a datachunk array to an attachment */ public static fromRawData(dataChunks: any[], mimeType: string, name: string, createUrl: boolean = false) { const blob = new Blob( dataChunks, { type : mimeType }); return this.fromBlob(blob, mimeType, name, createUrl); } /** Convert a blob to an attachment */ public static fromBlob(blob: Blob, type: string, name: string, createUrl: boolean = false) { const attachment = new this('', '', type, name, ''); attachment.blob = blob; if (createUrl) { attachment.objectUrl = window.URL.createObjectURL(blob); } return attachment; } public id: string; public key: string | undefined; public type: string; public name: string; public path: string; // Front-end only public blob: Blob | null = null; public objectUrl: string | null = null; constructor(id: string, key: string, type: string, name: string, path: string) { this.id = id; this.key = key; this.type = type; this.name = name; this.path = path; } /* Since we have extra value we don't want to save them when serializing */ public toJSON(key: string) { return { key: this.key, type: this.type, name: this.name, path: this.path, }; } } /** * A single message */ export class Message implements ChatMessage { /** Build a single message from raw data (usually obtains from the server as JSON) */ public static fromJson(data: ChatMessage) { // optional // const attachments: Attachment[] = []; if (data.attachment) { data.attachment.forEach( (a) => { attachments.push(new Attachment(a.id, '', a.type, a.name, a.path)); }); } return new this( data.id, data.isEnabled, data.type, data.status, data.timestamp, data.content || '', attachments, data.from, data.to, data.replyTo, data.actions, ); } public static fromInputMessage(input: InputMessage, from: string, to: string) { return new this( '', true, input.type, MESSAGE_STATUS.new, getTimestamp(), input.content, input.attachment, from, to, input.replyTo, input.actions, ); } public id: string; public isEnabled: boolean; public type: string; public status: string; public timestamp: string; public content?: string; public attachment?: Attachment[]; public from: string; public to: string; public replyTo?: string; public actions?: string; constructor(id: string, isEnabled: boolean, type: string, status: string, timestamp: string, content: string, attachment: Attachment[] | undefined, from: string, to: string, replyTo?: string, actions?: string, ) { this.id = id; this.isEnabled = isEnabled; this.type = type; this.status = status; this.timestamp = timestamp; this.content = content; this.attachment = attachment; this.from = from; this.to = to; this.replyTo = replyTo; this.actions = actions; } } /** * A list of chat messages */ export class MessageList implements Iterable { /** Build a list of messages from raw data (usually obtains from the server as JSON) */ public static fromJson(data: any) { const messages: Message[] = new Array(); // Hard validation of the format if (!('messages' in data) || !Array.isArray(data.messages) ) { throw new Error('MessageList.fromJson: `data.messages` is missing or not an array'); } // Load the messages data.messages.forEach((messageData: any) => { messages.push(Message.fromJson(messageData)); }); return new this(messages); } private messages: Message[]; constructor(messages: Message[]) { this.messages = messages; } /** * Number of messages in the list */ get length() { return this.messages.length; } /** * Update an existing message */ public updateMessage(message: Message) { this.addMessage(message); } /** * Add a multiple messages to the list */ public addMessages(messages: Message[]) { messages.forEach( (m) => { this.addMessage(m); } ); } /** * Add a single message to the list */ public addMessage(message: Message) { // Check if already there let oldm = this.messages.find((m) => m.id === message.id); if (!oldm) { // FIXME: dirty need a better reference // maybe it was a new message without ID so search by text oldm = this.messages.find((m) => m.id === '' && m.content === message.content); } if (oldm) { console.log('[ChatMessageList] Updating message'); // The message might have been updated oldm.id = message.id; oldm.isEnabled = message.isEnabled; /* isEnabled */ oldm.type = message.type; oldm.status = message.status; oldm.timestamp = message.timestamp; oldm.content = message.content; oldm.attachment = message.attachment; oldm.from = message.from; oldm.to = message.to; oldm.replyTo = message.replyTo; oldm.actions = message.actions; } else { this.messages.push(message); } } /** * Iterator. * Allors iterating over the list of messages with natural syntax * ``` * let list = MessageList.fromJson({ messages:[] }); * for (let message of list) { console.log(message); } * ``` */ public [Symbol.iterator]() { let counter = -1; return { next: () => { counter++; return { done: counter >= this.messages.length, value: this.messages[counter], }; }, }; } } /** * A message generated by the user input */ export class InputMessage { public static newTextMessage(text: string) { return new this(MESSAGE_TYPE.text, text, undefined, undefined, undefined); } public static newDocumentMessage(title: string, attachment: Attachment) { return new this(MESSAGE_TYPE.document, title, [attachment], undefined, undefined); } public static newAudioMessage(attachment: Attachment) { return new this(MESSAGE_TYPE.audio, '', [attachment], undefined, undefined); } public static newImageMessage(title: string, attachment: Attachment) { return new this(MESSAGE_TYPE.image, title, [attachment], undefined, undefined); } public type: string; public content: string; public attachment: Attachment[] | undefined; public replyTo?: string; public actions?: string; constructor(type: string, content: string, attachment?: Attachment[], replyTo?: string, actions?: string) { this.type = type; this.content = content; this.attachment = attachment; this.replyTo = replyTo; this.actions = actions; } } export function getFileCategory(mimeType: string) { const DOCUMENTS = [ 'text/plain', 'application/pdf', ]; const IMAGES = [ 'image/png', 'image/gif', 'image/jpeg', ]; mimeType = mimeType.toLowerCase().trim(); if ( IMAGES.find( (value) => value === mimeType ) ) { return MESSAGE_TYPE.image; } if ( DOCUMENTS.find( (value) => value === mimeType ) ) { return MESSAGE_TYPE.document; } return null; }