summary refs log tree commit diff
path: root/src/gateway
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/gateway/Server.ts (renamed from gateway/src/Server.ts)0
-rw-r--r--src/gateway/events/Close.ts (renamed from gateway/src/events/Close.ts)0
-rw-r--r--src/gateway/events/Connection.ts (renamed from gateway/src/events/Connection.ts)0
-rw-r--r--src/gateway/events/Message.ts (renamed from gateway/src/events/Message.ts)3
-rw-r--r--src/gateway/index.ts (renamed from gateway/src/index.ts)0
-rw-r--r--src/gateway/listener/listener.ts (renamed from gateway/src/listener/listener.ts)0
-rw-r--r--src/gateway/opcodes/Heartbeat.ts (renamed from gateway/src/opcodes/Heartbeat.ts)0
-rw-r--r--src/gateway/opcodes/Identify.ts298
-rw-r--r--src/gateway/opcodes/LazyRequest.ts (renamed from gateway/src/opcodes/LazyRequest.ts)2
-rw-r--r--src/gateway/opcodes/PresenceUpdate.ts (renamed from gateway/src/opcodes/PresenceUpdate.ts)0
-rw-r--r--src/gateway/opcodes/RequestGuildMembers.ts (renamed from gateway/src/opcodes/RequestGuildMembers.ts)0
-rw-r--r--src/gateway/opcodes/Resume.ts (renamed from gateway/src/opcodes/Resume.ts)0
-rw-r--r--src/gateway/opcodes/VoiceStateUpdate.ts (renamed from gateway/src/opcodes/VoiceStateUpdate.ts)2
-rw-r--r--src/gateway/opcodes/experiments.json (renamed from gateway/src/opcodes/experiments.json)0
-rw-r--r--src/gateway/opcodes/index.ts (renamed from gateway/src/opcodes/index.ts)0
-rw-r--r--src/gateway/opcodes/instanceOf.ts (renamed from gateway/src/opcodes/instanceOf.ts)0
-rw-r--r--src/gateway/start.ts (renamed from gateway/src/start.ts)0
-rw-r--r--src/gateway/util/Constants.ts (renamed from gateway/src/util/Constants.ts)0
-rw-r--r--src/gateway/util/Heartbeat.ts (renamed from gateway/src/util/Heartbeat.ts)0
-rw-r--r--src/gateway/util/Send.ts (renamed from gateway/src/util/Send.ts)0
-rw-r--r--src/gateway/util/SessionUtils.ts (renamed from gateway/src/util/SessionUtils.ts)0
-rw-r--r--src/gateway/util/WebSocket.ts (renamed from gateway/src/util/WebSocket.ts)4
-rw-r--r--src/gateway/util/index.ts (renamed from gateway/src/util/index.ts)0
23 files changed, 303 insertions, 6 deletions
diff --git a/gateway/src/Server.ts b/src/gateway/Server.ts

