class App { constructor() { this.data = { promises: {getUser: {}, subscribeUser: {}}, users: {}, }; this.socket = io({transports: ['websocket'], upgrade: false}); this.socket.on('PATCH', (patch) => {this.onPatch(patch);}); } getUser(userId) { if (userId in this.data.promises.getUser) { return this.data.promises.getUser[userId]; } this.data.promises.getUser[userId] = new Promise((resolve, reject) => { fetch(`/users/${userId}?backrefs=true&relationships=true`, {headers: {Accept: 'application/json'}}) .then( (response) => { if (response.status === 403) {this.flash('Forbidden', 'error'); reject(response);} return response.json(); }, (response) => { this.flash('Something went wrong', 'error'); reject(response); } ) .then( (user) => { this.data.users[userId] = user; resolve(this.data.users[userId]); }, (error) => { console.error(error, 'error'); reject(error); } ); }); return this.data.promises.getUser[userId]; } subscribeUser(userId) { if (userId in this.data.promises.subscribeUser) { return this.data.promises.subscribeUser[userId]; } this.data.promises.subscribeUser[userId] = new Promise((resolve, reject) => { this.socket.emit('SUBSCRIBE /users/', userId, (response) => { if (response.code === 200) { resolve(response); } else { reject(response); } }); }); return this.data.promises.subscribeUser[userId]; } flash(message, category) { let iconPrefix = ''; switch (category) { case 'corpus': { iconPrefix = 'book'; break; } case 'error': { iconPrefix = 'error'; break; } case 'job': { iconPrefix = 'J'; break; } default: { iconPrefix = 'notifications'; break; } } let toast = M.toast( { html: ` ${iconPrefix}${message} `.trim() } ); let toastCloseActionElement = toast.el.querySelector('.action-button[data-action="close"]'); toastCloseActionElement.addEventListener('click', () => {toast.dismiss();}); } onPatch(patch) { // Filter Patch to only include operations on users that are initialized let regExp = new RegExp(`^/users/(${Object.keys(this.data.users).join('|')})`); let filteredPatch = patch.filter(operation => regExp.test(operation.path)); // Handle job status updates let subRegExp = new RegExp(`^/users/([A-Za-z0-9]*)/jobs/([A-Za-z0-9]*)/status$`); let subFilteredPatch = filteredPatch .filter((operation) => {return operation.op === 'replace';}) .filter((operation) => {return subRegExp.test(operation.path);}); for (let operation of subFilteredPatch) { let [match, userId, jobId] = operation.path.match(subRegExp); this.flash(`[${this.data.users[userId].jobs[jobId].title}] New status: `, 'job'); } // Apply Patch jsonpatch.applyPatch(this.data, filteredPatch); } }