Redis redis.createClient() in Typescript - node.js

I was trying to connect Redis (v4.0.1) to my express server with typescript but having a bit issue. Am learning typescript. It's showing redlines on host inside redis.createClient() Can anyone help me out?
const host = process.env.REDIS_HOST;
const port = process.env.REDIS_PORT;
const redisClient = redis.createClient({
host,
port,
});
Argument of type '{ host: string | undefined; port: string | undefined; }' is not assignable to parameter of type 'Omit<RedisClientOptions<never, RedisScripts>, "modules">'.
Object literal may only specify known properties, and 'host' does not exist in type 'Omit<RedisClientOptions<never, RedisScripts>, "modules">'.ts(2345)

Options have changed when redis updated to 4.0.1. This should help you.

This works as expected (redis v4.1.0)
const url = process.env.REDIS_URL || 'redis://localhost:6379';
const redisClient = redis.createClient({
url
});

what I did in my project was this
file: services/internal/cache.ts
/* eslint-disable no-inline-comments */
import type { RedisClientType } from 'redis'
import { createClient } from 'redis'
import { config } from '#app/config'
import { logger } from '#app/utils/logger'
let redisClient: RedisClientType
let isReady: boolean
const cacheOptions = {
url: config.redis.tlsFlag ? config.redis.urlTls : config.redis.url,
}
if (config.redis.tlsFlag) {
Object.assign(cacheOptions, {
socket: {
// keepAlive: 300, // 5 minutes DEFAULT
tls: false,
},
})
}
async function getCache(): Promise<RedisClientType> {
if (!isReady) {
redisClient = createClient({
...cacheOptions,
})
redisClient.on('error', err => logger.error(`Redis Error: ${err}`))
redisClient.on('connect', () => logger.info('Redis connected'))
redisClient.on('reconnecting', () => logger.info('Redis reconnecting'))
redisClient.on('ready', () => {
isReady = true
logger.info('Redis ready!')
})
await redisClient.connect()
}
return redisClient
}
getCache().then(connection => {
redisClient = connection
}).catch(err => {
// eslint-disable-next-line #typescript-eslint/no-unsafe-assignment
logger.error({ err }, 'Failed to connect to Redis')
})
export {
getCache,
}
then you just import where you need:
import { getCache } from '#services/internal/cache'
const cache = await getCache()
cache.setEx(accountId, 60, JSON.stringify(account))

The option to add a host, port in redis.createClient is no longer supported by redis. So it is not inside type createClient. use URL instead.

import { createClient } from 'redis';
const client = createClient({
socket: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT)
},
password: process.env.REDIS_PW
});
client.on('error', (err) => console.error(err));
client.connect();
export { client };

Related

StreamMessageReader is not a constructor in Electron + Monaco app

