Loopback Interceptor values are over written from other request

114 Views Asked by At

I am working with loopback interceptor to use its value in the controller function.

from the moment I implemented interceptor is all i know that every user requests have different instance of interceptor.

here is my code for intercepting token

export class ExtractTokenInterceptor implements Provider<Interceptor> {
    constructor() { }

    value(): Interceptor {
        return this.intercept.bind(this);
    }

    async intercept<T>(
        invocationCtx: InvocationContext,
        next: () => ValueOrPromise<T>,
    ) {
        const req: any = await 
invocationCtx.get(RestBindings.Http.REQUEST, {
            optional: true,
        });
        // const req: any = await invocationCtx.parent.get(RestBindings.Http.REQUEST, { optional: true });
        const authorization = req.headers.hasOwnProperty('authorization') ? req.headers.authorization : null;
        const userName = req.headers.hasOwnProperty('x-username') ? req.headers['x-username'] : null;
        const token = authorization ? req.headers.authorization.split(' ')[1] : null;
        const referer = req.headers.referer;
        const clientIp = req.headers['x-forwarded-for'];
        invocationCtx.targetClass.prototype.token = token;
        invocationCtx.targetClass.prototype.referer = referer;
        invocationCtx.targetClass.prototype.clientIp = clientIp;
        invocationCtx.targetClass.prototype.userName = userName;
    

        if(invocationCtx.targetClass.prototype.jwt) {
            console.log('ERROR! INTERCEPTOR HAS VALUE', invocationCtx.targetClass.prototype.jwt);
        }

        if (token) {
            const decodedJwt = jwtDecode(token);
            console.log(invocationCtx.targetClass.prototype.jwt);
            invocationCtx.targetClass.prototype.jwt = decodedJwt;

            const loopCnt = 20;
            if(decodedJwt.preferred_username === 'user1') {
                let a = 0;
                const timeout = (ms) => {
                    return new Promise(resolve => setTimeout(resolve, ms));
                }
                while (a < loopCnt) { // i will delay the interceptor for specific user
                    await timeout(1000);
                    console.log('['+a+'] user is', invocationCtx.targetClass.prototype.jwt.preferred_username);
                    invocationCtx.targetClass.prototype.counter = a++;
                }
            }
        }
    

        const result = await next();
        return result;
    }
}

What happen here is this. I have two user named user1 and user2.

both user is logged in. both user will trigger a specific endpoint

if you can see in my interceptor, i put a delay in user1 interceptor.

i will print the current interceptor value base on the token.

then if user2 will trigger the same end point, while the interceptor of user1 is not yet finished, i can see the print for user1 will be changed by user2.

take a look at the code that i only put delay for user1 only so user2 can proceed to controller function

i cant find any solution from loopback why interceptor is using one instance per endpoint

1

There are 1 best solutions below

0
Shubham P On

The problem is that you are assigning the data in the prototype of the targetClass, which is causing issues with concurrency since the prototype is naturally shared across all instances of the class.

So when multiple requests are made, they will try to modify the same prototype object.

To solve this you can bind token, referrer etc. in the invocationCtx instead.

Quoting a code snippet from interceptors test cases that shows how can you bind and use those value in your class:

class MyController {
  @intercept(async (invocationCtx, next) => {
    invocationCtx.bind('name').to('Mary');
    return next();
  })
  async interceptedHello(@inject('name') name: string) {
    return `Hello, ${name}`;
  }
}