Why 'new WebSocket()' doesn't work for nestjs? - javascript

I am sending with const socket = new WebSocket('ws://localhost:3000'); socket.send('hello world'); from client and i receive log 'connected' log on the server but not 'hello world'. socket.send() not working for NestJS. When I look at chrome network. It sends the data but not receiving in server.
here is the code: chat.gateway.ts
#WebSocketGateway()
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit {
handleConnection(client: any, ...args: any[]): any {
console.log('connected');
}
handleDisconnect(client: any): any {
console.log(client);
console.log('disconnected');
}
#SubscribeMessage('message')
handleEvent(client: any, data: any): WsResponse<any> {
const event = 'events';
return { event, data };
}
afterInit(server: any): any {
console.log(server.path);
}
}
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { WsAdapter } from '#nestjs/platform-ws';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: 'http://localhost:4200',
credentials: true,
});
app.useWebSocketAdapter(new WsAdapter(app));
await app.listen(3000);
}
bootstrap();

Check out the Github examples: https://github.com/nestjs/nest/tree/master/sample/16-gateways-ws
On the client side:
const socket = new WebSocket('ws://localhost:80');
socket.onopen = () => {
console.log('Connected');
socket.send(
JSON.stringify({
event: 'message',
data: 'my very important message',
}),
);
socket.onmessage = (data) => {
console.log(data);
};
};
This should do the trick

Related

URQL WSS connection with GraphQL-WS says error 4500

import {
createClient,
defaultExchanges,dedupExchange, cacheExchange, fetchExchange,
subscriptionExchange,
gql
} from "#urql/core";
import { createClient as createWSClient } from "graphql-ws";
import { pipe, subscribe } from "wonka";
import { getToken, setToken } from "./helper";
const wsClient = createWSClient({
url: 'wss://**********/subscriptions',
reconnect: true,
});
const client = createClient({
url: "https://***********/",
fetchOptions: () => {
const token = getToken()
return token ? { headers: { authorization: `Bearer "${token}"` } } : {}
},
// the default:
exchanges: [
...defaultExchanges,
subscriptionExchange({
forwardSubscription(operation) {
return {
subscribe: (sink) => {
const dispose = wsClient.subscribe(operation, sink);
return {
unsubscribe: dispose,
};
},
};
},
}),
]
});
SUB_TO_MESSAGES = async () => {
console.log('sub')
const token = getToken();
console.log(String(token))
const { unsubscribe } = pipe(
await client.subscription(messageAdded,{ jwt: token }),
subscribe((result) => {
console.log(result)
})
)
};
I dont get the same issue with try and catch using GraphQL-WS but I still dont get any data from the server. The assignment is a vanillaJS project using GraphQL.I didndt post the url, jwt token,or the GET, POST, REgG as they work as intended. The rendering is done with a proxy. The error message is:
Connection Closed: 4500 Cannot read properties of undefined (reading 'Authorization')
Even playground doesnt work. Something wrong with the endpoint. It worked 2 weeks ago but admin says it still work yet I can find the problem. It used to work for me.
Here is the try and catch version:
import { createClient} from "graphql-ws";
import pStore from "./handler.js";
import { getToken } from "./helper";
const client = createClient({
url: "wss://******/subscriptions",
reconnect: true,
connectionParams:{
headers: {
"Authorization":`Bearer ${getToken()}`
}
},
})
async SUB_MESSAGE() {
try {
console.log('called Gql server')
const onNext = (res) => {
let obj = res.data.messageAdded
console.log(obj)
pStore[obj.id] = obj
pStore.render(obj)
};
let unsubscribe = () => {
/* complete the subscription */
};
new Promise((resolve, reject) => {
client.subscribe({
query: `subscription{messageAdded(jwt:"${getToken()}"){id text fromAgent createdAt updatedAt}}`,
},
{
next: (data)=> onNext(data),
error: reject,
complete: () => resolve(true),
})
})
}catch(error){
console.error('There has been a problem with your ws operation:', error);
}
}
Either way I think its a ad character, scope issue but I dont know where.

Passing request into Apollo Server context generated in function