I'm trying to build an application using Electron with Monaco editor. I used monaco language client to integrate the editor with clangd. But somehow it didn't work... After manually connecting to the LSP server through web socket, I get the following error in the terminal:
TypeError: node.exports.StreamMessageReader is not a constructor
at createStreamConnection (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3100:18)
at createProcessStreamConnection (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3094:12)
at createServerProcess (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3090:10)
at launch (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3114:28)
at /Volumes/Data/Develop/cpcode/dist/electron/main.js:3160:13
at WebSocketServer.completeUpgrade (/Volumes/Data/Develop/cpcode/node_modules/ws/lib/websocket-server.js:431:5)
at WebSocketServer.handleUpgrade (/Volumes/Data/Develop/cpcode/node_modules/ws/lib/websocket-server.js:339:10)
at Server.<anonymous> (/Volumes/Data/Develop/cpcode/dist/electron/main.js:3147:13)
at Server.emit (node:events:526:28)
at onParserExecuteCommon (node:_http_server:727:14)
I am starting the server in Electron main process. Here's the code for it:
process.env.DIST = join(__dirname, '..')
process.env.PUBLIC = app.isPackaged ? process.env.DIST : join(process.env.DIST, '../public')
import { join } from 'path'
import { app, BrowserWindow } from 'electron'
import ws from "ws";
import http from "http";
import url from "url";
import net from "net";
import express from "express";
import * as rpc from "vscode-ws-jsonrpc";
import * as server from "vscode-ws-jsonrpc/server";
import * as lsp from "vscode-languageserver";
import { Message } from "vscode-languageserver";
export function launch(socket: rpc.IWebSocket) {
const reader = new rpc.WebSocketMessageReader(socket);
const writer = new rpc.WebSocketMessageWriter(socket);
// start the language server as an external process
const socketConnection = server.createConnection(reader, writer, () =>
socket.dispose()
);
const serverConnection = server.createServerProcess("CPP", "clangd");
if (serverConnection) {
server.forward(socketConnection, serverConnection, (message) => {
if (Message.isRequest(message)) {
console.log("server request!!!");
if (message.method === lsp.InitializeRequest.type.method) {
const initializeParams = message.params as lsp.InitializeParams;
initializeParams.processId = process.pid;
}
}
return message;
});
}
}
process.on("uncaughtException", function (err: any) {
console.error("Uncaught Exception: ", err.toString());
if (err.stack) {
console.error(err.stack);
}
});
export const startLSP = () => {
// create the express application
const app = express();
// server the static content, i.e. index.html
app.use(express.static(__dirname));
// start the server
const server = app.listen(3000);
// create the web socket
const wss = new ws.Server({
noServer: true,
perMessageDeflate: false
});
server.on(
"upgrade",
(request: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
// eslint-disable-next-line n/no-deprecated-api
const pathname = request.url
? url.parse(request.url).pathname
: undefined;
if (pathname === "/lsp") {
wss.handleUpgrade(request, socket, head, (webSocket) => {
const socket: rpc.IWebSocket = {
send: (content) =>
webSocket.send(content, (error) => {
if (error) {
throw error;
}
}),
onMessage: (cb) => webSocket.on("message", cb),
onError: (cb) => webSocket.on("error", cb),
onClose: (cb) => webSocket.on("close", cb),
dispose: () => webSocket.close()
};
// launch the server when the web socket is opened
if (webSocket.readyState === webSocket.OPEN) {
launch(socket);
} else {
webSocket.on("open", () => launch(socket));
}
});
}
}
);
};
let win: BrowserWindow | null
// Here, you can also use other preload
const preload = join(__dirname, './preload.js')
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite#2.x
const serverURL = process.env['VITE_DEV_SERVER_URL']
function createWindow() {
win = new BrowserWindow({
icon: join(process.env.PUBLIC, 'logo.svg'),
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload,
},
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'hidden',
})
startLSP();
// Test active push message to Renderer-process.
win.webContents.on('did-finish-load', () => {
win?.webContents.send('main-process-message', (new Date).toLocaleString())
})
if (app.isPackaged) {
win.loadFile(join(process.env.DIST, 'index.html'))
} else {
win.loadURL(serverURL)
}
}
app.on('window-all-closed', () => {
win = null
})
app.whenReady().then(createWindow)
I am using Vite as a bundler and I'm not sure if it has anything to do with this. Anyway, here's my vite.config.ts:
import fs from 'fs'
import path from 'path'
import { defineConfig } from 'vite'
import electron from 'vite-plugin-electron'
import renderer from 'vite-plugin-electron-renderer';
import { nodePolyfills } from 'vite-plugin-node-polyfills'
fs.rmSync('dist', { recursive: true, force: true }) // v14.14.0
export default defineConfig({
plugins: [
electron({
main: {
entry: 'electron/main.ts',
},
preload: {
input: {
// Must be use absolute path, this is the restrict of Rollup
preload: path.join(__dirname, 'electron/preload.ts'),
},
},
// Enables use of Node.js API in the Renderer-process
// https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#electron-renderervite-serve
renderer: {},
}),
nodePolyfills({
// Whether to polyfill `node:` protocol imports.
protocolImports: true,
}),
],
})
After Googling, I tried to install vscode-languageserver and vscode-languageserver-protocol, but no luck. The problem still continues.
Does anyone has a solution to this? Thanks in advance!

issues after upgrading ioredis to v5.0.1

