• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

decentraland / events / 21645698326

03 Feb 2026 08:01PM UTC coverage: 61.281% (+7.1%) from 54.156%
21645698326

Pull #888

github

web-flow
Merge ebb26507e into b610bfa4c
Pull Request #888: feat: add connected users feature to event queries

83 of 97 branches covered (85.57%)

Branch coverage included in aggregate %.

161 of 270 new or added lines in 4 files covered. (59.63%)

1677 of 2775 relevant lines covered (60.43%)

1.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.51
/src/api/CommsGatekeeper.ts
1
import API from "decentraland-gatsby/dist/utils/api/API"
1✔
2
import Options from "decentraland-gatsby/dist/utils/api/Options"
1✔
3
import Time from "decentraland-gatsby/dist/utils/date/Time"
1✔
4
import env from "decentraland-gatsby/dist/utils/env"
1✔
5

1✔
6
export type SceneParticipantsResponse = {
1✔
7
  ok: boolean
1✔
8
  data: {
1✔
9
    addresses: string[]
1✔
10
  }
1✔
11
}
1✔
12

1✔
13
type CachedParticipants = {
1✔
14
  addresses: string[]
1✔
15
  expiresAt: number
1✔
16
}
1✔
17

1✔
18
export default class CommsGatekeeper extends API {
1✔
19
  static Url = env(
1✔
20
    `COMMS_GATEKEEPER_URL`,
1✔
21
    "https://comms-gatekeeper.decentraland.zone"
1✔
22
  )
1✔
23

1✔
24
  static Cache = new Map<string, CommsGatekeeper>()
1✔
25

1✔
26
  // Cache for participant addresses with 5-minute TTL
1✔
27
  private static participantsCache = new Map<string, CachedParticipants>()
1✔
28
  private static readonly CACHE_TTL_MS = Time.Minute * 5 // 5 minutes
1✔
29

1✔
30
  static from(url: string) {
1✔
NEW
31
    if (!this.Cache.has(url)) {
×
NEW
32
      this.Cache.set(url, new CommsGatekeeper(url))
×
NEW
33
    }
×
NEW
34
    return this.Cache.get(url)!
×
NEW
35
  }
×
36

1✔
37
  static get() {
1✔
NEW
38
    return this.from(env("COMMS_GATEKEEPER_URL", this.Url))
×
NEW
39
  }
×
40

1✔
41
  /**
1✔
42
   * Get the list of participant wallet addresses in a scene room.
1✔
43
   * @param pointer - Scene base parcel (e.g., "-7,-2"). The scene ID is resolved from the catalyst.
1✔
44
   * @param realmName - Realm name (default: "main")
1✔
45
   * @returns List of wallet addresses connected to the room
1✔
46
   */
1✔
47
  async getSceneParticipants(
1✔
NEW
48
    pointer: string,
×
NEW
49
    realmName = "main"
×
NEW
50
  ): Promise<string[]> {
×
NEW
51
    const cacheKey = `scene:${pointer}:${realmName}`
×
NEW
52
    const cached = CommsGatekeeper.participantsCache.get(cacheKey)
×
NEW
53

×
NEW
54
    // Return cached value if it exists and hasn't expired
×
NEW
55
    if (cached && cached.expiresAt > Date.now()) {
×
NEW
56
      return cached.addresses
×
NEW
57
    }
×
NEW
58

×
NEW
59
    const { signal, abort } = new AbortController()
×
NEW
60
    const fetchOptions = new Options({ signal })
×
NEW
61

×
NEW
62
    const timeoutId = setTimeout(() => {
×
NEW
63
      abort()
×
NEW
64
    }, Time.Second * 10)
×
NEW
65

×
NEW
66
    try {
×
NEW
67
      const params = new URLSearchParams({
×
NEW
68
        pointer,
×
NEW
69
        realm_name: realmName,
×
NEW
70
      })
×
NEW
71
      const response = await this.fetch<SceneParticipantsResponse>(
×
NEW
72
        `/scene-participants?${params}`,
×
NEW
73
        fetchOptions
×
NEW
74
      )
×
NEW
75
      const addresses = response.data.addresses
×
NEW
76

×
NEW
77
      // Cache the result with expiration
×
NEW
78
      CommsGatekeeper.participantsCache.set(cacheKey, {
×
NEW
79
        addresses,
×
NEW
80
        expiresAt: Date.now() + CommsGatekeeper.CACHE_TTL_MS,
×
NEW
81
      })
×
NEW
82

×
NEW
83
      return addresses
×
NEW
84
    } catch (error) {
×
NEW
85
      console.error(
×
NEW
86
        `Error fetching scene participants for pointer ${pointer}:`,
×
NEW
87
        error
×
NEW
88
      )
×
NEW
89
      return []
×
NEW
90
    } finally {
×
NEW
91
      clearTimeout(timeoutId)
×
NEW
92
    }
×
NEW
93
  }
×
94

1✔
95
  /**
1✔
96
   * Get the list of participant wallet addresses in a world room.
1✔
97
   * Uses realm_name parameter which is treated as a world name when no pointer is provided.
1✔
98
   * @param worldName - World name (e.g., "mycoolworld.dcl.eth")
1✔
99
   * @returns List of wallet addresses connected to the room
1✔
100
   */
1✔
101
  async getWorldParticipants(worldName: string): Promise<string[]> {
1✔
NEW
102
    const cacheKey = `world:${worldName}`
×
NEW
103
    const cached = CommsGatekeeper.participantsCache.get(cacheKey)
×
NEW
104

×
NEW
105
    // Return cached value if it exists and hasn't expired
×
NEW
106
    if (cached && cached.expiresAt > Date.now()) {
×
NEW
107
      return cached.addresses
×
NEW
108
    }
×
NEW
109

×
NEW
110
    const { signal, abort } = new AbortController()
×
NEW
111
    const fetchOptions = new Options({ signal })
×
NEW
112

×
NEW
113
    const timeoutId = setTimeout(() => {
×
NEW
114
      abort()
×
NEW
115
    }, Time.Second * 10)
×
NEW
116

×
NEW
117
    try {
×
NEW
118
      const params = new URLSearchParams({ realm_name: worldName })
×
NEW
119
      const response = await this.fetch<SceneParticipantsResponse>(
×
NEW
120
        `/scene-participants?${params}`,
×
NEW
121
        fetchOptions
×
NEW
122
      )
×
NEW
123
      const addresses = response.data.addresses
×
NEW
124

×
NEW
125
      // Cache the result with expiration
×
NEW
126
      CommsGatekeeper.participantsCache.set(cacheKey, {
×
NEW
127
        addresses,
×
NEW
128
        expiresAt: Date.now() + CommsGatekeeper.CACHE_TTL_MS,
×
NEW
129
      })
×
NEW
130

×
NEW
131
      return addresses
×
NEW
132
    } catch (error) {
×
NEW
133
      console.error(
×
NEW
134
        `Error fetching world participants for ${worldName}:`,
×
NEW
135
        error
×
NEW
136
      )
×
NEW
137
      return []
×
NEW
138
    } finally {
×
NEW
139
      clearTimeout(timeoutId)
×
NEW
140
    }
×
NEW
141
  }
×
142
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc