@fleetbase/fleetops-engine 0.6.52 → 0.6.54
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/addon/components/device/details.hbs +106 -135
- package/addon/components/device/details.js +174 -1
- package/addon/components/device/panel-header.hbs +2 -2
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
- package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
- package/addon/components/layout/fleet-ops-sidebar.js +364 -395
- package/addon/components/maintenance-schedule/form.js +1 -0
- package/addon/components/order/form/details.hbs +1 -43
- package/addon/components/order/form/details.js +18 -53
- package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
- package/addon/components/order/form/orchestrator-constraints.js +61 -0
- package/addon/components/order/form/payload.js +50 -2
- package/addon/components/order/form/route.js +15 -0
- package/addon/components/order/form/service-rate.hbs +62 -11
- package/addon/components/order/form/service-rate.js +124 -2
- package/addon/components/order/form.hbs +2 -0
- package/addon/components/telematic/details.hbs +7 -1
- package/addon/components/telematic/form.js +16 -8
- package/addon/components/work-order/details.hbs +5 -1
- package/addon/components/work-order/form.hbs +26 -4
- package/addon/components/work-order/form.js +5 -1
- package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
- package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
- package/addon/controllers/connectivity/devices/index/details.js +12 -0
- package/addon/controllers/connectivity/devices/index.js +4 -1
- package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
- package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
- package/addon/controllers/connectivity/telematics/details/events.js +39 -11
- package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
- package/addon/controllers/maintenance/index.js +100 -0
- package/addon/controllers/maintenance/work-orders/index.js +18 -1
- package/addon/controllers/management/fuel-transactions/index.js +1 -1
- package/addon/controllers/management/index.js +88 -0
- package/addon/controllers/settings/index.js +142 -0
- package/addon/extension.js +21 -0
- package/addon/routes/analytics/index.js +3 -0
- package/addon/routes/connectivity/devices/index/details/events.js +37 -1
- package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
- package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
- package/addon/routes/connectivity/devices/index.js +3 -0
- package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
- package/addon/routes/connectivity/telematics/details/devices.js +4 -0
- package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
- package/addon/routes/connectivity/telematics/details.js +1 -1
- package/addon/routes/connectivity/telematics/edit.js +1 -1
- package/addon/routes/maintenance/index.js +3 -0
- package/addon/routes/maintenance/work-orders/index.js +3 -0
- package/addon/routes/settings/index.js +3 -0
- package/addon/routes.js +2 -0
- package/addon/services/order-creation.js +54 -1
- package/addon/services/order-validation.js +3 -2
- package/addon/services/service-rate-actions.js +1 -0
- package/addon/styles/fleetops-engine.css +1821 -6
- package/addon/templates/analytics/index.hbs +14 -0
- package/addon/templates/analytics/reports/index.hbs +4 -4
- package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
- package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
- package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
- package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
- package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
- package/addon/templates/maintenance/index.hbs +113 -0
- package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
- package/addon/templates/management/index.hbs +109 -73
- package/addon/templates/management/vehicles/index/edit.hbs +1 -1
- package/addon/templates/operations/orders/index.hbs +1 -1
- package/addon/templates/operations/service-rates/index.hbs +1 -1
- package/addon/templates/settings/index.hbs +83 -0
- package/addon/templates/settings/routing.hbs +2 -2
- package/addon/utils/fleet-ops-options.js +38 -0
- package/app/components/order/form/orchestrator-constraints.js +1 -0
- package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
- package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/controllers/maintenance/index.js +1 -0
- package/app/controllers/management/index.js +1 -0
- package/app/controllers/settings/index.js +1 -0
- package/app/routes/analytics/index.js +1 -0
- package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
- package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/routes/maintenance/index.js +1 -0
- package/app/routes/settings/index.js +1 -0
- package/app/templates/analytics/index.js +1 -0
- package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
- package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
- package/app/templates/maintenance/index.js +1 -0
- package/app/templates/management/index.js +1 -0
- package/app/templates/settings/index.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
- package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
- package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
- package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
- package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
- package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
- package/server/src/Console/Commands/SyncTelematics.php +89 -0
- package/server/src/Exceptions/TelematicProviderException.php +13 -2
- package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
- package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
- package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
- package/server/src/Http/Filter/DeviceFilter.php +131 -0
- package/server/src/Http/Filter/DriverFilter.php +6 -0
- package/server/src/Http/Filter/SensorFilter.php +125 -0
- package/server/src/Http/Filter/VehicleFilter.php +19 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
- package/server/src/Http/Resources/v1/Driver.php +11 -2
- package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
- package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
- package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
- package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
- package/server/src/Models/Device.php +15 -1
- package/server/src/Models/DeviceEvent.php +1 -1
- package/server/src/Models/Driver.php +17 -1
- package/server/src/Models/Sensor.php +15 -2
- package/server/src/Models/Telematic.php +1 -1
- package/server/src/Models/Vehicle.php +5 -1
- package/server/src/Models/WorkOrder.php +5 -2
- package/server/src/Observers/DriverObserver.php +3 -3
- package/server/src/Observers/FleetObserver.php +23 -0
- package/server/src/Observers/VehicleObserver.php +3 -3
- package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
- package/server/src/Support/LiveCacheService.php +1 -1
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
- package/server/src/Support/Telematics/TelematicService.php +330 -30
- package/server/src/routes.php +10 -0
- package/server/tests/DeviceFilterTest.php +36 -0
- package/server/tests/HubRoutesTest.php +62 -0
- package/server/tests/OperationsMonitorEndpointTest.php +38 -0
- package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
- package/server/tests/TelematicsHardeningTest.php +455 -3
- package/server/tests/VehicleDriverAssignmentTest.php +27 -0
- package/translations/ar-ae.yml +1 -1
- package/translations/en-us.yaml +1 -0
- package/translations/vi-vn.yaml +1 -1
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Support\Telematics\Providers;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Exceptions\TelematicProviderException;
|
|
6
|
+
use Illuminate\Http\Client\ConnectionException;
|
|
7
|
+
use Illuminate\Http\Client\Response;
|
|
5
8
|
use Illuminate\Support\Carbon;
|
|
6
9
|
use Illuminate\Support\Facades\Http;
|
|
10
|
+
use Illuminate\Support\Facades\Log;
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* AFAQY telematics provider implementation.
|
|
@@ -13,20 +17,23 @@ use Illuminate\Support\Facades\Http;
|
|
|
13
17
|
*/
|
|
14
18
|
class AfaqyProvider extends AbstractProvider
|
|
15
19
|
{
|
|
16
|
-
protected string $baseUrl
|
|
17
|
-
protected int $requestsPerMinute
|
|
20
|
+
protected string $baseUrl = 'https://api.afaqy.sa';
|
|
21
|
+
protected int $requestsPerMinute = 60;
|
|
22
|
+
protected int $dataTimeout = 120;
|
|
23
|
+
protected int $connectTimeout = 15;
|
|
24
|
+
protected int $connectionTestTimeout = 30;
|
|
25
|
+
protected int $connectionTestConnectTimeout = 10;
|
|
18
26
|
|
|
19
27
|
protected function prepareAuthentication(): void
|
|
20
28
|
{
|
|
21
29
|
$this->baseUrl = rtrim($this->credentials['base_url'] ?? $this->baseUrl, '/');
|
|
22
|
-
$token = $this->credentials['token'] ??
|
|
30
|
+
$token = $this->canRefreshToken() ? $this->authenticate() : ($this->credentials['token'] ?? null);
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
];
|
|
32
|
+
if (!$token) {
|
|
33
|
+
throw new \InvalidArgumentException('AFAQY username/password or token is required.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
$this->setToken($token);
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
public function testConnection(array $credentials): array
|
|
@@ -40,7 +47,7 @@ class AfaqyProvider extends AbstractProvider
|
|
|
40
47
|
'projection' => ['_id', 'name', 'imei', 'last_update'],
|
|
41
48
|
'filters' => new \stdClass(),
|
|
42
49
|
],
|
|
43
|
-
]);
|
|
50
|
+
], true, $this->connectionTestTimeout, $this->connectionTestConnectTimeout);
|
|
44
51
|
|
|
45
52
|
return [
|
|
46
53
|
'success' => true,
|
|
@@ -54,20 +61,27 @@ class AfaqyProvider extends AbstractProvider
|
|
|
54
61
|
return [
|
|
55
62
|
'success' => false,
|
|
56
63
|
'message' => $e->getMessage(),
|
|
57
|
-
'metadata' => [],
|
|
64
|
+
'metadata' => method_exists($e, 'context') ? $e->context() : [],
|
|
58
65
|
];
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
public function fetchDevices(array $options = []): array
|
|
63
70
|
{
|
|
64
|
-
$limit
|
|
65
|
-
$offset
|
|
71
|
+
$limit = max(1, min((int) ($options['limit'] ?? 500), 500));
|
|
72
|
+
$offset = (int) ($options['cursor'] ?? 0);
|
|
73
|
+
$filters = $options['filters'] ?? new \stdClass();
|
|
74
|
+
|
|
75
|
+
if (is_array($filters) && empty($filters)) {
|
|
76
|
+
$filters = new \stdClass();
|
|
77
|
+
}
|
|
66
78
|
|
|
67
79
|
$response = $this->afaqyPost('/units/lists', [
|
|
68
80
|
'data' => [
|
|
69
81
|
'address' => $options['address'] ?? false,
|
|
70
|
-
'filters' => $
|
|
82
|
+
'filters' => $filters,
|
|
83
|
+
'limit' => $limit,
|
|
84
|
+
'offset' => $offset,
|
|
71
85
|
'projection' => $options['projection'] ?? [
|
|
72
86
|
'_id',
|
|
73
87
|
'name',
|
|
@@ -82,19 +96,42 @@ class AfaqyProvider extends AbstractProvider
|
|
|
82
96
|
'sensors_last_val',
|
|
83
97
|
],
|
|
84
98
|
],
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
], true);
|
|
100
|
+
|
|
101
|
+
$devices = $response['data'] ?? [];
|
|
102
|
+
$pagination = $response['pagination'] ?? [];
|
|
103
|
+
$pageOffset = (int) ($pagination['offset'] ?? $offset);
|
|
104
|
+
$pageLimit = (int) ($pagination['limit'] ?? $limit);
|
|
105
|
+
$resultCount = (int) ($pagination['resultCount'] ?? count($devices));
|
|
106
|
+
$total = (int) ($pagination['filtersCount'] ?? $pagination['allCount'] ?? count($devices));
|
|
107
|
+
$advanceBy = $resultCount > 0 ? $resultCount : max($pageLimit, count($devices), 1);
|
|
108
|
+
$nextCursor = ($pageOffset + $advanceBy) < $total ? $pageOffset + $advanceBy : null;
|
|
109
|
+
|
|
110
|
+
Log::info('AFAQY units page fetched', [
|
|
111
|
+
'telematic_uuid' => $this->telematic?->uuid,
|
|
112
|
+
'requested_offset' => $offset,
|
|
113
|
+
'requested_limit' => $limit,
|
|
114
|
+
'provider_offset' => $pageOffset,
|
|
115
|
+
'provider_limit' => $pageLimit,
|
|
116
|
+
'provider_result_count' => $resultCount,
|
|
117
|
+
'result_count' => count($devices),
|
|
118
|
+
'all_count' => $pagination['allCount'] ?? null,
|
|
119
|
+
'filters_count' => $pagination['filtersCount'] ?? null,
|
|
120
|
+
'next_cursor' => $nextCursor,
|
|
121
|
+
'has_more' => $nextCursor !== null,
|
|
87
122
|
]);
|
|
88
123
|
|
|
89
|
-
$devices = $response['data'] ?? [];
|
|
90
|
-
$pagination = $response['pagination'] ?? [];
|
|
91
|
-
$total = (int) ($pagination['filtersCount'] ?? $pagination['allCount'] ?? count($devices));
|
|
92
|
-
$nextCursor = ($offset + $limit) < $total ? $offset + $limit : null;
|
|
93
|
-
|
|
94
124
|
return [
|
|
95
125
|
'devices' => $devices,
|
|
96
126
|
'next_cursor' => $nextCursor,
|
|
97
127
|
'has_more' => $nextCursor !== null,
|
|
128
|
+
'pagination' => [
|
|
129
|
+
'allCount' => $pagination['allCount'] ?? null,
|
|
130
|
+
'filtersCount' => $pagination['filtersCount'] ?? null,
|
|
131
|
+
'resultCount' => $resultCount,
|
|
132
|
+
'offset' => $pageOffset,
|
|
133
|
+
'limit' => $pageLimit,
|
|
134
|
+
],
|
|
98
135
|
];
|
|
99
136
|
}
|
|
100
137
|
|
|
@@ -156,12 +193,14 @@ class AfaqyProvider extends AbstractProvider
|
|
|
156
193
|
'event_type' => $payload['event'] ?? $payload['event_type'] ?? 'telemetry_update',
|
|
157
194
|
'message' => $payload['message'] ?? $payload['event'] ?? null,
|
|
158
195
|
'occurred_at' => $this->parseTimestamp($lastUpdate['dtt'] ?? $lastUpdate['dts'] ?? null),
|
|
196
|
+
'online' => $payload['active'] ?? null,
|
|
159
197
|
'location' => [
|
|
160
198
|
'lat' => $lastUpdate['lat'] ?? data_get($lastUpdate, 'loc.coordinates.1'),
|
|
161
199
|
'lng' => $lastUpdate['lng'] ?? data_get($lastUpdate, 'loc.coordinates.0'),
|
|
162
200
|
],
|
|
163
201
|
'speed' => $lastUpdate['speed'] ?? $lastUpdate['spd'] ?? null,
|
|
164
202
|
'heading' => $lastUpdate['angle'] ?? $lastUpdate['ang'] ?? null,
|
|
203
|
+
'altitude' => $lastUpdate['alt'] ?? null,
|
|
165
204
|
'odometer' => data_get($payload, 'counters.odometer'),
|
|
166
205
|
'ignition' => $this->extractIgnition($payload),
|
|
167
206
|
'fuel_level' => $this->extractFuelLevel($payload),
|
|
@@ -240,31 +279,152 @@ class AfaqyProvider extends AbstractProvider
|
|
|
240
279
|
]);
|
|
241
280
|
|
|
242
281
|
if ($response->failed()) {
|
|
243
|
-
throw new
|
|
282
|
+
throw new TelematicProviderException('AFAQY authentication failed with status ' . $response->status(), $this->providerErrorContext('/auth/login', $response, false));
|
|
244
283
|
}
|
|
245
284
|
|
|
246
285
|
$token = data_get($response->json(), 'data.token');
|
|
247
286
|
|
|
248
287
|
if (!$token) {
|
|
249
|
-
throw new
|
|
288
|
+
throw new TelematicProviderException('AFAQY authentication did not return a token.', $this->providerErrorContext('/auth/login', $response, false));
|
|
250
289
|
}
|
|
251
290
|
|
|
252
291
|
return $token;
|
|
253
292
|
}
|
|
254
293
|
|
|
255
|
-
protected function afaqyPost(string $endpoint, array $payload = []): array
|
|
294
|
+
protected function afaqyPost(string $endpoint, array $payload = [], bool $tokenInQuery = false, ?int $timeout = null, ?int $connectTimeout = null): array
|
|
256
295
|
{
|
|
257
|
-
$
|
|
258
|
-
|
|
259
|
-
|
|
296
|
+
return $this->authenticatedPost($endpoint, $payload, $tokenInQuery, true, $timeout, $connectTimeout);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
protected function authenticatedPost(string $endpoint, array $payload = [], bool $tokenInQuery = false, bool $allowRetry = true, ?int $timeout = null, ?int $connectTimeout = null): array
|
|
300
|
+
{
|
|
301
|
+
[$url, $body] = $this->buildAuthenticatedRequest($endpoint, $payload, $tokenInQuery);
|
|
302
|
+
|
|
303
|
+
$startedAt = microtime(true);
|
|
304
|
+
$timeout ??= $this->dataTimeout;
|
|
305
|
+
$connectTimeout ??= $this->connectTimeout;
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
$response = Http::withHeaders($this->headers)
|
|
309
|
+
->timeout($timeout)
|
|
310
|
+
->connectTimeout($connectTimeout)
|
|
311
|
+
->post($url, $body);
|
|
312
|
+
} catch (ConnectionException $e) {
|
|
313
|
+
throw new TelematicProviderException('AFAQY API request timed out while waiting for provider response.', $this->transportErrorContext($endpoint, $payload, $e, !$allowRetry, $startedAt, $timeout, $connectTimeout), previous: $e);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if ($this->isTokenRejected($response)) {
|
|
317
|
+
if (!$allowRetry || !$this->canRefreshToken()) {
|
|
318
|
+
$message = $this->canRefreshToken()
|
|
319
|
+
? 'AFAQY token rejected after refresh with status ' . $response->status()
|
|
320
|
+
: 'AFAQY token rejected and username/password credentials are required to refresh it.';
|
|
321
|
+
|
|
322
|
+
throw new TelematicProviderException($message, $this->providerErrorContext($endpoint, $response, !$allowRetry));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
Log::warning('AFAQY token rejected; refreshing token and retrying request', $this->providerErrorContext($endpoint, $response, true));
|
|
326
|
+
|
|
327
|
+
$this->refreshToken();
|
|
328
|
+
|
|
329
|
+
return $this->authenticatedPost($endpoint, $payload, $tokenInQuery, false, $timeout, $connectTimeout);
|
|
330
|
+
}
|
|
260
331
|
|
|
261
332
|
if ($response->failed()) {
|
|
262
|
-
throw new
|
|
333
|
+
throw new TelematicProviderException('AFAQY API request failed with status ' . $response->status(), $this->providerErrorContext($endpoint, $response, !$allowRetry));
|
|
263
334
|
}
|
|
264
335
|
|
|
265
336
|
return $response->json() ?? [];
|
|
266
337
|
}
|
|
267
338
|
|
|
339
|
+
protected function buildAuthenticatedRequest(string $endpoint, array $payload = [], bool $tokenInQuery = false): array
|
|
340
|
+
{
|
|
341
|
+
$url = $this->baseUrl . $endpoint;
|
|
342
|
+
$body = $tokenInQuery ? $payload : array_merge(['token' => $this->credentials['token']], $payload);
|
|
343
|
+
|
|
344
|
+
if ($tokenInQuery) {
|
|
345
|
+
$url .= (str_contains($url, '?') ? '&' : '?') . http_build_query(['token' => $this->credentials['token']]);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return [$url, $body];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
protected function setToken(string $token): void
|
|
352
|
+
{
|
|
353
|
+
$this->credentials['token'] = $token;
|
|
354
|
+
$this->headers = [
|
|
355
|
+
'Accept' => 'application/json',
|
|
356
|
+
'Content-Type' => 'application/json',
|
|
357
|
+
'Authorization' => 'Bearer ' . $token,
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
protected function refreshToken(): void
|
|
362
|
+
{
|
|
363
|
+
unset($this->credentials['token']);
|
|
364
|
+
|
|
365
|
+
$this->setToken($this->authenticate());
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
protected function canRefreshToken(): bool
|
|
369
|
+
{
|
|
370
|
+
return !empty($this->credentials['username']) && !empty($this->credentials['password']);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
protected function isTokenRejected(Response $response): bool
|
|
374
|
+
{
|
|
375
|
+
return in_array($response->status(), [401, 403], true);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
protected function providerErrorContext(string $endpoint, Response $response, bool $retryAttempted): array
|
|
379
|
+
{
|
|
380
|
+
$json = $response->json() ?? [];
|
|
381
|
+
$json = is_array($json) ? $json : [];
|
|
382
|
+
|
|
383
|
+
return array_filter([
|
|
384
|
+
'provider' => 'afaqy',
|
|
385
|
+
'endpoint' => $endpoint,
|
|
386
|
+
'status_code' => $response->status(),
|
|
387
|
+
'provider_code' => data_get($json, 'code') ?? data_get($json, 'error.code') ?? data_get($json, 'error_code'),
|
|
388
|
+
'provider_message' => $this->providerErrorMessage($json),
|
|
389
|
+
'retry_attempted' => $retryAttempted,
|
|
390
|
+
], fn ($value) => $value !== null);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
protected function providerErrorMessage(array $json): ?string
|
|
394
|
+
{
|
|
395
|
+
$message = data_get($json, 'message')
|
|
396
|
+
?? data_get($json, 'error.message')
|
|
397
|
+
?? data_get($json, 'error_description')
|
|
398
|
+
?? data_get($json, 'error');
|
|
399
|
+
|
|
400
|
+
return is_scalar($message) ? (string) $message : null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
protected function transportErrorContext(string $endpoint, array $payload, ConnectionException $e, bool $retryAttempted, float $startedAt, int $timeout, int $connectTimeout): array
|
|
404
|
+
{
|
|
405
|
+
return array_filter([
|
|
406
|
+
'provider' => 'afaqy',
|
|
407
|
+
'endpoint' => $endpoint,
|
|
408
|
+
'requested_limit' => data_get($payload, 'data.limit'),
|
|
409
|
+
'requested_offset' => data_get($payload, 'data.offset'),
|
|
410
|
+
'timeout' => $timeout,
|
|
411
|
+
'connect_timeout' => $connectTimeout,
|
|
412
|
+
'elapsed_ms' => (int) ((microtime(true) - $startedAt) * 1000),
|
|
413
|
+
'bytes_received' => $this->extractBytesReceived($e->getMessage()),
|
|
414
|
+
'retry_attempted' => $retryAttempted,
|
|
415
|
+
'transport_error' => 'connection_exception',
|
|
416
|
+
], fn ($value) => $value !== null);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
protected function extractBytesReceived(string $message): ?int
|
|
420
|
+
{
|
|
421
|
+
if (preg_match('/with\s+(\d+)\s+bytes\s+received/i', $message, $matches)) {
|
|
422
|
+
return (int) $matches[1];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
|
|
268
428
|
protected function compactLastUpdate(array $lastUpdate): array
|
|
269
429
|
{
|
|
270
430
|
$params = $lastUpdate['params'] ?? $lastUpdate['prms'] ?? [];
|
|
@@ -16,10 +16,11 @@ class SafeeProvider extends AbstractProvider
|
|
|
16
16
|
protected string $baseUrl = 'https://api.safee.com';
|
|
17
17
|
protected int $requestsPerMinute = 3000;
|
|
18
18
|
protected ?string $accessToken = null;
|
|
19
|
+
protected array $authContext = [];
|
|
19
20
|
|
|
20
21
|
protected function prepareAuthentication(): void
|
|
21
22
|
{
|
|
22
|
-
$this->baseUrl =
|
|
23
|
+
$this->baseUrl = $this->resolveBaseUrl();
|
|
23
24
|
$this->accessToken = $this->credentials['access_token'] ?? $this->authenticate();
|
|
24
25
|
$scheme = $this->credentials['authorization_scheme'] ?? 'Bearer';
|
|
25
26
|
|
|
@@ -45,13 +46,14 @@ class SafeeProvider extends AbstractProvider
|
|
|
45
46
|
'metadata' => [
|
|
46
47
|
'status' => $response['status'] ?? null,
|
|
47
48
|
'time' => $response['time'] ?? null,
|
|
49
|
+
...$this->safeDiagnosticMetadata(),
|
|
48
50
|
],
|
|
49
51
|
];
|
|
50
52
|
} catch (\Throwable $e) {
|
|
51
53
|
return [
|
|
52
54
|
'success' => false,
|
|
53
55
|
'message' => $e->getMessage(),
|
|
54
|
-
'metadata' =>
|
|
56
|
+
'metadata' => $this->safeDiagnosticMetadata(),
|
|
55
57
|
];
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -219,7 +221,8 @@ class SafeeProvider extends AbstractProvider
|
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
$tokenUrl
|
|
224
|
+
$tokenUrl = $this->baseUrl . '/auth/realms/' . $this->credentials['realm_id'] . '/protocol/openid-connect/token';
|
|
225
|
+
$this->authContext = $this->buildAuthContext($tokenUrl);
|
|
223
226
|
|
|
224
227
|
$response = Http::asForm()
|
|
225
228
|
->acceptJson()
|
|
@@ -245,6 +248,44 @@ class SafeeProvider extends AbstractProvider
|
|
|
245
248
|
return $token;
|
|
246
249
|
}
|
|
247
250
|
|
|
251
|
+
protected function resolveBaseUrl(): string
|
|
252
|
+
{
|
|
253
|
+
$baseUrl = $this->filledCredential('api_base_url') ?? $this->filledCredential('server_uri') ?? $this->baseUrl;
|
|
254
|
+
|
|
255
|
+
return rtrim($baseUrl, '/');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
protected function filledCredential(string $key): ?string
|
|
259
|
+
{
|
|
260
|
+
$value = $this->credentials[$key] ?? null;
|
|
261
|
+
|
|
262
|
+
if (!is_string($value)) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
$value = trim($value);
|
|
267
|
+
|
|
268
|
+
return $value === '' ? null : $value;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
protected function buildAuthContext(string $tokenUrl): array
|
|
272
|
+
{
|
|
273
|
+
$parts = parse_url($tokenUrl) ?: [];
|
|
274
|
+
$scheme = $parts['scheme'] ?? null;
|
|
275
|
+
$host = $parts['host'] ?? null;
|
|
276
|
+
|
|
277
|
+
return [
|
|
278
|
+
'auth_host' => $host ? trim(($scheme ? $scheme . '://' : '') . $host, '/') : null,
|
|
279
|
+
'auth_path' => $parts['path'] ?? null,
|
|
280
|
+
'realm_id' => $this->credentials['realm_id'] ?? null,
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
protected function safeDiagnosticMetadata(): array
|
|
285
|
+
{
|
|
286
|
+
return array_filter($this->authContext, fn ($value) => $value !== null && $value !== '');
|
|
287
|
+
}
|
|
288
|
+
|
|
248
289
|
protected function safeeGet(string $endpoint): array
|
|
249
290
|
{
|
|
250
291
|
$response = Http::withHeaders($this->headers)
|