Our original code to create redis clients
import Redis, { Redis as RedisClient, Cluster, ClusterOptions } from 'ioredis';
import config from '../../../config';
const {
port, host, cluster, transitEncryption,
} = config.redis;
const retryStrategy = (times: number): number => Math.min(times * 50, 10000);
function GetRedisClient(): RedisClient {
return new Redis({
host,
port: port as number,
retryStrategy,
});
}
function GetRedisClusterClient(): Cluster {
const clusterOptions: ClusterOptions = {
clusterRetryStrategy: retryStrategy,
redisOptions: { db: 0 },
};
if (transitEncryption) {
clusterOptions.dnsLookup = (address, callback) => callback(null, address);
clusterOptions.redisOptions!.tls = {};
}
return new Redis.Cluster([
{
host,
port: port as number,
},
], clusterOptions);
}
function getClient() {
return cluster ? GetRedisClusterClient() : GetRedisClient();
}
export default getClient();
has become this as we upgrade to version 5.0.1 of ioredis:
import Redis, { Cluster, ClusterOptions } from 'ioredis';
import config from '../../../config';
const {
port, host, cluster, transitEncryption,
} = config.redis;
const retryStrategy = (times: number): number => Math.min(times * 50, 10000);
function GetRedisClient(): Redis {
return new Redis({
host,
port: port as number,
retryStrategy,
});
}
function GetRedisClusterClient(): Cluster {
const clusterOptions: ClusterOptions = {
clusterRetryStrategy: retryStrategy,
redisOptions: { db: 0 },
};
if (transitEncryption) {
clusterOptions.dnsLookup = (address, callback) => callback(undefined, address);
clusterOptions.redisOptions!.tls = {};
}
return new Cluster([
{
host,
port: port as number,
},
], clusterOptions);
}
function getClient() {
return cluster ? GetRedisClusterClient() : GetRedisClient();
}
export default getClient();
The dev dependency #types/ioredis has been removed from our package.json.
I'm getting a semantic error when I compile the project typescript.
xxx/node_modules/#types/connect-redis/index.d.ts(22,51): error TS2694: Namespace '"xxx/node_modules/ioredis/built/index"' has no exported member 'Redis'.
And the jest tests no longer run, flagging up the line return new Cluster, failing with the following
TypeError: _ioredis.Cluster is not a constructor
The tests:
import Redis, { Cluster, ClusterOptions } from 'ioredis';
import config from '../../../config';
jest.mock('ioredis');
jest.mock('../../../config', () => ({
redis: {
host: 'redis',
port: 1234,
cluster: false,
transitEncryption: false,
},
service_name: 'app name',
version: 'test',
}));
describe('redis client', () => {
const clusterTest = (clusterOptions: ClusterOptions) => {
// eslint-disable-next-line global-require
require('./client');
expect(Cluster)
.toHaveBeenCalled();
};
const clusterOptions: ClusterOptions = {
clusterRetryStrategy: expect.any(Function),
redisOptions: { db: 0 },
};
test('if the cluster variable is set, then the module should return a clustered client', () => {
config.redis.cluster = true;
clusterTest(clusterOptions);
});
test('if the cluster variable is set and transit encryption is set, then the module should return a clustered client with encryption', () => {
config.redis.cluster = true;
config.redis.transitEncryption = true;
clusterOptions.dnsLookup = expect.any(Function);
clusterOptions.redisOptions = { db: 0, tls: {} };
clusterTest(clusterOptions);
});
test('if the cluster variable is not set, then the module should return a standalone client', () => {
config.redis.cluster = false;
// eslint-disable-next-line global-require
require('./client');
expect(Redis).toHaveBeenCalled();
});
});
My colleague seems to get the tests running individually, but using code branch, I get the above problem.
Any suggestions about where I've gone wrong?

SFTP through HTTP proxy using ssh2-sftp-client library in Node.js

