summary refs log tree commit diff
path: root/src/gateway/opcodes
diff options
context:
space:
mode:
authorTheArcaneBrony <myrainbowdash949@gmail.com>2022-08-23 19:02:05 +0200
committerTheArcaneBrony <myrainbowdash949@gmail.com>2022-08-23 19:02:05 +0200
commitba0ba4b61ede1404454b8ab89bd9da61851f8a6e (patch)
tree254cf1238adea372014b55555e22db6637683d72 /src/gateway/opcodes
parentMerge remote-tracking branch 'Puyodead1/patch/prettier-config' into staging (diff)
parentNew db migration script - multiplatform, fix mariadb migrations (diff)
downloadserver-ba0ba4b61ede1404454b8ab89bd9da61851f8a6e.tar.xz
Merge branch 'dev/cherry-plugins-improvements' into staging
Diffstat (limited to 'src/gateway/opcodes')
-rw-r--r--src/gateway/opcodes/Identify.ts155
-rw-r--r--src/gateway/opcodes/LazyRequest.ts55
-rw-r--r--src/gateway/opcodes/PresenceUpdate.ts11
-rw-r--r--src/gateway/opcodes/Resume.ts4
-rw-r--r--src/gateway/opcodes/VoiceStateUpdate.ts49
-rw-r--r--src/gateway/opcodes/index.ts4
-rw-r--r--src/gateway/opcodes/instanceOf.ts2
7 files changed, 120 insertions, 160 deletions
diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts
index 44db598c..ac6955fd 100644
--- a/src/gateway/opcodes/Identify.ts
+++ b/src/gateway/opcodes/Identify.ts
@@ -1,35 +1,35 @@
-import { WebSocket, Payload } from "@fosscord/gateway";
+import { Payload, WebSocket } from "@fosscord/gateway";
 import {
+	Application,
 	checkToken,
+	Config,
+	emitEvent,
+	EVENTEnum,
+	IdentifySchema,
 	Intents,
 	Member,
-	ReadyEventData,
-	User,
-	Session,
-	EVENTEnum,
-	Config,
+	MemberPrivateProjection,
+	OrmUtils,
+	PresenceUpdateEvent,
+	PrivateSessionProjection,
+	PrivateUserProjection,
 	PublicMember,
 	PublicUser,
-	PrivateUserProjection,
 	ReadState,
-	Application,
-	emitEvent,
+	ReadyEventData,
+	Recipient,
+	Session,
 	SessionsReplace,
-	PrivateSessionProjection,
-	MemberPrivateProjection,
-	PresenceUpdateEvent,
-	UserSettings,
-	IdentifySchema,
+	User,
+	UserSettings
 } from "@fosscord/util";
-import { Send } from "../util/Send";
+import { setupListener } from "../listener/listener";
 import { CLOSECODES, OPCODES } from "../util/Constants";
+import { Send } from "../util/Send";
 import { genSessionId } from "../util/SessionUtils";
-import { setupListener } from "../listener/listener";
+import { check } from "./instanceOf";
 // import experiments from "./experiments.json";
 const experiments: any = [];
-import { check } from "./instanceOf";
-import { Recipient } from "@fosscord/util";
-import { OrmUtils } from "@fosscord/util";
 
 // TODO: user sharding
 // TODO: check privileged intents, if defined in the config
@@ -52,57 +52,44 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 
 	const session_id = genSessionId();
 	this.session_id = session_id; //Set the session of the WebSocket object