Learning GraphQL and a bit stuck with passing req into a generate context function I made to keep things neat.
I think I am doing something dumb with the line createContext((req) => req) as if I console.log( ctx.request ) in a route handler I get [Function (anonymous)]
Whats an alternative way to capture the this.req from the server.ts scope and pass it into createContext?
server.ts
import { ApolloServer } from 'apollo-server'
import { schema } from './nexusSchema'
import { createContext } from './context'
const server = new ApolloServer({
schema,
context: createContext((req) => req),
})
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})
context.ts
import { PrismaClient, Prisma as PrismaTypes } from '#prisma/client'
import { PrismaDelete, onDeleteArgs } from '#paljs/plugins'
import { PubSub } from 'apollo-server'
class Prisma extends PrismaClient {
constructor(options?: PrismaTypes.PrismaClientOptions) {
super(options)
}
async onDelete(args: onDeleteArgs) {
const prismaDelete = new PrismaDelete(this)
await prismaDelete.onDelete(args)
}
}
const prisma = new Prisma()
const pubsub = new PubSub()
export interface Context {
prisma: Prisma
select: any
pubsub: PubSub
request: {
request: {
headers: {
authorization: string
}
}
connection: {
context: {
Authorization: string
}
}
}
}
export function createContext(req): Context {
return {
prisma,
select: {},
pubsub,
request: req,
}
}
I was being dumb after all.
Note, I also had to adjust the interface for the request object to suit Apollo Server 2!
server.ts
const server = new ApolloServer({
schema,
context: createContext,
})
context.ts
export interface Context {
prisma: Prisma
select: any
pubsub: PubSub
request: {
req: {
headers: {
authorization: string
}
}
connection: {
context: {
Authorization: string
}
}
}
}
export const createContext = (req): Context => {
return {
prisma,
select: {},
pubsub,
request: req,
}
}

Spring RSocket Security + RSocket-WebSocket-Client (browser)

I am trying to make a site in Vue and backend on Spring. I want to use rsocket to transfer data, but as soon as I add rsocket seurity in spring, I get :
'metadata is malformed'
Would like to take a look at a working example using jwt/simpleauth
I solved the issue with Simple Auth, now I would like to synchronize this authorization with spring websecurity.
Those. so that routing in rsocket checks authorization via websecurity. I know that this can be implemented through the jwt token, i.e. send a jwt token to a client via rest, but how can I do this in code? JS client (browser) and Spring, how do I generate userdetails token?
Just in case, I'll leave an example of the simpleauth implementation:
// METADATA BUILDER
import {encodeRoute, encodeBearerAuthMetadata, encodeSimpleAuthMetadata, encodeAndAddCustomMetadata, encodeAndAddWellKnownMetadata, MESSAGE_RSOCKET_ROUTING, MESSAGE_RSOCKET_AUTHENTICATION} from "rsocket-core";
export default class Metadata {
constructor(json) {
this.route = json['route'];
this.auth = json['auth'];
}
toMetadata() {
let metadata = Buffer.alloc(0);
if (this.auth) {
if (this.auth["type"] === 'bearer') {
metadata = encodeAndAddCustomMetadata(
metadata,
MESSAGE_RSOCKET_AUTHENTICATION.string,
encodeBearerAuthMetadata(this.auth["token"]),
);
}
if (this.auth["type"] === 'simple') {
metadata = encodeAndAddCustomMetadata(
metadata,
MESSAGE_RSOCKET_AUTHENTICATION.string,
encodeSimpleAuthMetadata(this.auth["username"], this.auth["password"]),
);
}
}
if (this.route) {
metadata = encodeAndAddWellKnownMetadata(
metadata,
MESSAGE_RSOCKET_ROUTING,
encodeRoute(this.route)
);
}
return metadata;
}
}
// RSOCKET CLIENT CLASS
import RSocketWebSocketClient from "rsocket-websocket-client";
import {BufferEncoders, MESSAGE_RSOCKET_COMPOSITE_METADATA, RSocketClient,toBuffer} from "rsocket-core";
import Metadata from "./metadata";
export default class SpringClient {
constructor(wsUrl, keepAlive = 60000, lifetime = 180000, dataMimeType = "application/json") {
this.client = new RSocketClient({
"setup": {
"keepAlive": keepAlive,
"lifetime": lifetime,
"dataMimeType": dataMimeType,
"metadataMimeType": MESSAGE_RSOCKET_COMPOSITE_METADATA.string
},
"transport": new RSocketWebSocketClient({
"url": wsUrl
}, BufferEncoders)
});
}
bearerAuth(token) {
this.auth = {type: "bearer", token: token}
}
simpleAuth(username, password) {
this.auth = {type: "simple", username: username, password: password}
}
logout() {
this.auth = null;
}
connect(
completeCallback = (socket) => {
}, errorCallback = (error) => {
}, subscribeCallback = (cancel) => {
}
) {
this.client.connect().subscribe({
onComplete: socket => {
this.socket = socket;
completeCallback(socket);
},
onError: error => {
errorCallback(error);
},
onSubscribe: cancel => {
subscribeCallback(cancel);
}
});
}
requestResponse(data, route,
completeCallback = (data) => {
},
errorCallback = (error) => {
},
subscribeCallback = (cancel) => {
}
) {
if (this.socket) {
const metadata = new Metadata({
route: route,
auth: this.auth
}).toMetadata();
data = toBuffer(data);
this.socket.requestResponse({
data,
metadata
}).subscribe({
onComplete: data => {
completeCallback(data);
},
onError: error => {
errorCallback(error);
},
onSubscribe: cancel => {
subscribeCallback(cancel);
}
});
}
}
}
// EXAMPLE, HOW TO USE
import SpringClient from "./springclient";
this.client = new SpringClient("ws://localhost:7000/", 5000, 15000, "text/plain");
this.client.connect(
(socket) => {
console.log("got connection complete");
this.socket = socket;
},
(error) => {
console.log("got connection error");
console.error(error);
},
(cancel) => {
console.log("got connection subscribe");
/* call cancel() to abort */
}
)
this.client.simpleAuth("LOGIN", "PASSWORD");
this.client.requestResponse("MESSAGE", "ROUTE",
(data) => {
console.log("got response with requestResponse");
console.log(data.data);
},
(error) => {
console.log("got error with requestResponse");
console.error(error);
},
(cancel) => {
console.log(message);
/* call cancel() to stop onComplete/onError */
}
);