I need to transfer files using the Node.js library ssh2-sftp-client. My problem is I need to connect through an HTTP Proxy. The documentation has a step for SOCKS proxy. I am wondering how this can be achieved with HTTP proxy.
My version is based on the solution from your link. All the credits go to https://github.com/lmdc45
import {Injectable, Logger} from '#nestjs/common';
import * as Client from 'ssh2-sftp-client'
import * as fs from "fs";
import {ConfigService} from "#nestjs/config";
import * as http from "http";
import {SocksClient} from "socks";
import {Socket} from "net";
import {SocksClientOptions} from "socks/typings/common/constants";
export type SftpProxyConfig = { targetHost: string, targetPort: number, proxyType: 'http' | 'socks4' | 'socks5', proxyHost: string, proxyPort: number }
#Injectable()
export class SftpService {
private readonly logger = new Logger(SftpService.name);
constructor(
private readonly configService: ConfigService
) {}
async doSftpAction(
connectionOptions?: Client.ConnectOptions
) {
const sftp = new Client();
const connectionInfo = connectionOptions ? connectionOptions : await this.getSFTPConnectionInfo();
let sftpWrapper;
try {
sftpWrapper = await sftp.connect(connectionInfo);
// Here comes your code
} catch (error) {
this.logger.error(error.message, error);
throw new Error(error.message);
} finally {
if (sftpWrapper) {
await sftp.end();
}
}
}
private async getSFTPConnectionInfo(): Promise<Client.ConnectOptions> {
const host = this.configService.get<string>('SFTP_HOST');
const port = this.configService.get<number>('SFTP_PORT');
const connectionOptions: Client.ConnectOptions = {host, port};
const username = this.configService.get<string>('SFTP_USER');
const password = this.configService.get<string>('SFTP_PASSWORD');
const privateKey = this.configService.get<string>('SFTP_SSH_KEY');
const passphrase = this.configService.get<string>('SFTP_SSH_PASSPHRASE');
const proxyHost = this.configService.get<string>('SFTP_PROXY_HOST');
const proxyPort = this.configService.get<number>('SFTP_PROXY_PORT');
const proxyType = this.configService.get<'http' | 'socks4' | 'socks5'>('SFTP_PROXY_TYPE')
const debug = this.configService.get('SFTP_DEBUG_LOG') || false;
if (username)
connectionOptions.username = username;
if (password)
connectionOptions.password = password;
else if (privateKey) {
connectionOptions.privateKey = fs.readFileSync(this.configService.get<string>('SFTP_SSH_KEY'));
if (passphrase)
connectionOptions.passphrase = passphrase;
}
if (proxyHost && proxyPort && proxyType)
connectionOptions.sock = await this.getProxy({
targetHost: host,
targetPort: port,
proxyHost,
proxyPort,
proxyType
});
connectionOptions.debug = JSON.parse(debug) ? information => this.logger.log(information) : undefined;
return connectionOptions;
}
public async getProxy(config: SftpProxyConfig): Promise<NodeJS.ReadableStream> {
return (config.proxyType === 'http' ?
await this.getHttpProxy(config) :
await this.getSocksProxy(config)) as NodeJS.ReadableStream;
}
private async getHttpProxy(config: SftpProxyConfig):Promise<Socket>{
// Make a request to a tunneling proxy
const res = new Promise<Socket>((resolve, reject) => {
const options = {
port: config.proxyPort,
host: config.proxyHost,
method: 'CONNECT',
path: `${config.targetHost}:${config.targetPort}`
};
const req = http.request(options);
req.end();
req.on('connect', (res, socket, head) => {
this.logger.log(`Status Code: ${res.statusCode}`);
resolve(socket);
});
});
return res;
}
private async getSocksProxy(config: SftpProxyConfig): Promise<Socket> {
const options = {
proxy: {
host: config.proxyHost, // ipv4 or ipv6 or hostname
port: config.proxyPort,
type: config.proxyType === 'socks4' ? 4 : 5 // Proxy version (4 or 5)
},
command: 'connect', // SOCKS command (createConnection factory function only supports the connect command)
destination: {
host: config.targetHost,
port: config.targetPort
}
};
const info = await SocksClient.createConnection(options as SocksClientOptions);
return info.socket;
}
}

Mongodb Typescript UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'conn' of undefined at Object.connectToDatabase [as default]

