source/logic/model.ts
6faf71a8
 namespace ns_model
 {
 	
 	/**
 	 * adds a listener for a certain incident
 	 */
 	export function listen
 	(
 		model: type_model,
 		incident: string,
 		handler: (details?: any)=>void
 	): void
 	{
 		if (! model.listeners.hasOwnProperty(incident))
 		{
 			model.listeners[incident] = [];
 		}
 		else
 		{
 			// do nothing
 		}
 		model.listeners[incident].push(handler);
 	}
 
 
 	/**
 	 * sends a notification to all listeners for a certain incident
 	 */
 	function notify
 	(
 		model: type_model,
 		incident: string,
 		details: any = null
 	): void
 	{
 		if (model.listeners.hasOwnProperty(incident))
 		{
 			for (const handler of model.listeners[incident])
 			{
 				handler(details);
 			}
 		}
 	}
 
 
 	/**
 	 * sets the state
 	 */
 	export function set_state
 	(
 		model: type_model,
 		state: enum_state
 	): void
 	{
 		model.state = state;
 		notify(model, "state_changed");
 	}
 
 
 	/**
 	 * sets the active spot (channel or query)
 	 */
 	export function set_active
 	(
 		model: type_model,
 		spot: type_spot
 	): void
 	{
 		model.active = spot;
 		notify(model, "spots_changed");
 		notify(model, "entries_changed");
 		notify(model, "users_changed");
 	}
 
 
 	/**
 	 * updates the model according to a list of events
 	 */
 	function process_events
 	(
 		model: type_model,
 		events: Array<type_event>
 	): void
 	{
 		let shall_update_spots: boolean = false;
 		let shall_update_entries: boolean = false;
 		let shall_update_users: boolean = false;
 		
 		for (const event of events)
 		{
 			switch (event.kind)
 			{
 				default:
 				{
 					console.warn("unhandled event kind: " + event.kind);
 					break;
 				}
 				case "userlist":
 				{
 					model.channels[event.data["channel"]].users = event.data["users"];
 					shall_update_users = true;
 					break;
 				}
 				case "message_channel":
 				{
 					model.channels[event.data["channel"]].entries.push
 					({
 						"timestamp": event.timestamp,
 						"sender": event.data["sender"],
 						"content": event.data["content"],
 					});
 					shall_update_entries = true;
 					break;
 				}
 				case "message_query":
 				{
 					if (! model.queries.hasOwnProperty(event.data["user_name"]))
 					{
 						model.queries[event.data["user_name"]] = {"entries": []};
 						shall_update_spots = true;
 					}
 					else
 					{
 						// do nothing
 					}
 					model.queries[event.data["user_name"]].entries.push
 					({
 						"timestamp": event.timestamp,
 						"sender": event.data["sender"],
 						"content": event.data["content"],
 					});
 					shall_update_entries = true;
 					break;
 				}
 			}
 		}
 		
 		if (shall_update_spots) notify(model, "spots_changed");
 		if (shall_update_entries) notify(model, "entries_changed");
 		if (shall_update_users) notify(model, "users_changed");
 	}
 
 
 	/**
 	 * establishes the connection
 	 */
 	export async function connect
 	(
 		conf: type_conf,
 		model: type_model,
 		nickname: string,
 		channel_names: Array<string>
 	): Promise<void>
 	{
 		set_state(model, enum_state.connecting);
 		const connection_id: string = await backend_call
 		(
 			conf,
 			model.connection_id,
 			"connect",
 			{
 				"server": conf.irc.server,
 				"channels": channel_names,
 				"nickname": nickname,
 			}
 		);
 		model.connection_id = connection_id;
 		model.nickname = nickname;
 		for (const channel_name of channel_names)
 		{
 			model.channels[channel_name] =
 			{
 				"users": [],
 				"entries": [],
 			};
 		}
 		if (channel_names.length > 0)
 		{
 			set_active(model, {"kind": "channel", "name": channel_names[0]});
 		}
 		return Promise.resolve<void>(undefined);
 	}
 
 
 	/**
 	 * closes the connection
 	 */
 	export async function disconnect
 	(
 		conf: type_conf,
 		model: type_model
 	): Promise<void>
 	{
 		await backend_call
 		(
 			conf,
 			model.connection_id,
 			"disconnect",
 			null
 		);
 		set_state(model, enum_state.offline);
 		model.connection_id = null;
 		return Promise.resolve<void>(undefined);
 	}
 
 
 	/**
 	 * adds a client side message
 	 */
 	export function send
 	(
 		conf: type_conf,
 		model: type_model,
 		content: string
 	): void
 	{
 		switch (model.active.kind)
 		{
 			case "channel":
 			{
 				backend_call
 				(
 					conf,
 					model.connection_id,
 					"send_channel",
 					{
 						"channel": model.active.name,
 						"content": content,
 					}
 				);
 				const event: type_event =
 				{
 					"timestamp": get_timestamp(),
 					"kind": "message_channel",
 					"data":
 					{
 						"channel": model.active.name,
 						"sender": model.nickname,
 						"content": content,
 					}
 				};
 				process_events(model, [event]);
 				notify(model, "entries_changed");
 				notify(model, "message_sent");
 				break;
 			}
 			case "query":
 			{
 				backend_call
 				(
 					conf,
 					model.connection_id,
 					"send_query",
 					{
 						"receiver": model.active.name,
 						"content": content,
 					}
 				);
 				const event: type_event =
 				{
 					"timestamp": get_timestamp(),
 					"kind": "message_query",
 					"data":
 					{
 						"user_name": model.active.name,
 						"sender": model.nickname,
 						"content": content,
 					}
 				};
 				process_events(model, [event]);
 				notify(model, "entries_changed");
 				notify(model, "message_sent");
 				break;
 			}
 		}
 	}
 	
 	
de5e1e21
 	/**
 	 * adds a query to a user as spot
 	 */
 	export function open_query
 	(
 		model: type_model,
 		user_name: string
 	): void
 	{
 		if ((user_name !== model.nickname) && (! model.queries.hasOwnProperty(user_name)))
 		{
 			model.queries[user_name] = {"entries": []};
 			notify(model, "spots_changed");
 		}
 		else
 		{
 			// do nothing
 		}
 	}
 	
 	
6faf71a8
 	/**
 	 * sets up the model
 	 */
 	export function setup
 	(
 		conf: type_conf,
 		model: type_model
 	): void
 	{
 		setInterval
 		(
 			async () =>
 			{
 				switch (model.state)
 				{
 					default:
 					{
 						throw (new Error(`invalid state '${model.state}'`));
 						break;
 					}
 					case enum_state.offline:
 					{
 						// do nothing
 						break;
 					}
 					case enum_state.connecting:
 					{
 						const ready: boolean = await backend_call(conf, model.connection_id, "check", null);
 						if (ready)
 						{
 							set_state(model, enum_state.online);
 						}
 						else
 						{
 							// do nothing
 						}
 						break;
 					}
 					case enum_state.online:
 					{
 						const events: Array<type_event> = await backend_call(conf, model.connection_id, "fetch", null);
 						process_events(model, events);
 						break;
 					}
 				}
 			},
 			conf.settings.poll_interval_in_milliseconds
 		);
 		set_state(model, enum_state.offline);
 	}
 	
 }