From f7a273a58313c8ee71e0e4b489b6af5e14222b53 Mon Sep 17 00:00:00 2001 From: didinele Date: Sat, 13 Mar 2021 14:40:44 +0200 Subject: [PATCH 1/2] feat(gateway-service): support code injection --- libs/common/src/constants.ts | 42 ++++++++++++++++++++++- pnpm-lock.yaml | 35 +++++++++++++++---- services/gateway/package.json | 5 ++- services/gateway/src/index.ts | 63 ++++++++++++++++++++++++++--------- 4 files changed, 119 insertions(+), 26 deletions(-) diff --git a/libs/common/src/constants.ts b/libs/common/src/constants.ts index ff3fa27..4a653d5 100644 --- a/libs/common/src/constants.ts +++ b/libs/common/src/constants.ts @@ -15,7 +15,9 @@ export const CORDIS_META: { } = { url: pkg.homepage.split('#')[0], version: pkg.version -}; +} as const; + +Object.freeze(CORDIS_META); /** * Root endpoints for Discord @@ -28,3 +30,41 @@ export const ENDPOINTS = { } as const; Object.freeze(ENDPOINTS); + +/** + * Interface representing configuration for the gateway service + */ +export interface GatewayServiceConfig { + auth: string; + amqpHost: string; + debug: boolean; + shardCount: number | 'auto'; + startingShard: number; + totalShardCount: number | 'auto'; + ws: { + compress: boolean; + encoding: 'json' | 'etf'; + timeouts: { + open: number; + hello: number; + ready: number; + guild: number; + reconnect: number; + }; + largeThreshold: number; + intents: string[]; + }; +} + +export const GATEWAY_INJECTION_TOKENS = { + kConfig: Symbol('parsed configuration options'), + amqp: { + kChannel: Symbol('amqp channel object'), + kConnection: Symbol('amqp connection object'), + kService: Symbol('RoutingServer instance for distributing the incoming packets'), + kCommandsServer: Symbol('"server" recieving payloads (called commands) to send to Discord') + }, + kCluster: Symbol('Cluster instance') +} as const; + +Object.freeze(GATEWAY_INJECTION_TOKENS); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55594e6..4d89877 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -237,11 +237,10 @@ importers: '@cordis/brokers': link:../../libs/brokers '@cordis/common': link:../../libs/common '@cordis/gateway': link:../../libs/gateway - '@cordis/redis-store': link:../../libs/redis-store - '@cordis/store': link:../../libs/store erlpack: github.com/discord/erlpack/e27db8f82892bdb9b28a0547cc394d68b5d2242d - ioredis: 4.22.0 + reflect-metadata: 0.1.13 tslib: 2.1.0 + tsyringe: 4.4.0 yargs: 15.4.1 zlib-sync: 0.1.7 devDependencies: @@ -253,14 +252,13 @@ importers: '@cordis/brokers': workspace:^0.1.7 '@cordis/common': workspace:^0.1.7 '@cordis/gateway': workspace:^0.1.7 - '@cordis/redis-store': workspace:^0.1.7 - '@cordis/store': workspace:^0.1.7 '@types/node': ^14.14.31 '@types/yargs': ^15.0.13 discord-api-types: ^0.12.1 erlpack: github:discord/erlpack - ioredis: ^4.22.0 + reflect-metadata: ^0.1.13 tslib: ^2.1.0 + tsyringe: ^4.4.0 typescript: ^4.2.2 yargs: ^15.4.1 zlib-sync: ^0.1.7 @@ -2297,6 +2295,7 @@ packages: resolution: integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== /cluster-key-slot/1.1.0: + dev: true engines: node: '>=0.10.0' resolution: @@ -2474,6 +2473,7 @@ packages: /debug/4.3.1: dependencies: ms: 2.1.2 + dev: true engines: node: '>=6.0' peerDependencies: @@ -2551,6 +2551,7 @@ packages: resolution: integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk= /denque/1.5.0: + dev: true engines: node: '>=0.10' resolution: @@ -3454,6 +3455,7 @@ packages: redis-errors: 1.2.0 redis-parser: 3.0.0 standard-as-callback: 2.0.1 + dev: true engines: node: '>=6' resolution: @@ -4381,9 +4383,11 @@ packages: resolution: integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== /lodash.defaults/4.2.0: + dev: true resolution: integrity: sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= /lodash.flatten/4.4.0: + dev: true resolution: integrity: sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= /lodash.sortby/4.7.0: @@ -4540,6 +4544,7 @@ packages: resolution: integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= /ms/2.1.2: + dev: true resolution: integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== /nan/2.14.2: @@ -4785,6 +4790,7 @@ packages: resolution: integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== /p-map/2.1.0: + dev: true engines: node: '>=6' resolution: @@ -5022,9 +5028,11 @@ packages: resolution: integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= /redis-commands/1.7.0: + dev: true resolution: integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== /redis-errors/1.2.0: + dev: true engines: node: '>=4' resolution: @@ -5032,10 +5040,15 @@ packages: /redis-parser/3.0.0: dependencies: redis-errors: 1.2.0 + dev: true engines: node: '>=4' resolution: integrity: sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + /reflect-metadata/0.1.13: + dev: false + resolution: + integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== /regenerate-unicode-properties/8.2.0: dependencies: regenerate: 1.4.2 @@ -5565,6 +5578,7 @@ packages: resolution: integrity: sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== /standard-as-callback/2.0.1: + dev: true resolution: integrity: sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg== /static-extend/0.1.2: @@ -5796,7 +5810,6 @@ packages: resolution: integrity: sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== /tslib/1.14.1: - dev: true resolution: integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== /tslib/2.1.0: @@ -5814,6 +5827,14 @@ packages: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' resolution: integrity: sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg== + /tsyringe/4.4.0: + dependencies: + tslib: 1.14.1 + dev: false + engines: + node: '>= 6.0.0' + resolution: + integrity: sha512-SlMApe1lhIq546CDp7bF+IdF4RB6d+9C5T7B0AS0P/Bm+Qpizj/gEmZzvw9J/KlXPEt4qHTbi1TRvX3rCPSdTg== /tunnel-agent/0.6.0: dependencies: safe-buffer: 5.2.1 diff --git a/services/gateway/package.json b/services/gateway/package.json index bd6cc2d..d32a94c 100644 --- a/services/gateway/package.json +++ b/services/gateway/package.json @@ -31,11 +31,10 @@ "@cordis/brokers": "workspace:^0.1.7", "@cordis/common": "workspace:^0.1.7", "@cordis/gateway": "workspace:^0.1.7", - "@cordis/redis-store": "workspace:^0.1.7", - "@cordis/store": "workspace:^0.1.7", "erlpack": "github:discord/erlpack", - "ioredis": "^4.22.0", + "reflect-metadata": "^0.1.13", "tslib": "^2.1.0", + "tsyringe": "^4.4.0", "yargs": "^15.4.1", "zlib-sync": "^0.1.7" } diff --git a/services/gateway/src/index.ts b/services/gateway/src/index.ts index ac9ea43..1e1306c 100644 --- a/services/gateway/src/index.ts +++ b/services/gateway/src/index.ts @@ -1,10 +1,19 @@ +import 'reflect-metadata'; +import { container } from 'tsyringe'; import * as yargs from 'yargs'; import { createAmqp, RoutingServer, PubSubClient } from '@cordis/brokers'; -import createRedis, { Redis } from 'ioredis'; import { Cluster, IntentKeys } from '@cordis/gateway'; -import type { DiscordEvents } from '@cordis/common'; +import { DiscordEvents, GatewayServiceConfig, GATEWAY_INJECTION_TOKENS } from '@cordis/common'; import type { GatewaySendPayload } from 'discord-api-types/v8'; +const loadExtension = async (name: string) => { + try { + await require(`../extensions/${name}`); + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') console.error(`[${name.toUpperCase()} EXTENSION ERROR]`, e); + } +}; + const main = async () => { const { argv } = yargs .env('CORDIS') @@ -46,12 +55,6 @@ const main = async () => { 'demandOption': false, 'default': 'auto' as const }) - .option('redis-url', { - global: true, - description: 'URL for connecting to your redis instance', - type: 'string', - demandOption: false - }) .option('ws-compress', { 'global': true, 'description': 'Whether or not to use compression', @@ -62,7 +65,7 @@ const main = async () => { 'global': true, 'description': 'What websocket encoding to use, JSON or ETF', 'type': 'string', - 'default': 'etf' + 'default': 'etf' as const }) .option('ws-open-timeout', { 'global': true, @@ -116,6 +119,32 @@ const main = async () => { }) .help(); + const config: GatewayServiceConfig = { + auth: argv.auth, + amqpHost: argv['amqp-host'], + debug: argv.debug, + shardCount: argv['shard-count'], + startingShard: argv['starting-shard'], + totalShardCount: argv['total-shard-count'], + ws: { + compress: argv['ws-compress'], + encoding: argv['ws-encoding'], + timeouts: { + open: argv['ws-open-timeout'], + hello: argv['ws-hello-timeout'], + ready: argv['ws-ready-timeout'], + guild: argv['ws-guild-timeout'], + reconnect: argv['ws-reconnect-timeout'] + }, + largeThreshold: argv['ws-large-threshold'], + intents: argv['ws-intents'] + } + }; + + container.register(GATEWAY_INJECTION_TOKENS.kConfig, { useValue: config }); + + await loadExtension('pre-setup'); + const { channel, connection } = await createAmqp(argv['amqp-host']); connection .on('error', e => console.error(`[AMQP ERROR]: ${e}`)) @@ -124,11 +153,6 @@ const main = async () => { process.exit(1); }); - let redis: Redis | null = null; - if (argv['redis-url']) { - redis = new createRedis(argv['redis-url']); - } - const service = new RoutingServer(channel); const cluster = new Cluster( argv.auth, @@ -142,7 +166,6 @@ const main = async () => { reconnectTimeout: argv['ws-reconnect-timeout'], largeThreshold: argv['ws-large-threshold'], intents: argv['ws-intents'] as IntentKeys[], - redis: redis ?? undefined, shardCount: argv['shard-count'], startingShard: argv['starting-shard'], totalShardCount: argv['total-shard-count'] @@ -156,6 +179,14 @@ const main = async () => { cb: req => cluster.broadcast(req) }); + container.register(GATEWAY_INJECTION_TOKENS.amqp.kChannel, { useValue: channel }); + container.register(GATEWAY_INJECTION_TOKENS.amqp.kConnection, { useValue: connection }); + container.register(GATEWAY_INJECTION_TOKENS.amqp.kService, { useValue: service }); + container.register(GATEWAY_INJECTION_TOKENS.amqp.kCommandsServer, { useValue: gatewayCommandsServer }); + container.register(GATEWAY_INJECTION_TOKENS.kCluster, { useValue: cluster }); + + await loadExtension('pre-init'); + cluster .on('disconnecting', id => console.log(`[DISCONNECTING]: Shard id ${id}`)) .on('reconnecting', id => console.log(`[RECONNECTING]: Shard id ${id}`)) @@ -169,6 +200,8 @@ const main = async () => { try { await service.init({ name: 'gateway', topicBased: false }); await cluster.connect(); + + await loadExtension('post-init'); } catch (e) { console.error('Failed to initialize the service or the cluster', e); process.exit(1); From d8fb5238cf98c99c78af043b7f59c5346fc9ad69 Mon Sep 17 00:00:00 2001 From: didinele Date: Sat, 13 Mar 2021 15:17:56 +0200 Subject: [PATCH 2/2] chore: nitpicks --- libs/common/src/constants.ts | 3 +++ services/gateway/src/index.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/common/src/constants.ts b/libs/common/src/constants.ts index 4a653d5..1098b75 100644 --- a/libs/common/src/constants.ts +++ b/libs/common/src/constants.ts @@ -56,6 +56,9 @@ export interface GatewayServiceConfig { }; } +/** + * Injection tokens used by the gateway service + */ export const GATEWAY_INJECTION_TOKENS = { kConfig: Symbol('parsed configuration options'), amqp: { diff --git a/services/gateway/src/index.ts b/services/gateway/src/index.ts index 1e1306c..05ba1c2 100644 --- a/services/gateway/src/index.ts +++ b/services/gateway/src/index.ts @@ -185,8 +185,6 @@ const main = async () => { container.register(GATEWAY_INJECTION_TOKENS.amqp.kCommandsServer, { useValue: gatewayCommandsServer }); container.register(GATEWAY_INJECTION_TOKENS.kCluster, { useValue: cluster }); - await loadExtension('pre-init'); - cluster .on('disconnecting', id => console.log(`[DISCONNECTING]: Shard id ${id}`)) .on('reconnecting', id => console.log(`[RECONNECTING]: Shard id ${id}`)) @@ -195,6 +193,8 @@ const main = async () => { .on('ready', () => console.log('[READY]: All shards have fully connected')) .on('dispatch', data => service.publish(data.t, data.d)); + await loadExtension('pre-init'); + if (argv.debug) cluster.on('debug', (info, id) => console.log(`[DEBUG]: Shard id ${id}`, info)); try {