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

Austinb / GameQ / 25150110030

30 Apr 2026 06:04AM UTC coverage: 92.89% (-0.2%) from 93.121%
25150110030

Pull #769

travis-ci

web-flow
Merge 28436ecdf into 347cf58c5
Pull Request #769: Bump actions/checkout from 4 to 6

2587 of 2785 relevant lines covered (92.89%)

248.44 hits per line

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

58.73
/src/GameQ/Protocols/Eos.php
1
<?php
2

3
/**
4
 * This file is part of GameQ.
5
 *
6
 * GameQ is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GameQ is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19

20
namespace GameQ\Protocols;
21

22
use GameQ\Exception\Protocol as Exception;
23
use GameQ\Server;
24

25
/**
26
 * Epic Online Services Protocol Class
27
 *
28
 * Serves as a base class for EOS-powered games.
29
 *
30
 * @package GameQ\Protocols
31
 * @author  H.Rouatbi
32
 */
33
class Eos extends Http
34
{
35
    /**
36
     * The protocol being used
37
     *
38
     * @var string
39
     */
40
    protected $protocol = 'eos';
41

42
    /**
43
     * Longer string name of this protocol class
44
     *
45
     * @var string
46
     */
47
    protected $name_long = 'Epic Online Services';
48

49
    /**
50
     * String name of this protocol class
51
     *
52
     * @var string
53
     */
54
    protected $name = 'eos';
55

56
    /**
57
     * Grant type used for authentication
58
     *
59
     * @var string
60
     */
61
    protected $grant_type = 'client_credentials';
62

63
    /**
64
     * Deployment ID for the game or application
65
     *
66
     * @var string
67
     */
68
    protected $deployment_id = null;
69

70
    /**
71
     * User ID for authentication
72
     *
73
     * @var string
74
     */
75
    protected $user_id = null;
76

77
    /**
78
     * User secret key for authentication
79
     *
80
     * @var string
81
     */
82
    protected $user_secret = null;
83

84
    /**
85
     * Holds the server ip so we can overwrite it back
86
     *
87
     * @var string
88
     */
89
    protected $serverIp = null;
90

91
    /**
92
     * Holds the server port query so we can overwrite it back
93
     *
94
     * @var string
95
     */
96
    protected $serverPortQuery = null;
97

98
    /**
99
     * Normalize some items
100
     *
101
     * @var array
102
     */
103
    protected $normalize = [
104
        // General
105
        'general' => [
106
            // target       => source
107
            'hostname'   => 'hostname',
108
            'mapname'    => 'mapname',
109
            'maxplayers' => 'maxplayers',
110
            'numplayers' => 'numplayers',
111
            'password'   => 'password',
112
        ]
113
    ];
114

115
    /**
116
     * Process the response from the EOS API
117
     *
118
     * @return array
119
     * @throws Exception
120
     */
121
    public function processResponse()
44✔
122
    {
123
        $index = ($this->grant_type === 'external_auth') ? 2 : 1;
44✔
124
        $server_data = isset($this->packets_response[$index]) ? json_decode($this->packets_response[$index], true) : null;
44✔
125

126
        $server_data = isset($server_data['sessions']) ? $server_data['sessions'] : null;
44✔
127

128
        // If no server data, throw an exception
129
        if (empty($server_data)) {
44✔
130
            throw new Exception('No server data found. Server might be offline.');
11✔
131
        }
132

133
        return $server_data;
33✔
134
    }
135

136
    /**
137
     * Called before sending the request
138
     *
139
     * @param Server $server
140
     */
141
    public function beforeSend(Server $server)
44✔
142
    {
143
        $this->serverIp = $server->ip();
44✔
144
        $this->serverPortQuery = $server->portQuery();
44✔
145

146
        // Authenticate and get the access token
147
        $auth_token = $this->authenticate();
44✔
148

149
        if (!$auth_token) {
44✔
150
            return;
44✔
151
        }
152

153
        // Query for server data
154
        $this->queryServers($auth_token);
×
155
    }
156

157
    /**
158
     * Authenticate to get the access token
159
     *
160
     * @return string|null
161
     */
162
    protected function authenticate()
44✔
163
    {
164
        $auth_url = "https://api.epicgames.dev/auth/v1/oauth/token";
44✔
165
        $auth_headers = [
28✔
166
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
44✔
167
            'Accept-Encoding: deflate, gzip',
44✔
168
            'Content-Type: application/x-www-form-urlencoded',
44✔
169
        ];
32✔
170

171
        $auth_postfields = "grant_type={$this->grant_type}&deployment_id={$this->deployment_id}";
44✔
172

173
        if ($this->grant_type === 'external_auth') {
44✔
174
            // Perform device authentication if necessary
175
            $device_auth = $this->deviceAuthentication();
×
176
            if (!$device_auth) {
×
177
                return null;
×
178
            }
179
            $auth_postfields .= "&external_auth_type=deviceid_access_token"
180
                            . "&external_auth_token={$device_auth['access_token']}"
×
181
                            . "&nonce=ABCHFA3qgUCJ1XTPAoGDEF&display_name=User";
×
182
        }
183

184
        // Make the request to get the access token
185
        $response = $this->httpRequest($auth_url, $auth_headers, $auth_postfields);
44✔
186

187
        return isset($response['access_token']) ? $response['access_token'] : null;
44✔
188
    }
189

190
    /**
191
     * Query the EOS server for matchmaking data
192
     *
193
     * @param string $auth_token
194
     * @return array|null
195
     */
196
    protected function queryServers($auth_token)
×
197
    {
198
        $server_query_url = "https://api.epicgames.dev/matchmaking/v1/{$this->deployment_id}/filter";
×
199
        $query_headers = [
200
            "Authorization: Bearer {$auth_token}",
×
201
            'Accept: application/json',
×
202
            'Content-Type: application/json',
×
203
        ];
204

205
        $query_body = json_encode([
×
206
            'criteria' => [
207
                [
208
                    'key' => 'attributes.ADDRESS_s',
×
209
                    'op' => 'EQUAL',
×
210
                    'value' => $this->serverIp,
×
211
                ],
212
            ],
213
            'maxResults' => 200,
×
214
        ]);
215

216
        $response = $this->httpRequest($server_query_url, $query_headers, $query_body);
×
217

218
        return isset($response['sessions']) ? $response['sessions'] : null;
×
219
    }
220

221
    /**
222
     * Handle device authentication for external auth type
223
     *
224
     * @return array|null
225
     */
226
    protected function deviceAuthentication()
×
227
    {
228
        $device_auth_url = "https://api.epicgames.dev/auth/v1/accounts/deviceid";
×
229
        $device_auth_headers = [
230
            'Authorization: Basic ' . base64_encode("{$this->user_id}:{$this->user_secret}"),
×
231
            'Accept-Encoding: deflate, gzip',
×
232
            'Content-Type: application/x-www-form-urlencoded',
×
233
        ];
234

235
        $device_auth_postfields = "deviceModel=PC";
×
236

237
        return $this->httpRequest($device_auth_url, $device_auth_headers, $device_auth_postfields);
×
238
    }
239

240
    /**
241
     * Execute an HTTP request
242
     *
243
     * @param string $url
244
     * @param array $headers
245
     * @param string $postfields
246
     * @return array|null
247
     */
248
    protected function httpRequest($url, $headers, $postfields)
44✔
249
    {
250
        $ch = curl_init();
44✔
251

252
        curl_setopt($ch, CURLOPT_URL, $url);
44✔
253
        curl_setopt($ch, CURLOPT_POST, 1);
44✔
254
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
44✔
255
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
44✔
256
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields);
44✔
257

258
        $response = curl_exec($ch);
44✔
259

260
        if (!$response) {
44✔
261
            return null;
×
262
        }
263

264
        $this->packets_response[] = $response;
44✔
265

266
        return json_decode($response, true);
44✔
267
    }
268

269
    /**
270
     * Safely retrieves an attribute from an array or returns a default value.
271
     *
272
     * @param array $attributes
273
     * @param string $key
274
     * @param mixed $default
275
     * @return mixed
276
     */
277
    protected function getAttribute($attributes, $key, $default = null)
33✔
278
    {
279
        return isset($attributes[$key]) ? $attributes[$key] : $default;
33✔
280
    }
281
}
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