-	
-	const [user, read_states, members, recipients, session, application] =
-		await Promise.all([
-			User.findOneOrFail({
-				where: { id: this.user_id },
-				relations: ["relationships", "relationships.to", "settings"],
-				select: [...PrivateUserProjection, "relationships"],
-			}),
-			ReadState.find({ where: { user_id: this.user_id } }),
-			Member.find({
-				where: { id: this.user_id },
-				select: MemberPrivateProjection,
-				relations: [
-					"guild",
-					"guild.channels",
-					"guild.emojis",
-					"guild.emojis.user",
-					"guild.roles",
-					"guild.stickers",
-					"user",
-					"roles",
-				],
-			}),
-			Recipient.find({
-				where: { user_id: this.user_id, closed: false },
-				relations: [
-					"channel",
-					"channel.recipients",
-					"channel.recipients.user",
-				],
-				// TODO: public user selection
-			}),
-			// save the session and delete it when the websocket is closed
-			await OrmUtils.mergeDeep(new Session(), {
-				user_id: this.user_id,
-				session_id: session_id,
-				// TODO: check if status is only one of: online, dnd, offline, idle
-				status: identify.presence?.status || "offline", //does the session always start as online?
-				client_info: {
-					//TODO read from identity
-					client: "desktop",
-					os: identify.properties?.os,
-					version: 0,
-				},
-				activities: [],
-			}).save(),
-			Application.findOne({ where: { id: this.user_id } }),
-		]);
+
+	const [user, read_states, members, recipients, session, application] = await Promise.all([
+		User.findOneOrFail({
+			where: { id: this.user_id },
+			relations: ["relationships", "relationships.to", "settings"],
+			select: [...PrivateUserProjection, "relationships"]
+		}),
+		ReadState.find({ where: { user_id: this.user_id } }),
+		Member.find({
+			where: { id: this.user_id },
+			select: MemberPrivateProjection,
+			relations: ["guild", "guild.channels", "guild.emojis", "guild.emojis.user", "guild.roles", "guild.stickers", "user", "roles"]
+		}),
+		Recipient.find({
+			where: { user_id: this.user_id, closed: false },
+			relations: ["channel", "channel.recipients", "channel.recipients.user"]
+			// TODO: public user selection
+		}),
+		// save the session and delete it when the websocket is closed
+		await OrmUtils.mergeDeep(new Session(), {
+			user_id: this.user_id,
+			session_id: session_id,
+			// TODO: check if status is only one of: online, dnd, offline, idle
+			status: identify.presence?.status || "offline", //does the session always start as online?
+			client_info: {
+				//TODO read from identity
+				client: "desktop",
+				os: identify.properties?.os,
+				version: 0
+			},
+			activities: []
+		}).save(),
+		Application.findOne({ where: { id: this.user_id } })
+	]);
 
 	if (!user) return this.close(CLOSECODES.Authentication_failed);
-	if (!user.settings) { //settings may not exist after updating...
+	if (!user.settings) {
+		//settings may not exist after updating...
 		user.settings = new UserSettings();
 		user.settings.id = user.id;
 		//await (user.settings as UserSettings).save();
@@ -132,8 +119,8 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 				...x,
 				roles: x.roles.map((x) => x.id),
 				settings: undefined,
-				guild: undefined,
-			},
+				guild: undefined
+			}
 		];
 	}) as PublicMember[][];
 	let guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at }));
@@ -146,7 +133,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 					op: OPCODES.Dispatch,
 					t: EVENTEnum.GuildCreate,
 					s: this.sequence++,
-					d: guild,
+					d: guild
 				});
 			}, 500);
 			return { id: guild.id, unavailable: true };
@@ -163,9 +150,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 		//TODO is this needed? check if users in group dm that are not friends are sent in the READY event
 		users = users.concat(x.channel.recipients as unknown as User[]);
 		if (x.channel.isDm()) {
-			x.channel.recipients = x.channel.recipients!.filter(
-				(x) => x.id !== this.user_id
-			);
+			x.channel.recipients = x.channel.recipients!.filter((x) => x.id !== this.user_id);
 		}
 		return x.channel;
 	});
