{
  "version": 3,
  "sources": ["../../src/presence/LocalPresence.ts"],
  "sourcesContent": ["\nimport { EventEmitter } from 'events';\nimport { spliceOne } from '../utils/Utils.ts';\nimport type { Presence } from './Presence.ts';\n\nimport { hasDevModeCache, isDevMode, getDevModeCache, writeDevModeCache } from '../utils/DevMode.ts';\n\ntype Callback = (...args: any[]) => void;\n\nexport class LocalPresence implements Presence {\n    public subscriptions = new EventEmitter();\n\n    public data: {[roomName: string]: string[]} = {};\n    public hash: {[roomName: string]: {[key: string]: string}} = {};\n\n    public keys: {[name: string]: string | number} = {};\n\n    private timeouts: {[name: string]: NodeJS.Timeout} = {};\n\n    constructor() {\n      //\n      // reload from local cache on devMode\n      //\n      if (\n        isDevMode &&\n        hasDevModeCache()\n      ) {\n        const cache = getDevModeCache();\n        if (cache.data) { this.data = cache.data; }\n        if (cache.hash) { this.hash = cache.hash; }\n        if (cache.keys) { this.keys = cache.keys; }\n      }\n    }\n\n    public subscribe(topic: string, callback: (...args: any[]) => void) {\n        this.subscriptions.on(topic, callback);\n        return Promise.resolve(this);\n    }\n\n    public unsubscribe(topic: string, callback?: Callback) {\n        if (callback)  {\n            this.subscriptions.removeListener(topic, callback);\n\n        } else {\n            this.subscriptions.removeAllListeners(topic);\n        }\n\n        return this;\n    }\n\n    public publish(topic: string, data: any) {\n        this.subscriptions.emit(topic, data);\n        return this;\n    }\n\n    public async channels (pattern?: string) {\n      let eventNames = this.subscriptions.eventNames() as string[];\n      if (pattern) {\n        //\n        // This is a limited glob pattern to regexp implementation.\n        // If needed, we can use a full implementation like picomatch: https://github.com/micromatch/picomatch/\n        //\n        const regexp = new RegExp(\n          pattern.\n            replaceAll(\".\", \"\\\\.\").\n            replaceAll(\"$\", \"\\\\$\").\n            replaceAll(\"*\", \".*\").\n            replaceAll(\"?\", \".\"),\n          \"i\"\n        );\n        eventNames = eventNames.filter((eventName) => regexp.test(eventName));\n      }\n      return eventNames;\n    }\n\n    public async exists(key: string): Promise<boolean> {\n        return (\n          this.keys[key] !== undefined ||\n          this.data[key] !== undefined ||\n          this.hash[key] !== undefined\n        );\n    }\n\n    public set(key: string, value: string) {\n        this.keys[key] = value;\n    }\n\n    public setex(key: string, value: string, seconds: number) {\n        this.keys[key] = value;\n        this.expire(key, seconds);\n    }\n\n    public expire(key: string, seconds: number) {\n        // ensure previous timeout is clear before setting another one.\n        if (this.timeouts[key]) {\n            clearTimeout(this.timeouts[key]);\n        }\n        this.timeouts[key] = setTimeout(() => {\n            delete this.keys[key];\n            delete this.timeouts[key];\n        }, seconds * 1000);\n    }\n\n    public get(key: string) {\n        return this.keys[key];\n    }\n\n    public del(key: string) {\n        delete this.keys[key];\n        delete this.data[key];\n        delete this.hash[key];\n    }\n\n    public sadd(key: string, value: any) {\n        if (!this.data[key]) {\n            this.data[key] = [];\n        }\n\n        if (this.data[key].indexOf(value) === -1) {\n            this.data[key].push(value);\n        }\n    }\n\n    public async smembers(key: string): Promise<string[]> {\n        return this.data[key] || [];\n    }\n\n    public async sismember(key: string, field: string) {\n        return this.data[key] && this.data[key].includes(field) ? 1 : 0;\n    }\n\n    public srem(key: string, value: any) {\n        if (this.data[key]) {\n            spliceOne(this.data[key], this.data[key].indexOf(value));\n        }\n    }\n\n    public scard(key: string) {\n        return (this.data[key] || []).length;\n    }\n\n    public async sinter(...keys: string[]) {\n      const intersection: {[value: string]: number} = {};\n\n      for (let i = 0, l = keys.length; i < l; i++) {\n        (await this.smembers(keys[i])).forEach((member) => {\n          if (!intersection[member]) {\n            intersection[member] = 0;\n          }\n\n          intersection[member]++;\n        });\n      }\n\n      return Object.keys(intersection).reduce((prev, curr) => {\n        if (intersection[curr] > 1) {\n          prev.push(curr);\n        }\n        return prev;\n      }, []);\n    }\n\n    public hset(key: string, field: string, value: string) {\n        if (!this.hash[key]) { this.hash[key] = {}; }\n        this.hash[key][field] = value;\n        return Promise.resolve(true);\n    }\n\n    public hincrby(key: string, field: string, incrBy: number) {\n        if (!this.hash[key]) { this.hash[key] = {}; }\n        let value = Number(this.hash[key][field] || '0');\n        value += incrBy;\n        this.hash[key][field] = value.toString();\n        return Promise.resolve(value);\n    }\n\n    public hincrbyex(key: string, field: string, incrBy: number, expireInSeconds: number) {\n        if (!this.hash[key]) { this.hash[key] = {}; }\n        let value = Number(this.hash[key][field] || '0');\n        value += incrBy;\n        this.hash[key][field] = value.toString();\n\n        //\n        // FIXME: delete only hash[key][field]\n        // (we can't use \"HEXPIRE\" in Redis because it's only available since Redis version 7.4.0+)\n        //\n        if (this.timeouts[key]) {\n          clearTimeout(this.timeouts[key]);\n        }\n        this.timeouts[key] = setTimeout(() => {\n            delete this.hash[key];\n            delete this.timeouts[key];\n        }, expireInSeconds * 1000);\n\n        return Promise.resolve(value);\n    }\n\n    public async hget(key: string, field: string) {\n        return (typeof(this.hash[key]) === 'object')\n          ? this.hash[key][field] ?? null\n          : null;\n    }\n\n    public async hgetall(key: string) {\n        return this.hash[key] || {};\n    }\n\n    public hdel(key: string, field: any) {\n        const success = this.hash?.[key]?.[field] !== undefined;\n        if (success) {\n            delete this.hash[key][field];\n        }\n        return Promise.resolve(success);\n    }\n\n    public async hlen(key: string) {\n        return this.hash[key] && Object.keys(this.hash[key]).length || 0;\n    }\n\n    public async incr(key: string) {\n        if (!this.keys[key]) {\n            this.keys[key] = 0;\n        }\n        (this.keys[key] as number)++;\n        return Promise.resolve(this.keys[key] as number);\n    }\n\n    public async decr(key: string) {\n        if (!this.keys[key]) {\n            this.keys[key] = 0;\n        }\n        (this.keys[key] as number)--;\n        return Promise.resolve(this.keys[key] as number);\n    }\n\n    public llen(key: string) {\n      return Promise.resolve((this.data[key] && this.data[key].length) || 0);\n    }\n\n    public rpush(key: string, ...values: string[]): Promise<number> {\n      if (!this.data[key]) { this.data[key] = []; }\n\n      let lastLength: number = 0;\n\n      values.forEach(value => {\n        lastLength = this.data[key].push(value);\n      });\n\n      return Promise.resolve(lastLength);\n    }\n\n    public lpush(key: string, ...values: string[]): Promise<number> {\n      if (!this.data[key]) { this.data[key] = []; }\n\n      let lastLength: number = 0;\n\n      values.forEach(value => {\n        lastLength = this.data[key].unshift(value);\n      });\n\n      return Promise.resolve(lastLength);\n    }\n\n    public lpop(key: string): Promise<string> {\n      return Promise.resolve(Array.isArray(this.data[key])\n        ? this.data[key].shift()\n        : null);\n    }\n\n    public rpop(key: string): Promise<string | null> {\n      return Promise.resolve(this.data[key].pop());\n    }\n\n    public brpop(...args: [...keys: string[], timeoutInSeconds: number]): Promise<[string, string] | null> {\n      const keys = args.slice(0, -1) as string[];\n      const timeoutInSeconds = args[args.length - 1] as number;\n\n      const getFirstPopulated = (): [string, string] | null => {\n        const keyWithValue = keys.find(key => this.data[key] && this.data[key].length > 0);\n        if (keyWithValue) {\n          return [keyWithValue, this.data[keyWithValue].pop()];\n        } else {\n          return null;\n        }\n      }\n\n      const firstPopulated = getFirstPopulated();\n\n      if (firstPopulated) {\n        // return first populated key + item\n        return Promise.resolve(firstPopulated);\n\n      } else {\n        // 8 retries per second\n        const maxRetries = timeoutInSeconds * 8;\n\n        let tries = 0;\n        return new Promise((resolve) => {\n          const interval = setInterval(() => {\n            tries++;\n\n            const firstPopulated = getFirstPopulated();\n            if (firstPopulated) {\n              clearInterval(interval);\n              return resolve(firstPopulated);\n\n            } else if (tries >= maxRetries) {\n              clearInterval(interval);\n              return resolve(null);\n            }\n\n          }, (timeoutInSeconds * 1000) / maxRetries);\n        });\n      }\n    }\n\n    public setMaxListeners(number: number) {\n      this.subscriptions.setMaxListeners(number);\n    }\n\n    public shutdown() {\n      if (isDevMode) {\n        writeDevModeCache({\n          data: this.data,\n          hash: this.hash,\n          keys: this.keys\n        });\n      }\n    }\n\n}\n"],
  "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,oBAA6B;AAC7B,mBAA0B;AAG1B,qBAA+E;AAIxE,IAAM,gBAAN,MAAwC;AAAA,EAU3C,cAAc;AATd,SAAO,gBAAgB,IAAI,2BAAa;AAExC,SAAO,OAAuC,CAAC;AAC/C,SAAO,OAAsD,CAAC;AAE9D,SAAO,OAA0C,CAAC;AAElD,SAAQ,WAA6C,CAAC;AAMpD,QACE,gCACA,gCAAgB,GAChB;AACA,YAAM,YAAQ,gCAAgB;AAC9B,UAAI,MAAM,MAAM;AAAE,aAAK,OAAO,MAAM;AAAA,MAAM;AAC1C,UAAI,MAAM,MAAM;AAAE,aAAK,OAAO,MAAM;AAAA,MAAM;AAC1C,UAAI,MAAM,MAAM;AAAE,aAAK,OAAO,MAAM;AAAA,MAAM;AAAA,IAC5C;AAAA,EACF;AAAA,EAEO,UAAU,OAAe,UAAoC;AAChE,SAAK,cAAc,GAAG,OAAO,QAAQ;AACrC,WAAO,QAAQ,QAAQ,IAAI;AAAA,EAC/B;AAAA,EAEO,YAAY,OAAe,UAAqB;AACnD,QAAI,UAAW;AACX,WAAK,cAAc,eAAe,OAAO,QAAQ;AAAA,IAErD,OAAO;AACH,WAAK,cAAc,mBAAmB,KAAK;AAAA,IAC/C;AAEA,WAAO;AAAA,EACX;AAAA,EAEO,QAAQ,OAAe,MAAW;AACrC,SAAK,cAAc,KAAK,OAAO,IAAI;AACnC,WAAO;AAAA,EACX;AAAA,EAEA,MAAa,SAAU,SAAkB;AACvC,QAAI,aAAa,KAAK,cAAc,WAAW;AAC/C,QAAI,SAAS;AAKX,YAAM,SAAS,IAAI;AAAA,QACjB,QACE,WAAW,KAAK,KAAK,EACrB,WAAW,KAAK,KAAK,EACrB,WAAW,KAAK,IAAI,EACpB,WAAW,KAAK,GAAG;AAAA,QACrB;AAAA,MACF;AACA,mBAAa,WAAW,OAAO,CAAC,cAAc,OAAO,KAAK,SAAS,CAAC;AAAA,IACtE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,OAAO,KAA+B;AAC/C,WACE,KAAK,KAAK,GAAG,MAAM,UACnB,KAAK,KAAK,GAAG,MAAM,UACnB,KAAK,KAAK,GAAG,MAAM;AAAA,EAEzB;AAAA,EAEO,IAAI,KAAa,OAAe;AACnC,SAAK,KAAK,GAAG,IAAI;AAAA,EACrB;AAAA,EAEO,MAAM,KAAa,OAAe,SAAiB;AACtD,SAAK,KAAK,GAAG,IAAI;AACjB,SAAK,OAAO,KAAK,OAAO;AAAA,EAC5B;AAAA,EAEO,OAAO,KAAa,SAAiB;AAExC,QAAI,KAAK,SAAS,GAAG,GAAG;AACpB,mBAAa,KAAK,SAAS,GAAG,CAAC;AAAA,IACnC;AACA,SAAK,SAAS,GAAG,IAAI,WAAW,MAAM;AAClC,aAAO,KAAK,KAAK,GAAG;AACpB,aAAO,KAAK,SAAS,GAAG;AAAA,IAC5B,GAAG,UAAU,GAAI;AAAA,EACrB;AAAA,EAEO,IAAI,KAAa;AACpB,WAAO,KAAK,KAAK,GAAG;AAAA,EACxB;AAAA,EAEO,IAAI,KAAa;AACpB,WAAO,KAAK,KAAK,GAAG;AACpB,WAAO,KAAK,KAAK,GAAG;AACpB,WAAO,KAAK,KAAK,GAAG;AAAA,EACxB;AAAA,EAEO,KAAK,KAAa,OAAY;AACjC,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AACjB,WAAK,KAAK,GAAG,IAAI,CAAC;AAAA,IACtB;AAEA,QAAI,KAAK,KAAK,GAAG,EAAE,QAAQ,KAAK,MAAM,IAAI;AACtC,WAAK,KAAK,GAAG,EAAE,KAAK,KAAK;AAAA,IAC7B;AAAA,EACJ;AAAA,EAEA,MAAa,SAAS,KAAgC;AAClD,WAAO,KAAK,KAAK,GAAG,KAAK,CAAC;AAAA,EAC9B;AAAA,EAEA,MAAa,UAAU,KAAa,OAAe;AAC/C,WAAO,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK,GAAG,EAAE,SAAS,KAAK,IAAI,IAAI;AAAA,EAClE;AAAA,EAEO,KAAK,KAAa,OAAY;AACjC,QAAI,KAAK,KAAK,GAAG,GAAG;AAChB,kCAAU,KAAK,KAAK,GAAG,GAAG,KAAK,KAAK,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,IAC3D;AAAA,EACJ;AAAA,EAEO,MAAM,KAAa;AACtB,YAAQ,KAAK,KAAK,GAAG,KAAK,CAAC,GAAG;AAAA,EAClC;AAAA,EAEA,MAAa,UAAU,MAAgB;AACrC,UAAM,eAA0C,CAAC;AAEjD,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAI,GAAG,KAAK;AAC3C,OAAC,MAAM,KAAK,SAAS,KAAK,CAAC,CAAC,GAAG,QAAQ,CAAC,WAAW;AACjD,YAAI,CAAC,aAAa,MAAM,GAAG;AACzB,uBAAa,MAAM,IAAI;AAAA,QACzB;AAEA,qBAAa,MAAM;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,WAAO,OAAO,KAAK,YAAY,EAAE,OAAO,CAAC,MAAM,SAAS;AACtD,UAAI,aAAa,IAAI,IAAI,GAAG;AAC1B,aAAK,KAAK,IAAI;AAAA,MAChB;AACA,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACP;AAAA,EAEO,KAAK,KAAa,OAAe,OAAe;AACnD,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AAAE,WAAK,KAAK,GAAG,IAAI,CAAC;AAAA,IAAG;AAC5C,SAAK,KAAK,GAAG,EAAE,KAAK,IAAI;AACxB,WAAO,QAAQ,QAAQ,IAAI;AAAA,EAC/B;AAAA,EAEO,QAAQ,KAAa,OAAe,QAAgB;AACvD,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AAAE,WAAK,KAAK,GAAG,IAAI,CAAC;AAAA,IAAG;AAC5C,QAAI,QAAQ,OAAO,KAAK,KAAK,GAAG,EAAE,KAAK,KAAK,GAAG;AAC/C,aAAS;AACT,SAAK,KAAK,GAAG,EAAE,KAAK,IAAI,MAAM,SAAS;AACvC,WAAO,QAAQ,QAAQ,KAAK;AAAA,EAChC;AAAA,EAEO,UAAU,KAAa,OAAe,QAAgB,iBAAyB;AAClF,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AAAE,WAAK,KAAK,GAAG,IAAI,CAAC;AAAA,IAAG;AAC5C,QAAI,QAAQ,OAAO,KAAK,KAAK,GAAG,EAAE,KAAK,KAAK,GAAG;AAC/C,aAAS;AACT,SAAK,KAAK,GAAG,EAAE,KAAK,IAAI,MAAM,SAAS;AAMvC,QAAI,KAAK,SAAS,GAAG,GAAG;AACtB,mBAAa,KAAK,SAAS,GAAG,CAAC;AAAA,IACjC;AACA,SAAK,SAAS,GAAG,IAAI,WAAW,MAAM;AAClC,aAAO,KAAK,KAAK,GAAG;AACpB,aAAO,KAAK,SAAS,GAAG;AAAA,IAC5B,GAAG,kBAAkB,GAAI;AAEzB,WAAO,QAAQ,QAAQ,KAAK;AAAA,EAChC;AAAA,EAEA,MAAa,KAAK,KAAa,OAAe;AAC1C,WAAQ,OAAO,KAAK,KAAK,GAAG,MAAO,WAC/B,KAAK,KAAK,GAAG,EAAE,KAAK,KAAK,OACzB;AAAA,EACR;AAAA,EAEA,MAAa,QAAQ,KAAa;AAC9B,WAAO,KAAK,KAAK,GAAG,KAAK,CAAC;AAAA,EAC9B;AAAA,EAEO,KAAK,KAAa,OAAY;AACjC,UAAM,UAAU,KAAK,OAAO,GAAG,IAAI,KAAK,MAAM;AAC9C,QAAI,SAAS;AACT,aAAO,KAAK,KAAK,GAAG,EAAE,KAAK;AAAA,IAC/B;AACA,WAAO,QAAQ,QAAQ,OAAO;AAAA,EAClC;AAAA,EAEA,MAAa,KAAK,KAAa;AAC3B,WAAO,KAAK,KAAK,GAAG,KAAK,OAAO,KAAK,KAAK,KAAK,GAAG,CAAC,EAAE,UAAU;AAAA,EACnE;AAAA,EAEA,MAAa,KAAK,KAAa;AAC3B,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AACjB,WAAK,KAAK,GAAG,IAAI;AAAA,IACrB;AACA,IAAC,KAAK,KAAK,GAAG;AACd,WAAO,QAAQ,QAAQ,KAAK,KAAK,GAAG,CAAW;AAAA,EACnD;AAAA,EAEA,MAAa,KAAK,KAAa;AAC3B,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AACjB,WAAK,KAAK,GAAG,IAAI;AAAA,IACrB;AACA,IAAC,KAAK,KAAK,GAAG;AACd,WAAO,QAAQ,QAAQ,KAAK,KAAK,GAAG,CAAW;AAAA,EACnD;AAAA,EAEO,KAAK,KAAa;AACvB,WAAO,QAAQ,QAAS,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK,GAAG,EAAE,UAAW,CAAC;AAAA,EACvE;AAAA,EAEO,MAAM,QAAgB,QAAmC;AAC9D,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AAAE,WAAK,KAAK,GAAG,IAAI,CAAC;AAAA,IAAG;AAE5C,QAAI,aAAqB;AAEzB,WAAO,QAAQ,WAAS;AACtB,mBAAa,KAAK,KAAK,GAAG,EAAE,KAAK,KAAK;AAAA,IACxC,CAAC;AAED,WAAO,QAAQ,QAAQ,UAAU;AAAA,EACnC;AAAA,EAEO,MAAM,QAAgB,QAAmC;AAC9D,QAAI,CAAC,KAAK,KAAK,GAAG,GAAG;AAAE,WAAK,KAAK,GAAG,IAAI,CAAC;AAAA,IAAG;AAE5C,QAAI,aAAqB;AAEzB,WAAO,QAAQ,WAAS;AACtB,mBAAa,KAAK,KAAK,GAAG,EAAE,QAAQ,KAAK;AAAA,IAC3C,CAAC;AAED,WAAO,QAAQ,QAAQ,UAAU;AAAA,EACnC;AAAA,EAEO,KAAK,KAA8B;AACxC,WAAO,QAAQ,QAAQ,MAAM,QAAQ,KAAK,KAAK,GAAG,CAAC,IAC/C,KAAK,KAAK,GAAG,EAAE,MAAM,IACrB,IAAI;AAAA,EACV;AAAA,EAEO,KAAK,KAAqC;AAC/C,WAAO,QAAQ,QAAQ,KAAK,KAAK,GAAG,EAAE,IAAI,CAAC;AAAA,EAC7C;AAAA,EAEO,SAAS,MAAuF;AACrG,UAAM,OAAO,KAAK,MAAM,GAAG,EAAE;AAC7B,UAAM,mBAAmB,KAAK,KAAK,SAAS,CAAC;AAE7C,UAAM,oBAAoB,MAA+B;AACvD,YAAM,eAAe,KAAK,KAAK,SAAO,KAAK,KAAK,GAAG,KAAK,KAAK,KAAK,GAAG,EAAE,SAAS,CAAC;AACjF,UAAI,cAAc;AAChB,eAAO,CAAC,cAAc,KAAK,KAAK,YAAY,EAAE,IAAI,CAAC;AAAA,MACrD,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,iBAAiB,kBAAkB;AAEzC,QAAI,gBAAgB;AAElB,aAAO,QAAQ,QAAQ,cAAc;AAAA,IAEvC,OAAO;AAEL,YAAM,aAAa,mBAAmB;AAEtC,UAAI,QAAQ;AACZ,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,cAAM,WAAW,YAAY,MAAM;AACjC;AAEA,gBAAMA,kBAAiB,kBAAkB;AACzC,cAAIA,iBAAgB;AAClB,0BAAc,QAAQ;AACtB,mBAAO,QAAQA,eAAc;AAAA,UAE/B,WAAW,SAAS,YAAY;AAC9B,0BAAc,QAAQ;AACtB,mBAAO,QAAQ,IAAI;AAAA,UACrB;AAAA,QAEF,GAAI,mBAAmB,MAAQ,UAAU;AAAA,MAC3C,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEO,gBAAgB,QAAgB;AACrC,SAAK,cAAc,gBAAgB,MAAM;AAAA,EAC3C;AAAA,EAEO,WAAW;AAChB,QAAI,0BAAW;AACb,4CAAkB;AAAA,QAChB,MAAM,KAAK;AAAA,QACX,MAAM,KAAK;AAAA,QACX,MAAM,KAAK;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF;AAEJ;",
  "names": ["firstPopulated"]
}
