]> jfr.im git - irc/unrealircd/unrealircd-rpc-php.git/blob - lib/Connection.php
Return from $rpc->eventloop() after 2 seconds instead of 10.
[irc/unrealircd/unrealircd-rpc-php.git] / lib / Connection.php
1 <?php
2
3 namespace UnrealIRCd;
4
5 use Exception;
6 use WebSocket;
7
8 class Connection
9 {
10 protected WebSocket\Client $connection;
11
12 public $errno = 0;
13 public $error = NULL;
14
15 public function __construct(string $uri, string $api_login, array $options = null)
16 {
17 $context = $options["context"] ?? stream_context_create();
18
19 if (isset($options["tls_verify"]) && !$options["tls_verify"]) {
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
32 /* Start the connection now */
33 if (isset($options["issuer"]))
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 */
39 $this->connection->ping();
40 }
41 }
42
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
49 * @param array|null $params
50 * @param bool $no_wait
51 *
52 * @return object|array|bool
53 * @throws Exception
54 */
55 public function query(string $method, array|null $params = null, $no_wait = false): object|array|bool
56 {
57 $id = random_int(1, 99999);
58
59 $rpc = [
60 "jsonrpc" => "2.0",
61 "method" => $method,
62 "params" => $params,
63 "id" => $id
64 ];
65
66 $json_rpc = json_encode($rpc);
67 $this->connection->text($json_rpc);
68
69 if ($no_wait)
70 return true;
71
72 $starttime = time();
73 do {
74 $reply = $this->connection->receive();
75
76 $reply = json_decode($reply);
77
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"
84 */
85 continue;
86 }
87
88 if (property_exists($reply, 'result')) {
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 }
99 if (time() - $starttime > 10)
100 throw new Exception('RPC request timed out');
101 } while(1); // wait for the reply to OUR request
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 {
118 $this->connection->setTimeout(2); /* lower timeout for socket loop */
119 $starttime = microtime(true);
120 try {
121 $reply = $this->connection->receive();
122 } catch (WebSocket\TimeoutException $e) {
123 if (microtime(true) - $starttime < 1)
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 }
138 return NULL;
139 }
140
141 $this->connection->setTimeout(10); /* set timeout back again */
142
143 $reply = json_decode($reply);
144
145 if (property_exists($reply, 'result')) {
146 $this->errno = 0;
147 $this->error = NULL;
148 return $reply->result;
149 }
150
151 /* This would be weird */
152 if (property_exists($reply, 'error')) {
153 $this->errno = $reply->error->code;
154 $this->error = $reply->error->message;
155 return false;
156 }
157
158 /* This should never happen */
159 throw new Exception('Invalid JSON-RPC data from UnrealIRCd: not an error and not a result.');
160 }
161
162 public function rpc(): Rpc
163 {
164 return new Rpc($this);
165 }
166
167 public function stats(): Stats
168 {
169 return new Stats($this);
170 }
171
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
182 public function serverban(): ServerBan
183 {
184 return new ServerBan($this);
185 }
186
187 public function spamfilter(): Spamfilter
188 {
189 return new Spamfilter($this);
190 }
191
192 public function nameban(): NameBan
193 {
194 return new NameBan($this);
195 }
196
197 public function server(): Server
198 {
199 return new Server($this);
200 }
201
202 public function serverbanexception(): ServerBanException
203 {
204 return new ServerBanException($this);
205 }
206
207 public function log(): Log
208 {
209 return new Log($this);
210 }
211 }