@@ -192,8 +177,8 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 			user_id: this.user_id,
 			data: await Session.find({
 				where: { user_id: this.user_id },
-				select: PrivateSessionProjection,
-			}),
+				select: PrivateSessionProjection
+			})
 		} as SessionsReplace);
 		emitEvent({
 			event: "PRESENCE_UPDATE",
@@ -202,8 +187,8 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 				user: await User.getPublicUser(this.user_id),
 				activities: session.activities,
 				client_status: session?.client_info,
-				status: session.status,
-			},
+				status: session.status
+			}
 		} as PresenceUpdateEvent);
 	});
 
@@ -238,7 +223,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 
 	const d: ReadyEventData = {
 		v: 8,
-		application: {id: application?.id??'', flags: application?.flags??0}, //TODO: check this code!
+		application: { id: application?.id ?? "", flags: application?.flags ?? 0 }, //TODO: check this code!
 		user: privateUser,
 		user_settings: user.settings,
 		// @ts-ignore
@@ -255,12 +240,12 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 		read_state: {
 			entries: read_states,
 			partial: false,
-			version: 304128,
+			version: 304128
 		},
 		user_guild_settings: {
 			entries: user_guild_settings_entries,
 			partial: false, // TODO partial
-			version: 642,
+			version: 642
 		},
 		private_channels: channels,
 		session_id: session_id,
@@ -268,8 +253,8 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 		connected_accounts: [], // TODO
 		consents: {
 			personalization: {
-				consented: false, // TODO
-			},
+				consented: false // TODO
+			}
 		},
 		country_code: user.settings.locale,
 		friend_suggestion_count: 0, // TODO
@@ -277,7 +262,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 		experiments: experiments, // TODO
 		guild_join_requests: [], // TODO what is this?
 		users: users.filter((x) => x).unique(),
-		merged_members: merged_members,
+		merged_members: merged_members
 		// shard // TODO: only for user sharding
 	};
 
@@ -286,7 +271,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 		op: OPCODES.Dispatch,
 		t: EVENTEnum.Ready,
 		s: this.sequence++,
-		d,
+		d
 	});
 
 	//TODO send READY_SUPPLEMENTAL
diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts
index 74996f5b..ea69779e 100644
--- a/src/gateway/opcodes/LazyRequest.ts
+++ b/src/gateway/opcodes/LazyRequest.ts
@@ -1,9 +1,8 @@
-import { getPermission, listenEvent, Member, Role, getOrInitialiseDatabase, LazyRequest } from "@fosscord/util";
-import { Send } from "../util/Send";
+import { handlePresenceUpdate, Payload, WebSocket } from "@fosscord/gateway";
+import { getOrInitialiseDatabase, getPermission, LazyRequest, listenEvent, Member, Role } from "@fosscord/util";
 import { OPCODES } from "../util/Constants";
-import { WebSocket, Payload, handlePresenceUpdate } from "@fosscord/gateway";
+import { Send } from "../util/Send";
 import { check } from "./instanceOf";
-import { getRepository } from "typeorm";
 
 // TODO: only show roles/members that have access to this channel
 // TODO: config: to list all members (even those who are offline) sorted by role, or just those who are online
