From f2ef2daf3a5fd7376dd95d9dce9544ab76c74a99 Mon Sep 17 00:00:00 2001 From: Alexis Tyler Date: Sun, 6 Sep 2020 16:03:47 +0930 Subject: [PATCH] refactor(mothership): switch to new redis backed endpoints --- app/graphql/index.ts | 26 ++-- app/graphql/schema/resolvers.ts | 7 + app/mothership.ts | 248 +++++++++++++++----------------- app/run.ts | 6 +- app/server.ts | 2 +- app/ws.ts | 18 +++ package-lock.json | 82 ++++++++++- package.json | 4 +- tsconfig.json | 14 +- 9 files changed, 250 insertions(+), 157 deletions(-) diff --git a/app/graphql/index.ts b/app/graphql/index.ts index 3876b6272..441a9bca0 100644 --- a/app/graphql/index.ts +++ b/app/graphql/index.ts @@ -81,6 +81,7 @@ const baseTypes = [gql` ping: PingSubscription! info: InfoSubscription! pluginModule(plugin: String!, module: String!, params: JSON, result: String): PluginModuleSubscription! + online: Boolean! } `]; @@ -182,8 +183,9 @@ const getPluginModule = (pluginName: string, pluginModuleName: string) => { */ class FuncDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field: { [key: string]: any }) { + // @ts-ignore const { args } = this; - field.resolve = async function (source, directiveArgs: { [key: string]: any }, { user }, info: { [key: string]: any }) { + field.resolve = async function (_source, directiveArgs: { [key: string]: any }, { user }, info: { [key: string]: any }) { const {module: moduleName, result: resultType} = args; const {plugin: pluginName, module: pluginModuleName, result: pluginType, input, ...params} = directiveArgs; const operationType = info.operation.operation; @@ -369,19 +371,25 @@ export const graphql = { wsHasDisconnected(websocketId); } }, - context: ({req, connection}) => { - if (connection) { + context: ({req, connection, ...args_}, ...args__) => { + // Normal Websocket connection + if (connection && Object.keys(connection.context).length >= 1) { // Check connection for metadata return { ...connection.context }; } - const apiKey = req.headers['x-api-key']; - const user = apiKeyToUser(apiKey); - - return { - user - }; + // Normal HTTP connection + if (req) { + const apiKey = req.headers['x-api-key']; + const user = apiKeyToUser(apiKey); + return { + user + }; + } + + console.log({req, connection, args_, args__}); + throw new Error('Invalid'); } }; diff --git a/app/graphql/schema/resolvers.ts b/app/graphql/schema/resolvers.ts index e907a1c13..bf372481e 100644 --- a/app/graphql/schema/resolvers.ts +++ b/app/graphql/schema/resolvers.ts @@ -48,6 +48,10 @@ dee.on('*', async (data: { Type: string }) => { dee.listen(); +setInterval(() => { + publish('online', 'UPDATED', true); +}, 1000); + // This needs to be fixed to run from events setIntervalAsync(async () => { if (!canPublishToChannel('services')) { @@ -232,6 +236,9 @@ export const resolvers = { hasSubscribedToChannel(context.websocketId, channel); return pubsub.asyncIterator(channel); } + }, + online: { + ...createSubscription('online') } }, JSON: GraphQLJSON, diff --git a/app/mothership.ts b/app/mothership.ts index c725ae61f..3e50a1b10 100644 --- a/app/mothership.ts +++ b/app/mothership.ts @@ -1,14 +1,14 @@ import fs from 'fs'; -import request from 'request'; import WebSocket from 'ws'; -import merge from 'deepmerge'; import { log, utils, paths, states, config } from '@unraid/core'; import { DynamixConfig } from '@unraid/core/dist/lib/types'; -import { userCache, CachedServer } from './cache'; const { loadState } = utils; const { varState } = states; +process.on('uncaughtException', console.log); +process.on('unhandledRejection', console.log); + /** * One second in milliseconds. */ @@ -18,6 +18,16 @@ const ONE_SECOND = 1000; */ const ONE_MINUTE = 60 * ONE_SECOND; +/** + * Relay ws link. + */ +const RELAY_WS_LINK = process.env.RELAY_WS_LINK ? process.env.RELAY_WS_LINK : 'wss://relay.unraid.net'; + +/** + * Internal ws link. + */ +const INTERNAL_WS_LINK = process.env.INTERNAL_WS_LINK ? process.env.INTERNAL_WS_LINK : `ws+unix://${config.get('graphql-api-port')}`; + /** * Get a number between the lowest and highest value. * @param low Lowest value. @@ -38,7 +48,7 @@ const backoff = (attempt: number, maxDelay: number, multiplier: number) => { return Math.round(Math.min(delay * multiplier, maxDelay)); }; -let mothership; +let relay: WebSocket; interface WebSocketWithHeartBeat extends WebSocket { pingTimeout?: NodeJS.Timeout @@ -56,78 +66,20 @@ function heartbeat(this: WebSocketWithHeartBeat) { this.pingTimeout = setTimeout(() => { this.terminate(); }, 30000 + 1000); -} - -type MessageType = 'query' | 'mutation' | 'start' | 'stop' | 'proxy-data'; -interface Message { - type: MessageType; - payload: { - operationName: any; - variables: {}; - query: string; - data: any; - topic?: string; - } }; -type Server = CachedServer; -type Servers = Server[]; -interface ProxyMessage extends Omit { - type: MessageType - payload: { - topic: 'servers'; - data: Servers; - } -}; +const readFileIfExists = (filePath: string) => { + try { + return fs.readFileSync(filePath); + } catch {} -const isProxyMessage = (message: any): message is ProxyMessage => { - const keys = Object.keys(message.payload ?? {}); - return message.payload && message.type === 'proxy-data' && keys.length === 2 && keys.includes('topic') && keys.includes('data'); -}; - -const isServersPayload = (payload: any): payload is Servers => payload.topic === 'servers'; - -const forwardMessageToLocalSocket = (message: Message, apiKey: string) => { - log.debug(`Got a "${message.type}" request from mothership, forwarding to socket.`); - const port = config.get('graphql-api-port'); - const localEndpoint = (!isNaN(port as number)) ? `localhost:${port}` : `unix:${port}:`; - const url = `http://${localEndpoint}/graphql`; - request.post(url, { - body: JSON.stringify({ - operationName: null, - variables: {}, - query: message.payload.query - }), - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - 'x-api-key': apiKey - } - }, (error, response) => { - if (error) { - log.error(error); - return; - } - - try { - const data = JSON.parse(response.body).data; - const payload = { data }; - log.debug('Replying to mothership with payload %o', payload); - mothership.send(JSON.stringify({ - type: 'data', - payload - })); - } catch (error) { - log.error(error); - mothership.close(); - } - }); + return Buffer.from(''); }; /** * Connect to unraid's proxy server */ -export const connectToMothership = async (wsServer, currentRetryAttempt: number = 0) => { +export const connectToMothership = async (wsServer: WebSocket.Server, currentRetryAttempt: number = 0) => { // Kill the last connection first await disconnectFromMothership(); let retryAttempt = currentRetryAttempt; @@ -137,14 +89,15 @@ export const connectToMothership = async (wsServer, currentRetryAttempt: number } const apiKey = loadState(paths.get('dynamix-config')!).remote.apikey || ''; - const keyFile = varState.data?.regFile ? fs.readFileSync(varState.data?.regFile).toString('base64') : ''; + const keyFile = varState.data?.regFile ? readFileIfExists(varState.data?.regFile).toString('base64') : ''; const serverName = `${varState.data?.name}`; const lanIp = states.networkState.data.find(network => network.ipaddr[0]).ipaddr[0] || ''; const machineId = `${await utils.getMachineId()}`; + let localGraphqlApi: WebSocket; - // Connect to mothership + // Connect to mothership's relay endpoint // Keep reference outside this scope so we can disconnect later - mothership = new WebSocket('wss://proxy.unraid.net', ['graphql-ws'], { + relay = new WebSocket(RELAY_WS_LINK, ['graphql-ws'], { headers: { 'x-api-key': apiKey, 'x-flash-guid': varState.data?.flashGuid ?? '', @@ -155,84 +108,118 @@ export const connectToMothership = async (wsServer, currentRetryAttempt: number } }); - mothership.on('open', function() { - log.debug('Connected to mothership.'); + relay.on('open', async () => { + log.debug(`Connected to mothership's relay.`); // Reset retry attempts retryAttempt = 0; - // Connect mothership to the internal ws server - wsServer.emit('connection', mothership); + // Connect to the internal graphql server + localGraphqlApi = new WebSocket(INTERNAL_WS_LINK, ['graphql-ws']); - // Start ping/pong - // @ts-ignore - heartbeat.bind(this); + // Heartbeat + localGraphqlApi.on('ping', () => { + heartbeat.bind(localGraphqlApi)(); + }); + + // Errors + localGraphqlApi.on('error', error => { + log.error('ws:local-relay', 'error', error); + }); + + // Connection to local graphql endpoint is "closed" + localGraphqlApi.on('close', () => { + log.debug('ws:local-relay', 'close'); + }); + + // Connection to local graphql endpoint is "open" + localGraphqlApi.on('open', () => { + log.debug('ws:local-relay', 'open'); + + // Authenticate with ourselves + localGraphqlApi.send(JSON.stringify({ + type: 'connection_init', + payload: { + 'x-api-key': apiKey + } + })); + }); + + // Relay message back to mothership + localGraphqlApi.on('message', (data) => { + try { + relay.send(data); + } catch (error) { + // Relay socket is closed, close internal one + if (error.message.includes('WebSocket is not open')) { + localGraphqlApi.close(); + } + } + }); }); - mothership.on('close', async function (this: WebSocketWithHeartBeat) { - if (this.pingTimeout) { - clearTimeout(this.pingTimeout); + + // Relay is closed + relay.on('close', async function (this: WebSocketWithHeartBeat, ...args) { + try { + log.debug('Connection closed.', ...args); + + if (this.pingTimeout) { + clearTimeout(this.pingTimeout); + } + + // Clear all listeners before running this again + relay?.removeAllListeners(); + + // Close connection to local graphql endpoint + localGraphqlApi?.close(); + + // Reconnect + setTimeout(async () => { + await connectToMothership(wsServer, retryAttempt + 1); + }, backoff(retryAttempt, ONE_MINUTE, 5)); + } catch (error) { + log.error(error); } - - // Clear all listeners before running this again - mothership?.removeAllListeners(); - - // Reconnect - setTimeout(async () => { - await connectToMothership(wsServer, retryAttempt + 1); - }, backoff(retryAttempt, ONE_MINUTE, 2)); }); - mothership.on('error', error => { - // Mothership is down + relay.on('error', (error: NodeJS.ErrnoException) => { + // The relay is down if (error.message.includes('502')) { return; } - log.error(error.message); + // Connection refused, aka couldn't connect + // This is usually because the address is wrong or offline + if (error.code === 'ECONNREFUSED') { + // @ts-expect-error + log.debug(`Couldn't connect to ${error.address}:${error.port}`); + return; + } + + log.error(error); }); - mothership.on('ping', heartbeat); + relay.on('ping', heartbeat); - mothership.on('message', async (stringifiedData: string) => { + const sendMessage = (client, message, timeout = 1000) => { try { - const message: Message = JSON.parse(stringifiedData); - - // Proxy this to the http endpoint - if (message.type === 'query' || message.type === 'mutation') { - forwardMessageToLocalSocket(message, apiKey); + if (client.readyState === 0) { + setTimeout(() => { + sendMessage(client, message, timeout); + log.debug('Message sent to mothership.', message) + }, timeout); return; } - log.debug(`Got a "${message.type}" request from mothership, handling internally.`); + client.send(message); + } catch (error) { + log.error('Failed replying to mothership.', error); + }; + }; - if (isProxyMessage(message)) { - const payload = message.payload; - - if (isServersPayload(payload)) { - const cachedData = userCache.get('mine'); - const newData = { - servers: payload.data - }; - - // If we don't have cached data just save this - if (!cachedData || cachedData.length === 0) { - userCache.set('mine', newData); - return; - } - - // Loop all new servers and merge new data on top of the cached stuff - // This should mean { guid: "1", status: "offline" } should keep - // all data but update the "status" field. - const mergedData = { - servers: newData.servers.map(newServer => { - const cachedServer = cachedData?.find(cachedServer => cachedServer.guid === newServer.guid); - return cachedServer ? merge(cachedServer, newServer) : newServer; - }) - }; - - userCache.set('mine', mergedData); - } - } + relay.on('message', async (data: string) => { + try { + sendMessage(localGraphqlApi, data); } catch (error) { // Something weird happened while processing the message // This is likely a malformed message @@ -245,11 +232,10 @@ export const connectToMothership = async (wsServer, currentRetryAttempt: number * Disconnect from mothership. */ export const disconnectFromMothership = async () => { - if (mothership && mothership.readyState !== 0) { + if (relay && relay.readyState !== 0) { log.debug('Disconnecting from the proxy server.'); try { - mothership.close(); - mothership = undefined; + relay.close(); } catch {} } }; \ No newline at end of file diff --git a/app/run.ts b/app/run.ts index cbf30c235..5ed438a66 100644 --- a/app/run.ts +++ b/app/run.ts @@ -20,11 +20,15 @@ export const publish = (channel: string, mutation: string, node?: {}) => { }; if (!canPublishToChannel(channel)) { + // console.log(`can't post to ${channel}`); return; } // Update clients - pubsub.publish(channel, data); + const fieldName = Object.keys(data)[0]; + pubsub.publish(channel, { + [fieldName]: data[fieldName].node + }); }; interface RunOptions { diff --git a/app/server.ts b/app/server.ts index ba62c5f81..3b50c6b75 100644 --- a/app/server.ts +++ b/app/server.ts @@ -30,7 +30,7 @@ const ONE_SECOND = 1000; const app = express(); const port = String(config.get('graphql-api-port')); -app.use(async (req, res, next) => { +app.use(async (_req, res, next) => { // Only get the machine ID on first request // We do this to avoid using async in the main server function if (!app.get('x-machine-id')) { diff --git a/app/ws.ts b/app/ws.ts index 5c92d10bd..7b8c360fb 100644 --- a/app/ws.ts +++ b/app/ws.ts @@ -24,11 +24,27 @@ export const getWsConectionCountInChannel = (channel: string) => { }; export const hasSubscribedToChannel = (id: string, channel: string) => { + // Setup inital object + if (subscriptions[id] === undefined) { + subscriptions[id] = { + total: 1, + channels: [channel] + }; + return; + } subscriptions[id].total++; subscriptions[id].channels.push(channel); }; export const hasUnsubscribedFromChannel = (id: string, channel: string) => { + // Setup inital object + if (subscriptions[id] === undefined) { + subscriptions[id] = { + total: 0, + channels: [] + }; + return; + } subscriptions[id].total--; subscriptions[id].channels = subscriptions[id].channels.filter(existingChannel => existingChannel !== channel); }; @@ -60,12 +76,14 @@ export const wsHasDisconnected = (id: string) => { export const canPublishToChannel = (channel: string) => { // No ws connections if (getWsConectionCount() === 0) { + // log.debug('No ws connections, cannot publish'); return false; } // No ws connections to this channel const channelConnectionCount = getWsConectionCountInChannel(channel); if (channelConnectionCount === 0) { + // log.debug(`No connections to channel ${channel}`); return false; } diff --git a/package-lock.json b/package-lock.json index 76f73d311..1ef9d3207 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,40 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@apollo/client": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.1.3.tgz", + "integrity": "sha512-zXMiaj+dX0sgXIwEV5d/PI6B8SZT2bqlKNjZWcEXRY7NjESF5J3nd4v8KOsrhHe+A3YhNv63tIl35Sq7uf41Pg==", + "requires": { + "@types/zen-observable": "^0.8.0", + "@wry/context": "^0.5.2", + "@wry/equality": "^0.2.0", + "fast-json-stable-stringify": "^2.0.0", + "graphql-tag": "^2.11.0", + "hoist-non-react-statics": "^3.3.2", + "optimism": "^0.12.1", + "prop-types": "^15.7.2", + "symbol-observable": "^1.2.0", + "ts-invariant": "^0.4.4", + "tslib": "^1.10.0", + "zen-observable": "^0.8.14" + }, + "dependencies": { + "@wry/equality": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.2.0.tgz", + "integrity": "sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==", + "requires": { + "tslib": "^1.9.3" + } + }, + "graphql-tag": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz", + "integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA==" + } + } + }, "@apollo/protobufjs": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz", @@ -1090,6 +1124,11 @@ "integrity": "sha512-1jmXgoIyzxQSm33lYgEXvegtkhloHbed2I0QGlTN66U2F9/ExqJWSCSmaWC0IB/g1tW+IYSp+tDhcZBYB1ZGog==", "dev": true }, + "@types/zen-observable": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz", + "integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==" + }, "@typescript-eslint/eslint-plugin": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.0.tgz", @@ -1264,6 +1303,14 @@ } } }, + "@wry/context": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz", + "integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==", + "requires": { + "tslib": "^1.9.3" + } + }, "@wry/equality": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz", @@ -5783,6 +5830,14 @@ "warning": "^4.0.3" } }, + "cross-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz", + "integrity": "sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew==", + "requires": { + "node-fetch": "2.6.0" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -9654,6 +9709,14 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -11057,7 +11120,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -13569,6 +13631,14 @@ "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", "dev": true }, + "optimism": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.12.1.tgz", + "integrity": "sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==", + "requires": { + "@wry/context": "^0.5.2" + } + }, "optional": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz", @@ -14290,7 +14360,6 @@ "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -14531,8 +14600,7 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-popper": { "version": "1.3.7", @@ -16198,9 +16266,9 @@ } }, "subscriptions-transport-ws": { - "version": "0.9.16", - "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz", - "integrity": "sha512-pQdoU7nC+EpStXnCfh/+ho0zE0Z+ma+i7xvj7bkXKb1dvYHSZxgRPaU6spRP+Bjzow67c/rRDoix5RT0uU9omw==", + "version": "0.9.17", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz", + "integrity": "sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA==", "requires": { "backo2": "^1.0.2", "eventemitter3": "^3.1.0", diff --git a/package.json b/package.json index a3a10c3f5..c76a9ef9b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "index.js" ], "dependencies": { + "@apollo/client": "^3.1.3", "@gridplus/docker-events": "github:OmgImAlexis/docker-events", "@unraid/core": "github:unraid/core", "accesscontrol": "^2.2.1", @@ -36,6 +37,7 @@ "apollo-server": "2.14.2", "apollo-server-express": "2.14.2", "camelcase": "6.0.0", + "cross-fetch": "^3.0.5", "dot-prop": "^5.2.0", "express": "^4.17.1", "graphql": "^15.1.0", @@ -51,7 +53,7 @@ "redact-secrets": "github:omgimalexis/redact-secrets", "set-interval-async": "^1.0.33", "stoppable": "^1.1.0", - "subscriptions-transport-ws": "^0.9.16" + "subscriptions-transport-ws": "^0.9.17" }, "optionalDependencies": {}, "devDependencies": { diff --git a/tsconfig.json b/tsconfig.json index 199278973..6bf2c99f3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,10 +9,10 @@ "skipLibCheck": true, /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ - "allowJs": true, /* Allow javascript files to be compiled. */ + "allowJs": false, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ @@ -23,7 +23,7 @@ "rootDir": "./app", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ + "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ @@ -40,10 +40,10 @@ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + "noUnusedLocals": true, /* Report errors on unused locals. */ + "noUnusedParameters": true, /* Report errors on unused parameters. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */