Home Reference Source

src/meetings/MeetingManager.ts

import {List} from 'immutable';
import {BehaviorSubject, Observable} from 'rxjs';
import Semaphore from 'semaphore-async-await';

import {MeetingService, Meeting as RawMeeting} from '../wac-proxy/wac-stack/MeetingService';

import {Meeting} from './Meeting';
import {MeetingParticipant} from './MeetingParticipant';

export interface MeetingUpdateData
	extends Partial<Pick<Meeting, 'name' | 'validSince' | 'validUntil'>> {
	id: string;
	participants?: Iterable<MeetingParticipant>;
	password?: string;
}

const byValidSince = (a: Meeting, b: Meeting): number => a.validSince - b.validSince;

/**
 * ContactManager instance is obtained by calling the {@link Session#getMeetingManager}
 * method of {@link Session} and must not be directly instantiated.
 */
export class MeetingManager {
	/** @private */
	private initLock = new Semaphore(1);
	/** @private */
	private meetingService: MeetingService;
	/** @private */
	private meetingsSubject = new BehaviorSubject(List<Meeting>());
	/** @private */
	private meetings$ = this.meetingsSubject.asObservable();

	/** @ignore */
	constructor(meetingService: MeetingService) {
		/** @private */
		this.meetingService = meetingService;
		this.onMeeting = this.onMeeting.bind(this);
		this.onMeetingDelete = this.onMeetingDelete.bind(this);
	}

	/** @ignore */
	async init(): Promise<void> {
		await this.initLock.wait();
		this.meetingService.emitter.on('create', this.onMeeting);
		this.meetingService.emitter.on('delete', this.onMeetingDelete);
		this.meetingService.emitter.on('update', this.onMeeting);
		const rawMeetings = await this.meetingService.get();
		const meetings = List(rawMeetings)
			.map(m => new Meeting(m))
			.sort(byValidSince);
		this.meetingsSubject.next(meetings);
		this.initLock.release();
	}

	/** @ignore */
	async uninit(): Promise<void> {
		await this.initLock.wait();
		this.meetingService.emitter.off('create', this.onMeeting);
		this.meetingService.emitter.off('delete', this.onMeetingDelete);
		this.meetingService.emitter.off('update', this.onMeeting);
		this.meetingsSubject.next(List());
		this.initLock.release();
	}

	/**
	 * Retrieves a list with the available meetings
	 * @return {Observable<ImmutableList<Meeting>>}
	 */
	getMeetings$(): Observable<List<Meeting>> {
		return this.meetings$;
	}

	/**
	 * Retrieves a meeting with the following id
	 * @param {string} meetingId Id of the meeeting to retrieve
	 * @return {Observable<ImmutableList<Meeting>>}
	 */
	async getMeeting(meetingId: string): Promise<Meeting> {
		const meeting = await this.meetingService.getById(meetingId);
		return new Meeting(meeting);
	}

	/**
	 * Creates a meeting
	 * @param {string} name The name of the meeting.
	 * @param {Iterable<MeetingParticipant>} participants The participants of the new meeting session.
	 * @param {number} validSince The timestamp of the moment when the meeting starts.
	 * @param {number} [validUntil] The timestamp until the meeting is valid.
	 * @param {boolean} [sendSms=false] If true, an invitation for the meeting will be sent in an SMS by the server.
	 * @param {string} [password] Password for meeting access protection
	 * @return {Promise<Meeting>} The just created meeting
	 */
	async createMeeting(
		name: string,
		participants: Iterable<MeetingParticipant>,
		validSince: number,
		validUntil?: number,
		sendSms = false,
		password?: string,
	): Promise<Meeting> {
		const meetingData = await this.meetingService.create(
			{
				name,
				participants: [...participants],
				validSince,
				validUntil,
				password,
			},
			sendSms,
		);
		const meeting = new Meeting(meetingData);
		this.notifyMeeting(meeting);
		return meeting;
	}

	/**
	 * Updates a meeting
	 * @param {Meeting} meeting
	 * @param {boolean} [sendSms=false] Flag used to decide whether to send an SMS message or not
	 * @return {Promise<Meeting>}
	 */
	async saveMeeting(meeting: MeetingUpdateData, sendSms = false): Promise<Meeting> {
		const participants = meeting.participants ? {participants: [...meeting.participants]} : {};
		const rawMeeting = await this.meetingService.update(
			meeting.id,
			{
				...participants,
				name: meeting.name,
				validSince: meeting.validSince,
				validUntil: meeting.validUntil,
				password: meeting.password,
			},
			sendSms,
		);
		const newMeeting = new Meeting(rawMeeting);
		this.notifyMeeting(newMeeting);
		return newMeeting;
	}

	/**
	 * Removes a meeting given its id
	 * @param {object} meeting
	 * @param {string} meeting.id
	 */
	async removeMeeting({id}: Pick<Meeting, 'id'>): Promise<void> {
		await this.meetingService.delete(id);
		const meetings = this.meetingsSubject.value.filter(m => m.id !== id);
		this.meetingsSubject.next(meetings);
	}

	/** @private */
	private onMeeting(rawMeeting: RawMeeting): void {
		const meeting = new Meeting(rawMeeting);
		this.notifyMeeting(meeting);
	}

	/** @private */
	private onMeetingDelete({id}: {id: string}): void {
		const meetings = this.meetingsSubject.value;
		const updatedMeetings = meetings.filter(m => m.id !== id);
		this.meetingsSubject.next(updatedMeetings);
	}

	/** @private */
	private notifyMeeting(meeting: Meeting): void {
		const meetings = this.meetingsSubject.value;
		const index = meetings.findIndex(m => m.id === meeting.id);
		const updatedMeetings = index < 0 ? meetings.push(meeting) : meetings.set(index, meeting);
		const sortedMeetings = updatedMeetings.sort(byValidSince);
		this.meetingsSubject.next(sortedMeetings);
	}
}