@@ -16,16 +15,16 @@ async function getMembers(guild_id: string, range: [number, number]) {
 	// TODO: wait for typeorm to implement ordering for .find queries https://github.com/typeorm/typeorm/issues/2620
 	// TODO: rewrite this, released in 0.3.0
 
-	let members: Member[] = await (await getOrInitialiseDatabase()).getRepository(Member)
+	let members: Member[] = await (
+		await getOrInitialiseDatabase()
+	)
+		.getRepository(Member)
 		.createQueryBuilder("member")
 		.where("member.guild_id = :guild_id", { guild_id })
 		.leftJoinAndSelect("member.roles", "role")
 		.leftJoinAndSelect("member.user", "user")
 		.leftJoinAndSelect("user.sessions", "session")
-		.addSelect(
-			"CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END",
-			"_status"
-		)
+		.addSelect("CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END", "_status")
 		.orderBy("role.position", "DESC")
 		.addOrderBy("_status", "DESC")
 		.addOrderBy("user.username", "ASC")
@@ -44,21 +43,17 @@ async function getMembers(guild_id: string, range: [number, number]) {
 
 	for (const role of member_roles) {
 		// @ts-ignore
-		const [role_members, other_members] = partition(members, (m: Member) =>
-			m.roles.find((r) => r.id === role.id)
-		);
+		const [role_members, other_members] = partition(members, (m: Member) => m.roles.find((r) => r.id === role.id));
 		const group = {
 			count: role_members.length,
-			id: role.id === guild_id ? "online" : role.id,
+			id: role.id === guild_id ? "online" : role.id
 		};
 
 		items.push({ group });
 		groups.push(group);
 
 		for (const member of role_members) {
-			const roles = member.roles
-				.filter((x: Role) => x.id !== guild_id)
-				.map((x: Role) => x.id);
+			const roles = member.roles.filter((x: Role) => x.id !== guild_id).map((x: Role) => x.id);
 
 			const session = member.user.sessions.first();
 
@@ -71,10 +66,10 @@ async function getMembers(guild_id: string, range: [number, number]) {
 					presence: {
 						...session,
 						activities: session?.activities || [],
-						user: { id: member.user.id },
-					},
-				},
-			}
+						user: { id: member.user.id }
+					}
+				}
+			};
 
 			if (!member?.user?.sessions || !member.user.sessions.length) {
 				offlineItems.push(item);
@@ -90,7 +85,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
 	if (offlineItems.length) {
 		const group = {
 			count: offlineItems.length,
-			id: "offline",
+			id: "offline"
 		};
 		items.push({ group });
 		groups.push(group);
@@ -102,7 +97,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
 		items,
 		groups,
 		range,
-		members: items.map((x) => 'member' in x ? x.member : undefined).filter(x => !!x),
+		members: items.map((x) => ("member" in x ? x.member : undefined)).filter((x) => !!x)
 	};
 }
 
@@ -129,11 +124,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
 		op.members.forEach(async (member) => {
 			if (this.events[member.user.id]) return; // already subscribed as friend
 			if (this.member_events[member.user.id]) return; // already subscribed in member list
-			this.member_events[member.user.id] = await listenEvent(
-				member.user.id,
-				handlePresenceUpdate.bind(this),
-				this.listen_options
-			);
+			this.member_events[member.user.id] = await listenEvent(member.user.id, handlePresenceUpdate.bind(this), this.listen_options);
 		});
 	});
 
@@ -145,7 +136,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
 			ops: ops.map((x) => ({
 				items: x.items,
 				op: "SYNC",
-				range: x.range,
+				range: x.range
 			})),
 			online_count: member_count,
 			member_count,
@@ -154,8 +145,8 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
 			groups: ops
 				.map((x) => x.groups)
 				.flat()
-				.unique(),
-		},
+				.unique()
+		}
 	});
 }
 
@@ -164,9 +155,7 @@ function partition<T>(array: T[], isValid: Function) {
 	return array.reduce(
 		// @ts-ignore
 		([pass, fail], elem) => {
-			return isValid(elem)
-				? [[...pass, elem], fail]
-				: [pass, [...fail, elem]];
+			return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
 		},
 		[[], []]
 	);
diff --git a/src/gateway/opcodes/PresenceUpdate.ts b/src/gateway/opcodes/PresenceUpdate.ts
index f31c9161..ad712234 100644
--- a/src/gateway/opcodes/PresenceUpdate.ts
+++ b/src/gateway/opcodes/PresenceUpdate.ts
@@ -1,4 +1,4 @@
-import { WebSocket, Payload } from "@fosscord/gateway";
+import { Payload, WebSocket } from "@fosscord/gateway";
 import { ActivitySchema, emitEvent, PresenceUpdateEvent, Session, User } from "@fosscord/util";
 import { check } from "./instanceOf";
 
@@ -6,10 +6,7 @@ export async function onPresenceUpdate(this: WebSocket, { d }: Payload) {
 	check.call(this, ActivitySchema, d);
 	const presence = d as ActivitySchema;
 
-	await Session.update(
-		{ session_id: this.session_id },
-		{ status: presence.status, activities: presence.activities }
-	);
+	await Session.update({ session_id: this.session_id }, { status: presence.status, activities: presence.activities });
 
 	await emitEvent({
 		event: "PRESENCE_UPDATE",
@@ -18,7 +15,7 @@ export async function onPresenceUpdate(this: WebSocket, { d }: Payload) {
 			user: await User.getPublicUser(this.user_id),
 			activities: presence.activities,
 			client_status: {}, // TODO:
-			status: presence.status,
-		},
+			status: presence.status
+		}
 	} as PresenceUpdateEvent);
 }
