@@ -12,11 +12,24 @@ const sigLen = parseInt(process.env.SIG_LEN);
1212const hashLen = parseInt ( process . env . HASH_LEN ) ;
1313const ttl = parseInt ( process . env . TTL ) ;
1414const cacheTtl = parseInt ( process . env . CACHE_TTL ) ;
15+ const streamTimeout = parseInt ( process . env . STREAM_TIMEOUT ) ;
16+ const maxStreamCount = parseInt ( process . env . MAX_STREAM_COUNT ) ;
17+
1518const dbKeyPrefix = {
1619 manyToOne : "m2o:" ,
1720 oneToMany : "o2m:" ,
1821 oneToOne : "o2o:" ,
19- cache : "cache:"
22+ cache : "cache:" ,
23+ stream : {
24+ public : {
25+ send : "pipePubSend:" ,
26+ receive : "pipePubRecv:"
27+ } ,
28+ private : {
29+ send : "pipePrivSend:" ,
30+ receive : "pipePrivRecv:"
31+ }
32+ }
2033 }
2134// Redis client for user database
2235const redisData = new Redis ( {
@@ -44,9 +57,9 @@ function sign(str){
4457 return createHmac ( 'md5' , secret ) . update ( str ) . digest ( 'base64url' ) . substring ( 0 , sigLen ) ;
4558}
4659
47- //Brief: Return random base64url string of given length
60+ // Brief: Return random base64url string of given length
4861function randStr ( len = hashLen ) {
49- const byteSize = Math . ceil ( len * 6 / 8 ) ;
62+ const byteSize = Math . ceil ( len * 6 / 8 ) ; // base64 char is 6 bit, byte is 8
5063 const buff = Buffer . alloc ( byteSize ) ;
5164 getRandomValues ( buff ) ;
5265 return buff . toString ( 'base64url' ) . substring ( 0 , len ) ;
@@ -56,14 +69,15 @@ export function id(){
5669 return sign ( 'id' ) ;
5770}
5871
59- export function validate ( key ) {
72+ export function validate ( key , silent = true ) {
6073 const sig = key . substring ( 0 , sigLen ) ;
6174 const hash = key . substring ( sigLen ) ;
6275 if ( sig === sign ( hash + 'public' ) ) {
6376 return 'public' ;
6477 } else if ( sig === sign ( hash + 'private' ) ) {
6578 return 'private' ;
6679 } else {
80+ if ( ! silent ) throw new Error ( 'Invalid Key' ) ;
6781 return false ;
6882 }
6983}
@@ -199,3 +213,22 @@ export async function oneToOneTTL(privateKey, key){
199213 ] )
200214 return { ttl : bool ? ttl : 0 } ;
201215}
216+
217+ // Tokens are stored in LIFO stacks. Old and unused tokens are trimmed.
218+ export async function streamToken ( privateOrPublicKey , receive = true ) {
219+ const type = validate ( privateOrPublicKey , false ) ;
220+ const typeComplement = ( type == 'private' ) ? 'public' : 'private' ;
221+ const publicKey = genPublicKey ( privateOrPublicKey ) ;
222+ const mode = receive ? "receive" : "send" ;
223+ const modeComplement = receive ? "send" : "receive" ;
224+ const existing = await redisData . lpop ( dbKeyPrefix . stream [ typeComplement ] [ modeComplement ] + publicKey ) ;
225+ if ( existing ) return existing ;
226+ const token = randStr ( ) ;
227+ const dbKey = dbKeyPrefix . stream [ type ] [ mode ] + publicKey ;
228+ Promise . all ( [
229+ redisData . lpush ( dbKey , token ) ,
230+ redisData . expire ( dbKey , streamTimeout ) ,
231+ redisData . ltrim ( dbKey , 0 , maxStreamCount - 1 )
232+ ] )
233+ return token ;
234+ }
0 commit comments