@@ -13,6 +13,7 @@ import uuid from 'uuid';
1313import { runLiveQueryEventHandlers } from '../triggers' ;
1414import { getAuthForSessionToken , Auth } from '../Auth' ;
1515import { getCacheController } from '../Controllers' ;
16+ import LRU from 'lru-cache' ;
1617
1718class ParseLiveQueryServer {
1819 clients : Map ;
@@ -45,8 +46,17 @@ class ParseLiveQueryServer {
4546 Parse . serverURL = serverURL ;
4647 Parse . initialize ( config . appId , Parse . javaScriptKey , config . masterKey ) ;
4748
49+ // The cache controller is a proper cache controller
50+ // With access to User and Roles
4851 this . cacheController = getCacheController ( config )
4952
53+ // This auth cache stores the promises for each auth resolution
54+ // The main benefit is to be able to reuse the same user / session token resolution
55+ // And to chain
56+ this . authCache = new LRU ( {
57+ max : 500 , // 500 concurrent
58+ maxAge : 60 * 60 * 1000 // 1h
59+ } ) ;
5060 // Initialize websocket server
5161 this . parseWebSocketServer = new ParseWebSocketServer (
5262 server ,
@@ -334,24 +344,38 @@ class ParseLiveQueryServer {
334344 }
335345
336346 async getAuthForSessionToken ( sessionToken : ?string ) : { auth: ?Auth , userId : ?string } {
347+ if ( ! sessionToken ) {
348+ return { } ;
349+ }
350+ const fromCache = this . authCache . get ( sessionToken ) ;
351+ if ( fromCache ) {
352+ return fromCache ;
353+ }
337354 try {
338- const auth = await getAuthForSessionToken ( { cacheController : this . cacheController , sessionToken : sessionToken } ) ;
339- return { auth, userId : auth && auth . user && auth . user . id } // return the ID of the found user
355+ const authPromise = getAuthForSessionToken ( { cacheController : this . cacheController , sessionToken : sessionToken } )
356+ . then ( ( auth ) => {
357+ return { auth, userId : auth && auth . user && auth . user . id } ;
358+ } , ( ) => {
359+ // If you can't continue, let's just wrap it up and delete it.
360+ // Next time, one will try again
361+ this . authCache . del ( sessionToken ) ;
362+ } ) ;
363+ this . authCache . set ( sessionToken , authPromise ) ;
364+ return authPromise ;
340365 } catch ( e ) { /* ignore errors */ }
341366 return { } ;
342367 }
343368
344369 async _matchesCLP ( classLevelPermissions : ?any , object : any , client : any , requestId : number , op : string) : any {
345370 // try to match on user first, less expensive than with roles
346371 const subscriptionInfo = client . getSubscriptionInfo ( requestId ) ;
347- if ( typeof subscriptionInfo === 'undefined' ) {
348- return Promise . resolve ( [ '*' ] ) ;
349- }
350- const subscriptionSessionToken = subscriptionInfo . sessionToken ;
351372 const aclGroup = [ '*' ] ;
352- const { userId } = await this . getAuthForSessionToken ( subscriptionSessionToken ) ;
353- if ( userId ) {
354- aclGroup . push ( userId ) ;
373+ let userId ;
374+ if ( typeof subscriptionInfo !== 'undefined' ) {
375+ const { userId } = await this . getAuthForSessionToken ( subscriptionInfo . sessionToken ) ;
376+ if ( userId ) {
377+ aclGroup . push ( userId ) ;
378+ }
355379 }
356380 try {
357381 await SchemaController . validatePermission ( classLevelPermissions , object . className , aclGroup , op ) ;
@@ -390,9 +414,8 @@ class ParseLiveQueryServer {
390414 return Promise . resolve ( false ) ;
391415 }
392416
393- const subscriptionSessionToken = subscriptionInfo . sessionToken ;
394417 // TODO: get auth there and de-duplicate code below to work with the same Auth obj.
395- const { auth, userId } = await this . getAuthForSessionToken ( subscriptionSessionToken ) ;
418+ const { auth, userId } = await this . getAuthForSessionToken ( subscriptionInfo . sessionToken ) ;
396419 const isSubscriptionSessionTokenMatched = acl . getReadAccess ( userId ) ;
397420 if ( isSubscriptionSessionTokenMatched ) {
398421 return Promise . resolve ( true ) ;
0 commit comments