Typescript ExpressJS - injecting services into REST API Controllers

I am attempting an OOP approach to my express rest API with Typescript and having issues composing classes.
The idea is to have a AuthController class that calls a private AuthService class to interact with the database. However, when I attempt to call the AuthService class with the AuthController class, it always returns undefined.
App class which initializes my express app and registers my controllers:
class App {
private app;
private controllers: Controller[];
constructor(controllers: Controller[]) {
this.app = express();
this.controllers = controllers;
this.config();
this.initializeRoutes();
this.initializeErrorHandler();
}
config() {
console.log("running config");
this.app.use(cors());
this.app.use(express.json());
this.app.use(helmet());
}
public listen() {
this.app.listen(3000);
}
initializeRoutes() {
this.controllers.forEach((controller: any) => {
this.app.use("/", controller.router);
});
}
initializeErrorHandler() {
this.app.use(errorMiddleware);
}
}
(async () => {
try {
await connection(); //creates my database connection
} catch (error) {
console.log("Error while connecting to the database", error);
return error;
}
const app = new App([new AuthController()]);
app.listen();
})();
here is my AuthController being initialized in my call to const app = new App([new AuthController()]);
export default class AuthController implements Controller {
public path = "/api/auth";
public router = Router();
private authService: AuthService = new AuthService();
constructor() {
this.initializeRoutes();
}
public initializeRoutes() {
//login route
this.router.post(this.path.concat("/login"), this.login);
this.router.post(
this.path.concat("/register"),
validationMiddleware(CreateUserDto),
this.register
);
this.router.post(this.path.concat("/resetpassword"), this.resetPassword);
this.router.post(this.path.concat("/newpassword"), this.newPassword);
}
public async register(req: Request, res: Response, next: NextFunction) {
const userData: CreateUserDto = req.body;
try {
let {
tokens: { xAuthToken, xRefreshToken },
user,
} = await this.authService.register(userData);
res.setHeader("x-auth-token", xAuthToken);
res.setHeader("x-refresh-token", xRefreshToken);
res.json(user);
} catch (e) {
next(e);
}
}
}
and finally AuthService class
export class AuthService {
private userRepo: Repository<User> = getRepository(User);
constructor() {}
public async register(userData: CreateUserDto) {
let foundUser = await this.userRepo.findOne({
where: { email: userData.email.toLowerCase() },
});
console.log(foundUser);
if (foundUser) {
throw new EmailInUseException();
} else {
console.log(foundUser);
const user = new User();
user.email = userData.email.toLowerCase();
user.password = userData.password;
await this.userRepo.save(user);
const tokens = this.createTokens(user);
return {
tokens,
user,
};
}
}
}
Anytime I call the AuthService from the AuthController, I receive an 'error cannot read property 'authService' of undefined'.
I have tried changing the code to initialize the AuthService directly const app = new App([new AuthController(new AuthService()]); but this doesn't fix the issue.
Any help is appreciated!
Found the issue. The 'this' context was being changed. I switched my 'register' method to an arrow function which fixed the problem
original:
class AuthController{
public async register(req: Request, res: Response, next: NextFunction) {
const userData: CreateUserDto = req.body;
try {
let {
tokens: { xAuthToken, xRefreshToken },
user,
} = await this.authService.register(userData);
res.setHeader("x-auth-token", xAuthToken);
res.setHeader("x-refresh-token", xRefreshToken);
res.json(user);
} catch (e) {
next(e);
}
}
}
new:
class AuthController{
public register = async (req: Request, res: Response, next: NextFunction) => {
const userData: CreateUserDto = req.body;
try {
let {
tokens: { xAuthToken, xRefreshToken },
user,
} = await this.authService.register(userData);
res.setHeader("x-auth-token", xAuthToken);
res.setHeader("x-refresh-token", xRefreshToken);
res.json(user);
} catch (e) {
next(e);
}
};
}

How to use GraphQL subscription correctly?

I have a GraphQL powered app. The query and mutation parts work well. I try to add GraphQL subscription.
The server GraphQL subscription part code is inspired by the demo in the readme of apollographql/subscriptions-transport-ws.
Please also check the comments in the code for more details.
import Koa from 'koa';
import Router from 'koa-router';
import graphqlHTTP from 'koa-graphql';
import asyncify from 'callback-to-async-iterator';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import firebase from 'firebase-admin';
import { execute, subscribe } from 'graphql';
import { GraphQLObjectType, GraphQLString } from 'graphql';
const MeType = new GraphQLObjectType({
name: 'Me',
fields: () => ({
name: { type: GraphQLString },
// ...
}),
});
const listenMe = async (callback) => {
// Below the firebase API returns real-time data
return firebase
.database()
.ref('/users/123')
.on('value', (snapshot) => {
// snapshot.val() returns an Object including name field.
// Here I tested is correct, it always returns { name: 'Rose', ... }
// when some other fields inside got updated in database.
return callback(snapshot.val());
});
};
const Subscription = new GraphQLObjectType({
name: 'Subscription',
fields: () => ({
meChanged: {
type: MeType,
subscribe: () => asyncify(listenMe),
},
}),
});
const schema = new GraphQLSchema({
query: Query,
mutation: Mutation,
subscription: Subscription,
});
const app = new Koa();
app
.use(new Router()
.post('/graphql', async (ctx) => {
// ...
await graphqlHTTP({
schema,
graphiql: true,
})(ctx);
})
.routes());
const server = app.listen(3009);
SubscriptionServer.create(
{
schema,
execute,
subscribe,
},
{
server,
path: '/subscriptions',
},
);
I am using Altair GraphQL Client to test since it supports GraphQL subscription.
As the screenshot shows, it does get new data every time when the data changes in database.
However, meChanged is null and it does not throw any error. Any idea? Thanks
Finally have a new library can do the work without full Apollo framework.
https://github.com/enisdenjo/graphql-ws
Here are the codes that I have succeed:
Server (GraphQL Schema Definition Language)
import { useServer } from 'graphql-ws/lib/use/ws';
import WebSocket from 'ws';
import { buildSchema } from 'graphql';
const schema = buildSchema(`
type Subscription {
greeting: String
}
`);
const roots = {
subscription: {
greeting: async function* sayHiIn5Languages() {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
yield { greeting: hi };
}
},
},
};
const wsServer = new ws.Server({
server, // Your HTTP server
path: '/graphql',
});
useServer(
{
schema,
execute,
subscribe,
roots,
},
wsServer
);
Server (GraphQL.js GraphQLSchema object way)
import { execute, subscribe, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
import { useServer } from 'graphql-ws/lib/use/ws';
import WebSocket from 'ws';
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const subscription = new GraphQLObjectType({
name: 'Subscription',
fields: {
greeting: {
type: GraphQLString,
resolve: (source) => {
if (source instanceof Error) {
throw source;
}
return source.greeting;
},
subscribe: () => {
return pubsub.asyncIterator('greeting');
},
},
},
});
const schema = new GraphQLSchema({
query,
mutation,
subscription,
});
setInterval(() => {
pubsub.publish('greeting', {
greeting: 'Bonjour',
});
}, 1000);
const wsServer = new ws.Server({
server, // Your HTTP server
path: '/graphql',
});
useServer(
{
schema,
execute,
subscribe,
roots,
},
wsServer
);
Client
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://localhost:5000/graphql',
});
client.subscribe(
{
query: 'subscription { greeting }',
},
{
next: (data) => {
console.log('data', data);
},
error: (error) => {
console.error('error', error);
},
complete: () => {
console.log('no more greetings');
},
}
);
DISCLOSE: I am not associated with the library.

Categories

Resources