@artilingo/artiframe-cli 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/artiframe.js +1 -1
- package/core-stubs/docs/de.html +99 -6
- package/core-stubs/docs/en.html +100 -7
- package/core-stubs/docs/es.html +99 -6
- package/core-stubs/docs/fr.html +100 -7
- package/core-stubs/docs/tr.html +95 -2
- package/package.json +2 -1
- package/src/App.php +11 -0
- package/src/Commands/AddCommand.php +16 -2
- package/src/Commands/ModeCommand.php +71 -0
- package/src/Lang/de.php +7 -0
- package/src/Lang/en.php +7 -0
- package/src/Lang/es.php +7 -0
- package/src/Lang/fr.php +7 -0
- package/src/Lang/tr.php +7 -0
- package/stubs/make/api-standart.stub +20 -0
- package/stubs/make/api-switch-case.stub +38 -0
- package/stubs/make/class.stub +25 -0
- package/stubs/make/view.stub +30 -0
- package/stubs/service/image.stub +334 -0
- package/stubs/service/iyzico.stub +637 -0
- package/stubs/service/jwt.stub +312 -0
- package/stubs/service/phpmailer.stub +143 -0
- package/stubs/service/pusher.stub +232 -0
- package/stubs/service/qrcode.stub +169 -0
- package/stubs/service/redis.stub +361 -0
- package/stubs/service/s3.stub +377 -0
- package/stubs/service/sentry.stub +139 -0
- package/stubs/service/stripe.stub +526 -0
- package/stubs/service/twilio.stub +361 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Auth;
|
|
4
|
+
|
|
5
|
+
use Firebase\JWT\JWT;
|
|
6
|
+
use Firebase\JWT\Key;
|
|
7
|
+
use Firebase\JWT\ExpiredException;
|
|
8
|
+
use Firebase\JWT\SignatureInvalidException;
|
|
9
|
+
use Firebase\JWT\BeforeValidException;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* JWT Authentication Service
|
|
13
|
+
*
|
|
14
|
+
* Provides a clean wrapper around firebase/php-jwt for token generation,
|
|
15
|
+
* verification, refresh, and HTTP middleware integration.
|
|
16
|
+
*
|
|
17
|
+
* Required environment variables:
|
|
18
|
+
* JWT_SECRET_KEY — Secret key used for signing and verifying tokens
|
|
19
|
+
* JWT_ALGORITHM — Signing algorithm (e.g. HS256, HS384, HS512)
|
|
20
|
+
* JWT_EXPIRY — Default token lifetime in seconds (e.g. 3600)
|
|
21
|
+
*
|
|
22
|
+
* @package Auth
|
|
23
|
+
*/
|
|
24
|
+
class JwtAuth
|
|
25
|
+
{
|
|
26
|
+
/**
|
|
27
|
+
* Secret key for signing tokens.
|
|
28
|
+
*
|
|
29
|
+
* @var string
|
|
30
|
+
*/
|
|
31
|
+
private string $secretKey;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Signing algorithm (HS256, HS384, HS512, etc.).
|
|
35
|
+
*
|
|
36
|
+
* @var string
|
|
37
|
+
*/
|
|
38
|
+
private string $algorithm;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default token expiry in seconds.
|
|
42
|
+
*
|
|
43
|
+
* @var int
|
|
44
|
+
*/
|
|
45
|
+
private int $expiry;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a new JwtAuth instance.
|
|
49
|
+
*
|
|
50
|
+
* Loads the secret key, algorithm, and default expiry from $_ENV.
|
|
51
|
+
*/
|
|
52
|
+
public function __construct()
|
|
53
|
+
{
|
|
54
|
+
$this->secretKey = $_ENV['JWT_SECRET_KEY'];
|
|
55
|
+
$this->algorithm = $_ENV['JWT_ALGORITHM'] ?? 'HS256';
|
|
56
|
+
$this->expiry = (int) ($_ENV['JWT_EXPIRY'] ?? 3600);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate a signed JWT token.
|
|
61
|
+
*
|
|
62
|
+
* Merges the given payload with standard claims (iat, exp) and encodes it.
|
|
63
|
+
*
|
|
64
|
+
* @param array $payload Custom claims to include in the token
|
|
65
|
+
* @param int|null $expiry Token lifetime in seconds (null = use default from $_ENV)
|
|
66
|
+
* @return string Encoded JWT token string
|
|
67
|
+
*/
|
|
68
|
+
public function generate(array $payload, int $expiry = null): string
|
|
69
|
+
{
|
|
70
|
+
$now = time();
|
|
71
|
+
$expiresIn = $expiry ?? $this->expiry;
|
|
72
|
+
|
|
73
|
+
$tokenPayload = array_merge($payload, [
|
|
74
|
+
'iat' => $now,
|
|
75
|
+
'exp' => $now + $expiresIn,
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
return JWT::encode($tokenPayload, $this->secretKey, $this->algorithm);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Verify and decode a JWT token.
|
|
83
|
+
*
|
|
84
|
+
* Returns a standardised response array with the decoded payload on success
|
|
85
|
+
* or an error message on failure.
|
|
86
|
+
*
|
|
87
|
+
* @param string $token The JWT token to verify
|
|
88
|
+
* @return array {
|
|
89
|
+
* @type string $status 'success' or 'error'
|
|
90
|
+
* @type string $message Human-readable result description
|
|
91
|
+
* @type array $data Decoded payload (on success only)
|
|
92
|
+
* }
|
|
93
|
+
*/
|
|
94
|
+
public function verify(string $token): array
|
|
95
|
+
{
|
|
96
|
+
try {
|
|
97
|
+
$decoded = JWT::decode($token, new Key($this->secretKey, $this->algorithm));
|
|
98
|
+
$payload = (array) $decoded;
|
|
99
|
+
|
|
100
|
+
return [
|
|
101
|
+
'status' => 'success',
|
|
102
|
+
'message' => 'Token is valid.',
|
|
103
|
+
'data' => $payload,
|
|
104
|
+
];
|
|
105
|
+
} catch (ExpiredException $e) {
|
|
106
|
+
return [
|
|
107
|
+
'status' => 'error',
|
|
108
|
+
'message' => 'Token has expired.',
|
|
109
|
+
'data' => [],
|
|
110
|
+
];
|
|
111
|
+
} catch (SignatureInvalidException $e) {
|
|
112
|
+
return [
|
|
113
|
+
'status' => 'error',
|
|
114
|
+
'message' => 'Invalid token signature.',
|
|
115
|
+
'data' => [],
|
|
116
|
+
];
|
|
117
|
+
} catch (BeforeValidException $e) {
|
|
118
|
+
return [
|
|
119
|
+
'status' => 'error',
|
|
120
|
+
'message' => 'Token is not yet valid.',
|
|
121
|
+
'data' => [],
|
|
122
|
+
];
|
|
123
|
+
} catch (\Exception $e) {
|
|
124
|
+
return [
|
|
125
|
+
'status' => 'error',
|
|
126
|
+
'message' => 'Token verification failed: ' . $e->getMessage(),
|
|
127
|
+
'data' => [],
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extract the payload from a token without throwing exceptions.
|
|
134
|
+
*
|
|
135
|
+
* Useful when you need to inspect claims even if the token might be invalid.
|
|
136
|
+
* Returns null on any failure instead of throwing.
|
|
137
|
+
*
|
|
138
|
+
* @param string $token The JWT token to decode
|
|
139
|
+
* @return array|null Decoded payload or null on failure
|
|
140
|
+
*/
|
|
141
|
+
public function payload(string $token): ?array
|
|
142
|
+
{
|
|
143
|
+
try {
|
|
144
|
+
$decoded = JWT::decode($token, new Key($this->secretKey, $this->algorithm));
|
|
145
|
+
return (array) $decoded;
|
|
146
|
+
} catch (\Exception $e) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Refresh a token by generating a new one with the same payload.
|
|
153
|
+
*
|
|
154
|
+
* Strips the old iat/exp claims and re-signs with a fresh expiry.
|
|
155
|
+
* Returns null if the original token cannot be decoded.
|
|
156
|
+
*
|
|
157
|
+
* @param string $token The current JWT token to refresh
|
|
158
|
+
* @param int|null $expiry New expiry duration in seconds (null = use default)
|
|
159
|
+
* @return string|null New JWT token or null on failure
|
|
160
|
+
*/
|
|
161
|
+
public function refresh(string $token, int $expiry = null): ?string
|
|
162
|
+
{
|
|
163
|
+
try {
|
|
164
|
+
$decoded = JWT::decode($token, new Key($this->secretKey, $this->algorithm));
|
|
165
|
+
$payload = (array) $decoded;
|
|
166
|
+
|
|
167
|
+
// Remove old timing claims so generate() sets fresh ones
|
|
168
|
+
unset($payload['iat'], $payload['exp'], $payload['nbf']);
|
|
169
|
+
|
|
170
|
+
return $this->generate($payload, $expiry);
|
|
171
|
+
} catch (\Exception $e) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check whether a token has expired.
|
|
178
|
+
*
|
|
179
|
+
* Attempts to decode the token and catches ExpiredException specifically.
|
|
180
|
+
* Returns true if expired, false if still valid or on other errors.
|
|
181
|
+
*
|
|
182
|
+
* @param string $token The JWT token to check
|
|
183
|
+
* @return bool True if the token is expired
|
|
184
|
+
*/
|
|
185
|
+
public function isExpired(string $token): bool
|
|
186
|
+
{
|
|
187
|
+
try {
|
|
188
|
+
JWT::decode($token, new Key($this->secretKey, $this->algorithm));
|
|
189
|
+
return false;
|
|
190
|
+
} catch (ExpiredException $e) {
|
|
191
|
+
return true;
|
|
192
|
+
} catch (\Exception $e) {
|
|
193
|
+
// Token is invalid for another reason, not strictly "expired"
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Extract the user_id claim from a token.
|
|
200
|
+
*
|
|
201
|
+
* A convenience shortcut for the most common payload lookup.
|
|
202
|
+
*
|
|
203
|
+
* @param string $token The JWT token
|
|
204
|
+
* @return mixed The user_id value or null if not present / token invalid
|
|
205
|
+
*/
|
|
206
|
+
public function getUserId(string $token): mixed
|
|
207
|
+
{
|
|
208
|
+
$payload = $this->payload($token);
|
|
209
|
+
|
|
210
|
+
if ($payload === null) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return $payload['user_id'] ?? null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Generate a long-lived refresh token.
|
|
219
|
+
*
|
|
220
|
+
* Identical to generate() but with a longer default expiry (7 days).
|
|
221
|
+
* Adds a 'token_type' => 'refresh' claim to distinguish from access tokens.
|
|
222
|
+
*
|
|
223
|
+
* @param array $payload Custom claims to include
|
|
224
|
+
* @param int $expiry Lifetime in seconds (default: 604800 = 7 days)
|
|
225
|
+
* @return string Encoded refresh token
|
|
226
|
+
*/
|
|
227
|
+
public function generateRefreshToken(array $payload, int $expiry = 604800): string
|
|
228
|
+
{
|
|
229
|
+
$payload['token_type'] = 'refresh';
|
|
230
|
+
|
|
231
|
+
return $this->generate($payload, $expiry);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* HTTP middleware that validates the Authorization header.
|
|
236
|
+
*
|
|
237
|
+
* Reads the Bearer token from the Authorization header, verifies it, and
|
|
238
|
+
* sets $_REQUEST['jwt_user'] with the decoded payload on success.
|
|
239
|
+
* On failure, sends a 401 JSON response via apiResponse() and exits.
|
|
240
|
+
*
|
|
241
|
+
* Usage:
|
|
242
|
+
* JwtAuth::middleware();
|
|
243
|
+
* // If execution reaches here, $_REQUEST['jwt_user'] is available
|
|
244
|
+
*
|
|
245
|
+
* @return void
|
|
246
|
+
*/
|
|
247
|
+
public static function middleware(): void
|
|
248
|
+
{
|
|
249
|
+
$instance = new self();
|
|
250
|
+
|
|
251
|
+
// Extract Bearer token from Authorization header
|
|
252
|
+
$authHeader = $_SERVER['HTTP_AUTHORIZATION']
|
|
253
|
+
?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION']
|
|
254
|
+
?? '';
|
|
255
|
+
|
|
256
|
+
if (empty($authHeader)) {
|
|
257
|
+
if (function_exists('apiResponse')) {
|
|
258
|
+
apiResponse(401, 'Authorization header is missing.');
|
|
259
|
+
} else {
|
|
260
|
+
http_response_code(401);
|
|
261
|
+
header('Content-Type: application/json');
|
|
262
|
+
echo json_encode([
|
|
263
|
+
'status' => 'error',
|
|
264
|
+
'code' => 401,
|
|
265
|
+
'message' => 'Authorization header is missing.',
|
|
266
|
+
]);
|
|
267
|
+
}
|
|
268
|
+
exit;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Strip "Bearer " prefix
|
|
272
|
+
$token = '';
|
|
273
|
+
if (preg_match('/^Bearer\s+(.+)$/i', $authHeader, $matches)) {
|
|
274
|
+
$token = $matches[1];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (empty($token)) {
|
|
278
|
+
if (function_exists('apiResponse')) {
|
|
279
|
+
apiResponse(401, 'Bearer token is missing.');
|
|
280
|
+
} else {
|
|
281
|
+
http_response_code(401);
|
|
282
|
+
header('Content-Type: application/json');
|
|
283
|
+
echo json_encode([
|
|
284
|
+
'status' => 'error',
|
|
285
|
+
'code' => 401,
|
|
286
|
+
'message' => 'Bearer token is missing.',
|
|
287
|
+
]);
|
|
288
|
+
}
|
|
289
|
+
exit;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
$result = $instance->verify($token);
|
|
293
|
+
|
|
294
|
+
if ($result['status'] !== 'success') {
|
|
295
|
+
if (function_exists('apiResponse')) {
|
|
296
|
+
apiResponse(401, $result['message']);
|
|
297
|
+
} else {
|
|
298
|
+
http_response_code(401);
|
|
299
|
+
header('Content-Type: application/json');
|
|
300
|
+
echo json_encode([
|
|
301
|
+
'status' => 'error',
|
|
302
|
+
'code' => 401,
|
|
303
|
+
'message' => $result['message'],
|
|
304
|
+
]);
|
|
305
|
+
}
|
|
306
|
+
exit;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Attach decoded user payload to the request superglobal
|
|
310
|
+
$_REQUEST['jwt_user'] = $result['data'];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace App\Service;
|
|
3
|
+
|
|
4
|
+
use PHPMailer\PHPMailer\PHPMailer;
|
|
5
|
+
use PHPMailer\PHPMailer\Exception;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ArtiFrame Mailer Wrapper for PHPMailer
|
|
9
|
+
*
|
|
10
|
+
* Provides a clean, fluent API on top of the battle-tested PHPMailer library.
|
|
11
|
+
* Handles complex RFC standards, TLS handshakes, and CRLF injections automatically.
|
|
12
|
+
*/
|
|
13
|
+
class Mailer
|
|
14
|
+
{
|
|
15
|
+
private PHPMailer $mail;
|
|
16
|
+
|
|
17
|
+
public function __construct()
|
|
18
|
+
{
|
|
19
|
+
$this->mail = new PHPMailer(true);
|
|
20
|
+
|
|
21
|
+
// SMTP Configuration from .env
|
|
22
|
+
$this->mail->isSMTP();
|
|
23
|
+
$this->mail->Host = $_ENV['MAIL_HOST'] ?? 'localhost';
|
|
24
|
+
$this->mail->SMTPAuth = !empty($_ENV['MAIL_USERNAME']);
|
|
25
|
+
$this->mail->Username = $_ENV['MAIL_USERNAME'] ?? '';
|
|
26
|
+
$this->mail->Password = $_ENV['MAIL_PASSWORD'] ?? '';
|
|
27
|
+
|
|
28
|
+
$encryption = strtolower($_ENV['MAIL_ENCRYPTION'] ?? '');
|
|
29
|
+
if ($encryption === 'tls') {
|
|
30
|
+
$this->mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
|
31
|
+
} elseif ($encryption === 'ssl') {
|
|
32
|
+
$this->mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
$this->mail->Port = $_ENV['MAIL_PORT'] ?? 25;
|
|
36
|
+
|
|
37
|
+
// Security & Formatting Defaults
|
|
38
|
+
$this->mail->CharSet = 'UTF-8';
|
|
39
|
+
$this->mail->Encoding = 'base64';
|
|
40
|
+
|
|
41
|
+
$this->mail->setFrom(
|
|
42
|
+
$_ENV['MAIL_FROM_ADDRESS'] ?? 'hello@example.com',
|
|
43
|
+
$_ENV['MAIL_FROM_NAME'] ?? 'ArtiFrame'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Instantiates a new Mailer instance for fluent chaining.
|
|
49
|
+
*/
|
|
50
|
+
public static function create(): self
|
|
51
|
+
{
|
|
52
|
+
return new self();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Add a primary recipient.
|
|
57
|
+
*/
|
|
58
|
+
public function to(string $email, string $name = ''): self
|
|
59
|
+
{
|
|
60
|
+
$this->mail->addAddress($email, $name);
|
|
61
|
+
return $this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Add a Carbon Copy (CC) recipient.
|
|
66
|
+
*/
|
|
67
|
+
public function cc(string $email, string $name = ''): self
|
|
68
|
+
{
|
|
69
|
+
$this->mail->addCC($email, $name);
|
|
70
|
+
return $this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add a Blind Carbon Copy (BCC) recipient.
|
|
75
|
+
*/
|
|
76
|
+
public function bcc(string $email, string $name = ''): self
|
|
77
|
+
{
|
|
78
|
+
$this->mail->addBCC($email, $name);
|
|
79
|
+
return $this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set the email subject.
|
|
84
|
+
*/
|
|
85
|
+
public function subject(string $subject): self
|
|
86
|
+
{
|
|
87
|
+
$this->mail->Subject = $subject;
|
|
88
|
+
return $this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Set the body format to HTML. Automatically generates an AltBody (Plain Text) version.
|
|
93
|
+
*/
|
|
94
|
+
public function html(string $html, ?string $altText = null): self
|
|
95
|
+
{
|
|
96
|
+
$this->mail->isHTML(true);
|
|
97
|
+
$this->mail->Body = $html;
|
|
98
|
+
$this->mail->AltBody = $altText ?? strip_tags(str_replace(['<br>', '<br/>'], "\n", $html));
|
|
99
|
+
return $this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set the body format to strict Plain Text.
|
|
104
|
+
*/
|
|
105
|
+
public function text(string $text): self
|
|
106
|
+
{
|
|
107
|
+
$this->mail->isHTML(false);
|
|
108
|
+
$this->mail->Body = $text;
|
|
109
|
+
return $this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Attach a file (PDF, DOCX, ZIP, etc.)
|
|
114
|
+
*/
|
|
115
|
+
public function attach(string $path, string $name = ''): self
|
|
116
|
+
{
|
|
117
|
+
$this->mail->addAttachment($path, $name);
|
|
118
|
+
return $this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Embed an inline image for HTML rendering (e.g., logo).
|
|
123
|
+
* Usage in HTML: <img src="cid:YOUR_CID">
|
|
124
|
+
*/
|
|
125
|
+
public function embedImage(string $path, string $cid, string $name = ''): self
|
|
126
|
+
{
|
|
127
|
+
$this->mail->addEmbeddedImage($path, $cid, $name);
|
|
128
|
+
return $this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Execute the SMTP transmission.
|
|
133
|
+
* @throws \Exception
|
|
134
|
+
*/
|
|
135
|
+
public function send(): bool
|
|
136
|
+
{
|
|
137
|
+
try {
|
|
138
|
+
return $this->mail->send();
|
|
139
|
+
} catch (Exception $e) {
|
|
140
|
+
throw new \Exception("SMTP Mailer Error: " . $this->mail->ErrorInfo);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Service;
|
|
4
|
+
|
|
5
|
+
use Pusher\Pusher;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pusher Realtime Service
|
|
9
|
+
*
|
|
10
|
+
* Provides a clean interface for Pusher Channels — broadcasting events,
|
|
11
|
+
* authenticating private/presence channels, and querying channel state.
|
|
12
|
+
* Uses the pusher/pusher-php-server SDK. All configuration from $_ENV.
|
|
13
|
+
*
|
|
14
|
+
* Required environment variables:
|
|
15
|
+
* PUSHER_APP_ID — Your Pusher application ID
|
|
16
|
+
* PUSHER_APP_KEY — Your Pusher application key
|
|
17
|
+
* PUSHER_APP_SECRET — Your Pusher application secret
|
|
18
|
+
* PUSHER_CLUSTER — Pusher cluster region (e.g., 'eu', 'us2', 'ap1')
|
|
19
|
+
*
|
|
20
|
+
* @package Service
|
|
21
|
+
*/
|
|
22
|
+
class PusherService
|
|
23
|
+
{
|
|
24
|
+
/**
|
|
25
|
+
* The Pusher SDK client instance.
|
|
26
|
+
*
|
|
27
|
+
* @var \Pusher\Pusher
|
|
28
|
+
*/
|
|
29
|
+
private Pusher $pusher;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a new PusherService instance.
|
|
33
|
+
*
|
|
34
|
+
* Initializes the Pusher SDK client using credentials
|
|
35
|
+
* and cluster configuration from environment variables.
|
|
36
|
+
*
|
|
37
|
+
* @throws \Pusher\PusherException If credentials are invalid or missing
|
|
38
|
+
*/
|
|
39
|
+
public function __construct()
|
|
40
|
+
{
|
|
41
|
+
$this->pusher = new Pusher(
|
|
42
|
+
$_ENV['PUSHER_APP_KEY'],
|
|
43
|
+
$_ENV['PUSHER_APP_SECRET'],
|
|
44
|
+
$_ENV['PUSHER_APP_ID'],
|
|
45
|
+
[
|
|
46
|
+
'cluster' => $_ENV['PUSHER_CLUSTER'] ?? 'mt1',
|
|
47
|
+
'useTLS' => true,
|
|
48
|
+
'encrypted' => true,
|
|
49
|
+
]
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Broadcast an event to a single channel.
|
|
55
|
+
*
|
|
56
|
+
* Triggers an event on the specified channel, delivering
|
|
57
|
+
* the given data payload to all subscribers of that channel.
|
|
58
|
+
*
|
|
59
|
+
* Channel naming conventions:
|
|
60
|
+
* - Public channels: 'my-channel'
|
|
61
|
+
* - Private channels: 'private-my-channel'
|
|
62
|
+
* - Presence channels: 'presence-my-channel'
|
|
63
|
+
*
|
|
64
|
+
* @param string $channel The channel name to broadcast on
|
|
65
|
+
* @param string $event The event name to trigger
|
|
66
|
+
* @param array $data The event payload data
|
|
67
|
+
* @return bool True on success, false on failure
|
|
68
|
+
*/
|
|
69
|
+
public function broadcast(string $channel, string $event, array $data): bool
|
|
70
|
+
{
|
|
71
|
+
try {
|
|
72
|
+
$response = $this->pusher->trigger($channel, $event, $data);
|
|
73
|
+
|
|
74
|
+
return (bool) $response;
|
|
75
|
+
} catch (\Exception $e) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Broadcast an event to multiple channels simultaneously.
|
|
82
|
+
*
|
|
83
|
+
* Triggers the same event with the same payload across
|
|
84
|
+
* multiple channels in a single API call. Pusher limits
|
|
85
|
+
* this to a maximum of 100 channels per call.
|
|
86
|
+
*
|
|
87
|
+
* @param array $channels List of channel names (max 100)
|
|
88
|
+
* @param string $event The event name to trigger
|
|
89
|
+
* @param array $data The event payload data
|
|
90
|
+
* @return bool True on success, false on failure
|
|
91
|
+
*/
|
|
92
|
+
public function broadcastToMany(array $channels, string $event, array $data): bool
|
|
93
|
+
{
|
|
94
|
+
try {
|
|
95
|
+
$response = $this->pusher->trigger($channels, $event, $data);
|
|
96
|
+
|
|
97
|
+
return (bool) $response;
|
|
98
|
+
} catch (\Exception $e) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Send an event to a specific user via their user channel.
|
|
105
|
+
*
|
|
106
|
+
* Uses Pusher's user event feature to deliver a message directly
|
|
107
|
+
* to a single authenticated user, regardless of which channels
|
|
108
|
+
* they are subscribed to. The user must have been authenticated
|
|
109
|
+
* with a user ID on the client side.
|
|
110
|
+
*
|
|
111
|
+
* @param string $userId The target user's unique identifier
|
|
112
|
+
* @param string $event The event name to trigger
|
|
113
|
+
* @param array $data The event payload data
|
|
114
|
+
* @return bool True on success, false on failure
|
|
115
|
+
*/
|
|
116
|
+
public function toUser(string $userId, string $event, array $data): bool
|
|
117
|
+
{
|
|
118
|
+
try {
|
|
119
|
+
$response = $this->pusher->sendToUser($userId, $event, $data);
|
|
120
|
+
|
|
121
|
+
return (bool) $response;
|
|
122
|
+
} catch (\Exception $e) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get information about a specific channel.
|
|
129
|
+
*
|
|
130
|
+
* Returns metadata about the channel including whether it is
|
|
131
|
+
* currently occupied and, for presence channels, the user count.
|
|
132
|
+
*
|
|
133
|
+
* @param string $channel The channel name to query
|
|
134
|
+
* @return array Channel info ['occupied' => bool, 'user_count' => int|null]
|
|
135
|
+
* Returns empty array on failure
|
|
136
|
+
*/
|
|
137
|
+
public function channelInfo(string $channel): array
|
|
138
|
+
{
|
|
139
|
+
try {
|
|
140
|
+
$info = $this->pusher->getChannelInfo($channel, [
|
|
141
|
+
'info' => 'user_count,subscription_count',
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
return [
|
|
145
|
+
'occupied' => $info->occupied ?? false,
|
|
146
|
+
'user_count' => $info->user_count ?? null,
|
|
147
|
+
'subscription_count' => $info->subscription_count ?? null,
|
|
148
|
+
];
|
|
149
|
+
} catch (\Exception $e) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* List all active (occupied) channels.
|
|
156
|
+
*
|
|
157
|
+
* Optionally filters channels by a name prefix. For example,
|
|
158
|
+
* passing 'presence-' will return only active presence channels.
|
|
159
|
+
*
|
|
160
|
+
* @param string $prefix Optional channel name prefix filter
|
|
161
|
+
* @return array List of active channel names
|
|
162
|
+
*/
|
|
163
|
+
public function channels(string $prefix = ''): array
|
|
164
|
+
{
|
|
165
|
+
try {
|
|
166
|
+
$params = [];
|
|
167
|
+
|
|
168
|
+
if ($prefix !== '') {
|
|
169
|
+
$params['filter_by_prefix'] = $prefix;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
$result = $this->pusher->getChannels($params);
|
|
173
|
+
|
|
174
|
+
if (isset($result->channels)) {
|
|
175
|
+
return array_keys((array) $result->channels);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return [];
|
|
179
|
+
} catch (\Exception $e) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Authenticate a private or presence channel subscription.
|
|
186
|
+
*
|
|
187
|
+
* This method should be called from your authentication endpoint
|
|
188
|
+
* when a client attempts to subscribe to a private or presence channel.
|
|
189
|
+
* Returns the authentication signature string that the client SDK expects.
|
|
190
|
+
*
|
|
191
|
+
* For presence channels, use presenceAuth() instead to include user data.
|
|
192
|
+
*
|
|
193
|
+
* @param string $channelName The channel being subscribed to
|
|
194
|
+
* @param string $socketId The connecting socket's ID
|
|
195
|
+
* @return string JSON-encoded authentication response
|
|
196
|
+
*/
|
|
197
|
+
public function auth(string $channelName, string $socketId): string
|
|
198
|
+
{
|
|
199
|
+
try {
|
|
200
|
+
return $this->pusher->authorizeChannel($channelName, $socketId);
|
|
201
|
+
} catch (\Exception $e) {
|
|
202
|
+
return json_encode(['error' => $e->getMessage()]);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Authenticate a presence channel subscription with user data.
|
|
208
|
+
*
|
|
209
|
+
* Similar to auth() but also attaches user identity and optional
|
|
210
|
+
* metadata to the presence channel. This allows other subscribers
|
|
211
|
+
* to see who is present in the channel.
|
|
212
|
+
*
|
|
213
|
+
* @param string $channelName The presence channel being subscribed to
|
|
214
|
+
* @param string $socketId The connecting socket's ID
|
|
215
|
+
* @param string $userId The unique identifier for the subscribing user
|
|
216
|
+
* @param array $userInfo Optional additional user metadata (e.g., name, avatar)
|
|
217
|
+
* @return string JSON-encoded authentication response with user data
|
|
218
|
+
*/
|
|
219
|
+
public function presenceAuth(string $channelName, string $socketId, string $userId, array $userInfo = []): string
|
|
220
|
+
{
|
|
221
|
+
try {
|
|
222
|
+
$presenceData = [
|
|
223
|
+
'user_id' => $userId,
|
|
224
|
+
'user_info' => $userInfo,
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
return $this->pusher->authorizePresenceChannel($channelName, $socketId, $userId, $userInfo);
|
|
228
|
+
} catch (\Exception $e) {
|
|
229
|
+
return json_encode(['error' => $e->getMessage()]);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|