index 82fbeba2..82fbeba2 100644 --- a/gateway/src/Server.ts +++ b/src/gateway/Server.ts
diff --git a/gateway/src/events/Close.ts b/src/gateway/events/Close.ts
index 5b7c512c..5b7c512c 100644 --- a/gateway/src/events/Close.ts +++ b/src/gateway/events/Close.ts
diff --git a/gateway/src/events/Connection.ts b/src/gateway/events/Connection.ts
index 508b4741..508b4741 100644 --- a/gateway/src/events/Connection.ts +++ b/src/gateway/events/Connection.ts
diff --git a/gateway/src/events/Message.ts b/src/gateway/events/Message.ts
index 7ed1dd06..569f5fc7 100644 --- a/gateway/src/events/Message.ts +++ b/src/gateway/events/Message.ts
@@ -6,7 +6,6 @@ try { } catch (error) {} import OPCodeHandlers from "../opcodes"; import { check } from "../opcodes/instanceOf"; -import WS from "ws"; const PayloadSchema = { op: Number, @@ -15,7 +14,7 @@ const PayloadSchema = { $t: String, }; -export async function Message(this: WebSocket, buffer: WS.RawData) { +export async function Message(this: WebSocket, buffer: Buffer) { // TODO: compression let data: Payload; diff --git a/gateway/src/index.ts b/src/gateway/index.ts
index d77ce931..d77ce931 100644 --- a/gateway/src/index.ts +++ b/src/gateway/index.ts
diff --git a/gateway/src/listener/listener.ts b/src/gateway/listener/listener.ts
index 8c69e193..8c69e193 100644 --- a/gateway/src/listener/listener.ts +++ b/src/gateway/listener/listener.ts
diff --git a/gateway/src/opcodes/Heartbeat.ts b/src/gateway/opcodes/Heartbeat.ts
index 42b72d4b..42b72d4b 100644 --- a/gateway/src/opcodes/Heartbeat.ts +++ b/src/gateway/opcodes/Heartbeat.ts
diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts new file mode 100644
index 00000000..4f17ab70 --- /dev/null +++ b/src/gateway/opcodes/Identify.ts
@@ -0,0 +1,298 @@ +import { WebSocket, Payload } from "@fosscord/gateway"; +import { + checkToken, + Intents, + Member, + ReadyEventData, + User, + Session, + EVENTEnum, + Config, + PublicMember, + PublicUser, + PrivateUserProjection, + ReadState, + Application, + emitEvent, + SessionsReplace, + PrivateSessionProjection, + MemberPrivateProjection, + PresenceUpdateEvent, + UserSettings, + IdentifySchema, +} from "@fosscord/util"; +import { Send } from "../util/Send"; +import { CLOSECODES, OPCODES } from "../util/Constants"; +import { genSessionId } from "../util/SessionUtils"; +import { setupListener } from "../listener/listener"; +// 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 +// TODO: check if already identified + +export async function onIdentify(this: WebSocket, data: Payload) { + clearTimeout(this.readyTimeout); + check.call(this, IdentifySchema, data.d); + + const identify: IdentifySchema = data.d; + + try { + const { jwtSecret } = Config.get().security; + var { decoded } = await checkToken(identify.token, jwtSecret); // will throw an error if invalid + } catch (error) { + console.error("invalid token", error); + return this.close(CLOSECODES.Authentication_failed); + } + this.user_id = decoded.id; + + 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 } }), + ]); + + if (!user) return this.close(CLOSECODES.Authentication_failed); + if (!user.settings) { //settings may not exist after updating... + user.settings = new UserSettings(); + user.settings.id = user.id; + await user.settings.save(); + } + + if (!identify.intents) identify.intents = "0x6ffffffff" + this.intents = new Intents(identify.intents); + if (identify.shard) { + this.shard_id = identify.shard[0]; + this.shard_count = identify.shard[1]; + if ( + this.shard_count == null || + this.shard_id == null || + this.shard_id >= this.shard_count || + this.shard_id < 0 || + this.shard_count <= 0 + ) { + console.log(identify.shard); + return this.close(CLOSECODES.Invalid_shard); + } + } + let users: PublicUser[] = []; + + const merged_members = members.map((x: Member) => { + return [ + { + ...x, + roles: x.roles.map((x) => x.id), + settings: undefined, + guild: undefined, + }, + ]; + }) as PublicMember[][]; + let guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at })); + + // @ts-ignore + guilds = guilds.map((guild) => { + if (user.bot) { + setTimeout(() => { + Send(this, { + op: OPCODES.Dispatch, + t: EVENTEnum.GuildCreate, + s: this.sequence++, + d: guild, + }); + }, 500); + return { id: guild.id, unavailable: true }; + } + + return guild; + }); + + const user_guild_settings_entries = members.map((x) => x.settings); + + const channels = recipients.map((x) => { + // @ts-ignore + x.channel.recipients = x.channel.recipients?.map((x) => x.user); + //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 + ); + } + return x.channel; + }); + + for (let relation of user.relationships) { + const related_user = relation.to; + const public_related_user = { + username: related_user.username, + discriminator: related_user.discriminator, + id: related_user.id, + public_flags: related_user.public_flags, + avatar: related_user.avatar, + bot: related_user.bot, + bio: related_user.bio, + premium_since: user.premium_since + }; + users.push(public_related_user); + } + + setImmediate(async () => { + // run in seperate "promise context" because ready payload is not dependent on those events + emitEvent({ + event: "SESSIONS_REPLACE", + user_id: this.user_id, + data: await Session.find({ + where: { user_id: this.user_id }, + select: PrivateSessionProjection, + }), + } as SessionsReplace); + emitEvent({ + event: "PRESENCE_UPDATE", + user_id: this.user_id, + data: { + user: await User.getPublicUser(this.user_id), + activities: session.activities, + client_status: session?.client_info, + status: session.status, + }, + } as PresenceUpdateEvent); + }); + + read_states.forEach((s: any) => { + s.id = s.channel_id; + delete s.user_id; + delete s.channel_id; + }); + + const privateUser = { + avatar: user.avatar, + mobile: user.mobile, + desktop: user.desktop, + discriminator: user.discriminator, + email: user.email, + flags: user.flags, + id: user.id, + mfa_enabled: user.mfa_enabled, + nsfw_allowed: user.nsfw_allowed, + phone: user.phone, + premium: user.premium, + premium_type: user.premium_type, + public_flags: user.public_flags, + username: user.username, + verified: user.verified, + bot: user.bot, + accent_color: user.accent_color || 0, + banner: user.banner, + bio: user.bio, + premium_since: user.premium_since + }; + + const d: ReadyEventData = { + v: 8, + application: {id: application?.id??'', flags: application?.flags??''}, //TODO: check this code! + user: privateUser, + user_settings: user.settings, + // @ts-ignore + guilds: guilds.map((x) => { + // @ts-ignore + x.guild_hashes = {}; // @ts-ignore + x.guild_scheduled_events = []; // @ts-ignore + x.threads = []; + return x; + }), + guild_experiments: [], // TODO + geo_ordered_rtc_regions: [], // TODO + relationships: user.relationships.map((x) => x.toPublicRelationship()), + read_state: { + entries: read_states, + partial: false, + version: 304128, + }, + user_guild_settings: { + entries: user_guild_settings_entries, + partial: false, // TODO partial + version: 642, + }, + private_channels: channels, + session_id: session_id, + analytics_token: "", // TODO + connected_accounts: [], // TODO + consents: { + personalization: { + consented: false, // TODO + }, + }, + country_code: user.settings.locale, + friend_suggestion_count: 0, // TODO + // @ts-ignore + experiments: experiments, // TODO + guild_join_requests: [], // TODO what is this? + users: users.filter((x) => x).unique(), + merged_members: merged_members, + // shard // TODO: only for user sharding + }; + + // TODO: send real proper data structure + await Send(this, { + op: OPCODES.Dispatch, + t: EVENTEnum.Ready, + s: this.sequence++, + d, + }); + + //TODO send READY_SUPPLEMENTAL + //TODO send GUILD_MEMBER_LIST_UPDATE + //TODO send SESSIONS_REPLACE + //TODO send VOICE_STATE_UPDATE to let the client know if another device is already connected to a voice channel + + await setupListener.call(this); +} diff --git a/gateway/src/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts
index cd0586de..74996f5b 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/src/gateway/opcodes/LazyRequest.ts
@@ -16,7 +16,7 @@ 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 = 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") diff --git a/gateway/src/opcodes/PresenceUpdate.ts b/src/gateway/opcodes/PresenceUpdate.ts
index f31c9161..f31c9161 100644 --- a/gateway/src/opcodes/PresenceUpdate.ts +++ b/src/gateway/opcodes/PresenceUpdate.ts
diff --git a/gateway/src/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts
index b80721dc..b80721dc 100644 --- a/gateway/src/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts
diff --git a/gateway/src/opcodes/Resume.ts b/src/gateway/opcodes/Resume.ts
index 42dc586d..42dc586d 100644 --- a/gateway/src/opcodes/Resume.ts +++ b/src/gateway/opcodes/Resume.ts
diff --git a/gateway/src/opcodes/VoiceStateUpdate.ts b/src/gateway/opcodes/VoiceStateUpdate.ts
index 73f73565..c4297a68 100644 --- a/gateway/src/opcodes/VoiceStateUpdate.ts +++ b/src/gateway/opcodes/VoiceStateUpdate.ts
@@ -12,7 +12,7 @@ import { VoiceStateUpdateSchema, } from "@fosscord/util"; import { OrmUtils } from "@fosscord/util"; -import { Region } from "@fosscord/util/src/config"; +import { Region } from "@fosscord/util"; // 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. diff --git a/gateway/src/opcodes/experiments.json b/src/gateway/opcodes/experiments.json
index 0370b5da..0370b5da 100644 --- a/gateway/src/opcodes/experiments.json +++ b/src/gateway/opcodes/experiments.json
diff --git a/gateway/src/opcodes/index.ts b/src/gateway/opcodes/index.ts
index 027739db..027739db 100644 --- a/gateway/src/opcodes/index.ts +++ b/src/gateway/opcodes/index.ts
diff --git a/gateway/src/opcodes/instanceOf.ts b/src/gateway/opcodes/instanceOf.ts
index eb6f6ea1..eb6f6ea1 100644 --- a/gateway/src/opcodes/instanceOf.ts +++ b/src/gateway/opcodes/instanceOf.ts
diff --git a/gateway/src/start.ts b/src/gateway/start.ts
index 2000522a..2000522a 100644 --- a/gateway/src/start.ts +++ b/src/gateway/start.ts
diff --git a/gateway/src/util/Constants.ts b/src/gateway/util/Constants.ts
index 692f9028..692f9028 100644 --- a/gateway/src/util/Constants.ts +++ b/src/gateway/util/Constants.ts
diff --git a/gateway/src/util/Heartbeat.ts b/src/gateway/util/Heartbeat.ts
index f6871cfe..f6871cfe 100644 --- a/gateway/src/util/Heartbeat.ts +++ b/src/gateway/util/Heartbeat.ts
diff --git a/gateway/src/util/Send.ts b/src/gateway/util/Send.ts
index 2a28d8e0..2a28d8e0 100644 --- a/gateway/src/util/Send.ts +++ b/src/gateway/util/Send.ts
diff --git a/gateway/src/util/SessionUtils.ts b/src/gateway/util/SessionUtils.ts
index bf854042..bf854042 100644 --- a/gateway/src/util/SessionUtils.ts +++ b/src/gateway/util/SessionUtils.ts
diff --git a/gateway/src/util/WebSocket.ts b/src/gateway/util/WebSocket.ts
index e3313f40..9496da85 100644 --- a/gateway/src/util/WebSocket.ts +++ b/src/gateway/util/WebSocket.ts
@@ -8,8 +8,8 @@ export interface WebSocket extends WS { session_id: string; encoding: "etf" | "json"; compress?: "zlib-stream"; - shard_count?: bigint; - shard_id?: bigint; + shard_count?: number; + shard_id?: number; deflate?: Deflate; heartbeatTimeout: NodeJS.Timeout; readyTimeout: NodeJS.Timeout; diff --git a/gateway/src/util/index.ts b/src/gateway/util/index.ts
index 0be5ecee..0be5ecee 100644 --- a/gateway/src/util/index.ts +++ b/src/gateway/util/index.ts