diff --git a/src/gateway/opcodes/Resume.ts b/src/gateway/opcodes/Resume.ts
index 42dc586d..f320864b 100644
--- a/src/gateway/opcodes/Resume.ts
+++ b/src/gateway/opcodes/Resume.ts
@@ -1,11 +1,11 @@
-import { WebSocket, Payload } from "@fosscord/gateway";
+import { Payload, WebSocket } from "@fosscord/gateway";
 import { Send } from "../util/Send";
 
 export async function onResume(this: WebSocket, data: Payload) {
 	console.log("Got Resume -> cancel not implemented");
 	await Send(this, {
 		op: 9,
-		d: false,
+		d: false
 	});
 
 	// return this.close(CLOSECODES.Invalid_session);
diff --git a/src/gateway/opcodes/VoiceStateUpdate.ts b/src/gateway/opcodes/VoiceStateUpdate.ts
index c4297a68..20502584 100644
--- a/src/gateway/opcodes/VoiceStateUpdate.ts
+++ b/src/gateway/opcodes/VoiceStateUpdate.ts
@@ -1,18 +1,18 @@
 import { Payload, WebSocket } from "@fosscord/gateway";
-import { genVoiceToken } from "../util/SessionUtils";
-import { check } from "./instanceOf";
 import {
 	Config,
 	emitEvent,
 	Guild,
 	Member,
+	OrmUtils,
+	Region,
 	VoiceServerUpdateEvent,
 	VoiceState,
 	VoiceStateUpdateEvent,
-	VoiceStateUpdateSchema,
+	VoiceStateUpdateSchema
 } from "@fosscord/util";
-import { OrmUtils } from "@fosscord/util";
-import { Region } from "@fosscord/util";
+import { genVoiceToken } from "../util/SessionUtils";
+import { check } from "./instanceOf";
 // TODO: check if a voice server is setup
 // Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not.
 
@@ -20,7 +20,7 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 	check.call(this, VoiceStateUpdateSchema, data.d);
 	const body = data.d as VoiceStateUpdateSchema;
 
-	if(body.guild_id == null) {
+	if (body.guild_id == null) {
 		console.log(`[Gateway] VoiceStateUpdate called with guild_id == null by user ${this.user_id}!`);
 		return;
 	}
@@ -28,26 +28,20 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 	let voiceState: VoiceState;
 	try {
 		voiceState = await VoiceState.findOneOrFail({
-			where: { user_id: this.user_id },
+			where: { user_id: this.user_id }
 		});
-		if (
-			voiceState.session_id !== this.session_id &&
-			body.channel_id === null
-		) {
+		if (voiceState.session_id !== this.session_id && body.channel_id === null) {
 			//Should we also check guild_id === null?
 			//changing deaf or mute on a client that's not the one with the same session of the voicestate in the database should be ignored
 			return;
 		}
 
 		//If a user change voice channel between guild we should send a left event first
-		if (
-			voiceState.guild_id !== body.guild_id &&
-			voiceState.session_id === this.session_id
-		) {
+		if (voiceState.guild_id !== body.guild_id && voiceState.session_id === this.session_id) {
 			await emitEvent({
 				event: "VOICE_STATE_UPDATE",
 				data: { ...voiceState, channel_id: null },
-				guild_id: voiceState.guild_id,
+				guild_id: voiceState.guild_id
 			});
 		}
 
@@ -60,7 +54,7 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 			user_id: this.user_id,
 			deaf: false,
 			mute: false,
-			suppress: false,
+			suppress: false
 		});
 	}
 
@@ -69,12 +63,11 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 	//TODO this may fail
 	voiceState.member = await Member.findOneOrFail({
 		where: { id: voiceState.user_id, guild_id: voiceState.guild_id },
-		relations: ["user", "roles"],
+		relations: ["user", "roles"]
 	});
 
 	//If the session changed we generate a new token
-	if (voiceState.session_id !== this.session_id)
-		voiceState.token = genVoiceToken();
+	if (voiceState.session_id !== this.session_id) voiceState.token = genVoiceToken();
 	voiceState.session_id = this.session_id;
 
 	const { id, ...newObj } = voiceState;
@@ -84,8 +77,8 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 		emitEvent({
 			event: "VOICE_STATE_UPDATE",
 			data: newObj,
-			guild_id: voiceState.guild_id,
-		} as VoiceStateUpdateEvent),
+			guild_id: voiceState.guild_id
+		} as VoiceStateUpdateEvent)
 	]);
 
 	//If it's null it means that we are leaving the channel and this event is not needed