typescript connection to mongo database throws error , it can read conn of undefined after conn has been declared globally
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'conn' of undefined
at Object.connectToDatabase [as default]
import { MongoClient, Db } from "mongodb";
import config from "../config/config";
const { dbName, mongoDBUri } = config;
type MongoConnection = {
client: MongoClient;
db: Db;
};
declare global {
namespace NodeJS {
interface Global {
mongodb: {
conn: MongoConnection | null;
promise: Promise<MongoConnection> | null;
};
}
}
}
let cached = global.mongodb;
async function connectToDatabase() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
cached.promise = MongoClient.connect(mongoDBUri as string, opts).then(
(client) => {
return {
client,
db: client.db(dbName),
};
}
);
}
cached.conn = await cached.promise;
return cached.conn;
}
export default connectToDatabase;
You can use the below setup
//interfaces/db.interface
export interface dbConfig {
host: string;
port: number;
database: string;
username: string;
password: string;
}
//database.ts
import { dbConfig } from "#interfaces/db.interface";
const { host, port, database, username, password }: dbConfig = config.get("dbConfig");
export const dbConnection = {
url: `mongodb://${username}:${password}#${host}:${port}/${database}?authSource=admin`,
options: {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
},
};
//app.ts (express app)
import { dbConnection } from "#databases";
constructor(routes: Routes[]) {
this.app = express();
this.port = process.env.PORT || 5000;
this.env = process.env.NODE_ENV || "development";
this.connectToDatabase();
}
private connectToDatabase() {
if (this.env !== "production") {
set("debug", true);
}
connect(dbConnection.url, dbConnection.options)
.catch((error) =>
console.log(`${error}`)
);
}
Here I am assuming you have the setup of paths in the tsconfig.json file so that # will work in imports.
After several times of trying, I had to use the NextJs MongoDB connection pattern and convert it to typescript and it worked perfectly fine
import config from "./../config/config";
import { MongoClient, Db } from "mongodb";
const { dbName, mongoDBUri } = config;
if (!mongoDBUri) {
throw new Error(
"Define the mongoDBUri environment variable inside .env"
);
}
if (!dbName) {
throw new Error(
"Define the dbName environment variable inside .env"
);
}
type MongoConnection = {
client: MongoClient;
db: Db;
};
declare global {
namespace NodeJS {
interface Global {
mongodb: {
conn: MongoConnection | null;
promise: Promise<MongoConnection> | null;
};
}
}
}
let cached = global.mongodb;
if (!cached) {
cached = global.mongodb = { conn: null, promise: null };
}
export default async function connectToDatabase() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
console.log("Establishing new database connection");
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
cached.promise = MongoClient.connect(mongoDBUri as string, opts).then(
(client) => {
return {
client,
db: client.db(dbName),
};
}
);
}
cached.conn = await cached.promise;
return cached.conn;
}

Node.JS multiple new Class only last executed (http & socket.io-client)

First sorry for my bad english.
I tried connect to server using http token authentication with socket.io as exchange data. When socket.io got an error then request token by http and try to connect again.
I put the connection function in the class then call it from main program. My problem is when I create multiple client why only last work, here my code
call the class
const SocketHttpClient = require('./class/socket-http-client')
let ioClient_1 = new SocketHttpClient('localhost', 3001, 'admin', 'admin', '/login')
let ioClient_2 = new SocketHttpClient('localhost', 3002, 'admin', 'admin', '/login')
let ioClient_3 = new SocketHttpClient('localhost', 3003, 'admin', 'admin', '/login')
connection class
// =================================================================================================
// INCLUDE LIBRARY
// =================================================================================================
const ioClient = require('socket.io-client')
const http = require('http')
const cookie = require('cookie')
const log = require('./log-custom')
// =================================================================================================
// VARIABLE
// =================================================================================================
let data
let httpOption
ioOption = {
transportOptions: {
polling: {
extraHeaders: {
Cookie: 'hello=world'
}
}
}
}
module.exports = class Socket {
constructor(ioHostname, ioPort, httpUsername, httpPassword, loginPath) {
data = {
username: httpUsername,
password: httpPassword
}
data = JSON.stringify(data)
httpOption = {
hostname: ioHostname,
port: ioPort,
path: loginPath,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length
}
}
log(`Connecting to "${httpOption.hostname}:${httpOption.port}"`)
this.io = ioClient(`http://${httpOption.hostname}:${httpOption.port}`, ioOption)
this.io.on('connect', () => {
log(`Connected to socket "${httpOption.hostname}:${httpOption.port}"`)
})
this.io.on('disconnect', () => {
log(`Disconnected to socket "${httpOption.hostname}:${httpOption.port}"`)
log('Reconnecting...')
})
this.io.on('error', () => {
console.log(`error: ${httpOption.hostname}:${httpOption.port}`)
// When error then try to reconnect...
setTimeout(() => {
this.io.disconnect()
this.io.close()
this.req = http.request(httpOption, res => {
this.c = cookie.parse(res.headers['set-cookie'][0])
ioOption.transportOptions.polling.extraHeaders.Cookie = `sid=${this.c.sid}`
this.io.io.opts.query = ioOption
})
this.req.write(data)
this.req.end()
this.req.on('error', err => {
})
// delay to reconnect
setTimeout(() => {
this.io.connect()
}, 1000)
}, 1000)
})
}
}
the result:
ioClient_1 => not working
ioClient_2 => not working
ioClient_3 => working fine as expected
Where I doing wrong here? Need advice from you guys. Thank you in advanced.

Resources