]> jfr.im git - irc/unrealircd/unrealircd-rpc-php.git/blame - lib/Connection.php
Return from $rpc->eventloop() after 2 seconds instead of 10.
[irc/unrealircd/unrealircd-rpc-php.git] / lib / Connection.php
CommitLineData
562c96c0
BM
1<?php
2
50d6d455 3namespace UnrealIRCd;
562c96c0 4
8d5a112f 5use Exception;
562c96c0
BM
6use WebSocket;
7
8class Connection
9{
50d6d455
D
10 protected WebSocket\Client $connection;
11
1a3fda20
BM
12 public $errno = 0;
13 public $error = NULL;
14
a18591f9 15 public function __construct(string $uri, string $api_login, array $options = null)
50d6d455
D
16 {
17 $context = $options["context"] ?? stream_context_create();
18
8d5a112f 19 if (isset($options["tls_verify"]) && !$options["tls_verify"]) {
50d6d455
D
20 stream_context_set_option($context, 'ssl', 'verify_peer', false);
21 stream_context_set_option($context, 'ssl', 'verify_peer_name', false);
22 }
23
24 $this->connection = new WebSocket\Client($uri, [
25 'context' => $context,
26 'headers' => [
27 'Authorization' => sprintf('Basic %s', base64_encode($api_login)),
28 ],
29 'timeout' => 10,
30 ]);
31
57c1d592 32 /* Start the connection now */
02da13a9 33 if (isset($options["issuer"]))
fbdccdf5
BM
34 {
35 /* Set issuer and don't wait for the reply (async) */
36 $this->query('rpc.set_issuer', ['name' => $options["issuer"]], true);
37 } else {
38 /* Ping-pong */
02da13a9 39 $this->connection->ping();
fbdccdf5 40 }
50d6d455
D
41 }
42
8d5a112f
D
43 /**
44 * Encode and send a query to the RPC server.
45 *
46 * @note I'm not sure on the response type except that it may be either an object or array.
47 *
48 * @param string $method
a18591f9 49 * @param array|null $params
fbdccdf5 50 * @param bool $no_wait
a18591f9 51 *
8d5a112f
D
52 * @return object|array|bool
53 * @throws Exception
54 */
fbdccdf5 55 public function query(string $method, array|null $params = null, $no_wait = false): object|array|bool
50d6d455 56 {
8d5a112f
D
57 $id = random_int(1, 99999);
58
50d6d455
D
59 $rpc = [
60 "jsonrpc" => "2.0",
61 "method" => $method,
62 "params" => $params,
8d5a112f 63 "id" => $id
50d6d455
D
64 ];
65
66 $json_rpc = json_encode($rpc);
50d6d455 67 $this->connection->text($json_rpc);
a3518a0a 68
fbdccdf5
BM
69 if ($no_wait)
70 return true;
71
21768a11 72 $starttime = time();
a3518a0a
BM
73 do {
74 $reply = $this->connection->receive();
75
76 $reply = json_decode($reply);
77
21667b50
BM
78 if (property_exists($reply, 'id') && ($id !== $reply->id))
79 {
80 /* This is not our request. Perhaps we are streaming log events
81 * or this is an asynchronous response to like set_issuer.
82 * We don't care about that, continue.
83 * NOTE: This does mean that this event info is "lost"
a3518a0a 84 */
21667b50
BM
85 continue;
86 }
87
88 if (property_exists($reply, 'result')) {
a3518a0a
BM
89 $this->errno = 0;
90 $this->error = NULL;
91 return $reply->result;
92 }
93
94 if (property_exists($reply, 'error')) {
95 $this->errno = $reply->error->code;
96 $this->error = $reply->error->message;
97 return false;
98 }
21768a11
BM
99 if (time() - $starttime > 10)
100 throw new Exception('RPC request timed out');
fbdccdf5 101 } while(1); // wait for the reply to OUR request
a3518a0a
BM
102
103 /* This should never happen */
104 throw new Exception('Invalid JSON-RPC response from UnrealIRCd: not an error and not a result.');
105 }
106
107 /**
108 * Grab and/or wait for next event. Used for log streaming.
109 * @note This function will return NULL after a 10 second timeout,
110 * this so the function is not entirely blocking. You can safely
111 * retry the operation if the return value === NULL.
112 *
113 * @return object|array|bool|null
114 * @throws Exception
115 */
116 public function eventloop(): object|array|bool|null
117 {
94e1459d 118 $this->connection->setTimeout(2); /* lower timeout for socket loop */
49faf2cf 119 $starttime = microtime(true);
a3518a0a
BM
120 try {
121 $reply = $this->connection->receive();
49faf2cf 122 } catch (WebSocket\TimeoutException $e) {
101a298c 123 if (microtime(true) - $starttime < 1)
49faf2cf
BM
124 {
125 /* There's some bug in the library: if we
126 * caught the timeout exception once (so
127 * harmless) and then later the server gets
128 * killed or closes the connection otherwise,
129 * then it will again throw WebSocket\TimeoutException
130 * even though it has nothing to do with timeouts.
131 * We detect this by checking if the timeout
132 * took less than 1 second, then we know for sure
133 * that it wasn't really a timeout (since the
134 * timeout is normally 10 seconds).
135 */
136 throw $e;
137 }
a3518a0a
BM
138 return NULL;
139 }
50d6d455 140
94e1459d
BM
141 $this->connection->setTimeout(10); /* set timeout back again */
142
50d6d455
D
143 $reply = json_decode($reply);
144
65cc0295 145 if (property_exists($reply, 'result')) {
1a3fda20
BM
146 $this->errno = 0;
147 $this->error = NULL;
657dad63 148 return $reply->result;
50d6d455 149 }
bea99f3a 150
a3518a0a 151 /* This would be weird */
1a3fda20
BM
152 if (property_exists($reply, 'error')) {
153 $this->errno = $reply->error->code;
154 $this->error = $reply->error->message;
155 return false;
bea99f3a
D
156 }
157
1a3fda20 158 /* This should never happen */
a3518a0a 159 throw new Exception('Invalid JSON-RPC data from UnrealIRCd: not an error and not a result.');
50d6d455 160 }
7c7017a2 161
c6e7dd34
BM
162 public function rpc(): Rpc
163 {
164 return new Rpc($this);
165 }
166
1ef2e09f
BM
167 public function stats(): Stats
168 {
169 return new Stats($this);
170 }
171
7c7017a2
BM
172 public function user(): User
173 {
174 return new User($this);
175 }
176
177 public function channel(): Channel
178 {
179 return new Channel($this);
180 }
181
e4de690d 182 public function serverban(): ServerBan
7c7017a2 183 {
e4de690d 184 return new ServerBan($this);
7c7017a2
BM
185 }
186
187 public function spamfilter(): Spamfilter
188 {
189 return new Spamfilter($this);
190 }
1ef2e09f 191
8fd8402e
VP
192 public function nameban(): NameBan
193 {
194 return new NameBan($this);
195 }
1ef2e09f 196
1d1d6c90
VP
197 public function server(): Server
198 {
199 return new Server($this);
200 }
1ef2e09f 201
65b6ddcb 202 public function serverbanexception(): ServerBanException
9d9157c2 203 {
9ff6e559 204 return new ServerBanException($this);
9d9157c2 205 }
1ef2e09f 206
a3518a0a
BM
207 public function log(): Log
208 {
209 return new Log($this);
210 }
562c96c0 211}