@@ -94,13 +87,9 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 		const regions = Config.get().regions;
 		let guildRegion: Region;
 		if (guild && guild.region) {
-			guildRegion = regions.available.filter(
-				(r) => r.id === guild.region
-			)[0];
+			guildRegion = regions.available.filter((r) => r.id === guild.region)[0];
 		} else {
-			guildRegion = regions.available.filter(
-				(r) => r.id === regions.default
-			)[0];
+			guildRegion = regions.available.filter((r) => r.id === regions.default)[0];
 		}
 
 		await emitEvent({
@@ -108,9 +97,9 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) {
 			data: {
 				token: voiceState.token,
 				guild_id: voiceState.guild_id,
-				endpoint: guildRegion.endpoint,
+				endpoint: guildRegion.endpoint
 			},
-			guild_id: voiceState.guild_id,
+			guild_id: voiceState.guild_id
 		} as VoiceServerUpdateEvent);
 	}
 }
diff --git a/src/gateway/opcodes/index.ts b/src/gateway/opcodes/index.ts
index 027739db..d5dc7de1 100644
--- a/src/gateway/opcodes/index.ts
+++ b/src/gateway/opcodes/index.ts
@@ -1,4 +1,4 @@
-import { WebSocket, Payload } from "@fosscord/gateway";
+import { Payload, WebSocket } from "@fosscord/gateway";
 import { onHeartbeat } from "./Heartbeat";
 import { onIdentify } from "./Identify";
 import { onLazyRequest } from "./LazyRequest";
@@ -21,5 +21,5 @@ export default {
 	// 9: Invalid Session
 	// 10: Hello
 	// 13: Dm_update
-	14: onLazyRequest,
+	14: onLazyRequest
 };
diff --git a/src/gateway/opcodes/instanceOf.ts b/src/gateway/opcodes/instanceOf.ts
index eb6f6ea1..95d74963 100644
--- a/src/gateway/opcodes/instanceOf.ts
+++ b/src/gateway/opcodes/instanceOf.ts
@@ -1,5 +1,5 @@
-import { instanceOf } from "@fosscord/util";
 import { WebSocket } from "@fosscord/gateway";
+import { instanceOf } from "@fosscord/util";
 import { CLOSECODES } from "../util/Constants";
 
 export function check(this: WebSocket, schema: any, data: any) {