@fleetbase/fleetops-engine 0.6.20 → 0.6.21

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.
Files changed (120) hide show
  1. package/addon/components/custom-entity/form.hbs +14 -14
  2. package/addon/components/device/details.hbs +92 -43
  3. package/addon/components/device/form.hbs +108 -60
  4. package/addon/components/device/form.js +36 -8
  5. package/addon/components/device/panel-header.hbs +32 -0
  6. package/addon/components/device/panel-header.js +3 -0
  7. package/addon/components/driver/form.hbs +1 -1
  8. package/addon/components/driver/form.js +49 -47
  9. package/addon/components/entity/form.hbs +7 -5
  10. package/addon/components/layout/fleet-ops-sidebar.js +12 -12
  11. package/addon/components/map/drawer/device-event-listing.hbs +58 -0
  12. package/addon/components/map/drawer/device-event-listing.js +181 -0
  13. package/addon/components/map/drawer/position-listing.hbs +84 -0
  14. package/addon/components/map/drawer/position-listing.js +289 -0
  15. package/addon/components/map/drawer.js +2 -0
  16. package/addon/components/map/leaflet-live-map.hbs +7 -2
  17. package/addon/components/order/details/payload.hbs +6 -4
  18. package/addon/components/order/details/payload.js +2 -0
  19. package/addon/components/order-config-manager/custom-fields.js +1 -1
  20. package/addon/components/positions-replay.hbs +333 -0
  21. package/addon/components/positions-replay.js +372 -0
  22. package/addon/components/sensor/details.hbs +64 -38
  23. package/addon/components/sensor/form.hbs +112 -63
  24. package/addon/components/sensor/form.js +36 -24
  25. package/addon/components/sensor/panel-header.hbs +32 -0
  26. package/addon/components/sensor/panel-header.js +3 -0
  27. package/addon/components/telematic/details.hbs +40 -16
  28. package/addon/components/telematic/form.hbs +63 -64
  29. package/addon/components/telematic/form.js +73 -4
  30. package/addon/components/vehicle/card.hbs +1 -1
  31. package/addon/controllers/analytics/reports/index/edit.js +1 -1
  32. package/addon/controllers/connectivity/devices/index/details.js +22 -1
  33. package/addon/controllers/connectivity/devices/index/edit.js +66 -1
  34. package/addon/controllers/connectivity/devices/index.js +51 -9
  35. package/addon/controllers/connectivity/events/index.js +65 -16
  36. package/addon/controllers/connectivity/sensors/index/details.js +22 -1
  37. package/addon/controllers/connectivity/sensors/index/edit.js +66 -1
  38. package/addon/controllers/connectivity/sensors/index.js +66 -6
  39. package/addon/controllers/connectivity/telematics/index/details.js +22 -1
  40. package/addon/controllers/connectivity/telematics/index/edit.js +66 -1
  41. package/addon/controllers/connectivity/telematics/index.js +20 -11
  42. package/addon/controllers/management/fleets/index/details.js +26 -21
  43. package/addon/controllers/management/fleets/index/edit.js +9 -6
  44. package/addon/controllers/management/vehicles/index/details.js +21 -13
  45. package/addon/controllers/settings/custom-fields.js +6 -0
  46. package/addon/helpers/get-fleet-ops-option-label.js +11 -0
  47. package/addon/routes/connectivity/devices/index/details.js +27 -1
  48. package/addon/routes/connectivity/devices/index/edit.js +27 -1
  49. package/addon/routes/connectivity/sensors/index/details.js +27 -1
  50. package/addon/routes/connectivity/sensors/index/edit.js +27 -1
  51. package/addon/routes/connectivity/telematics/index/details.js +27 -1
  52. package/addon/routes/connectivity/telematics/index/edit.js +27 -1
  53. package/addon/routes/management/vehicles/index/details/positions.js +3 -0
  54. package/addon/routes.js +1 -0
  55. package/addon/services/movement-tracker.js +81 -30
  56. package/addon/styles/fleetops-engine.css +157 -0
  57. package/addon/templates/connectivity/devices/index/details/index.hbs +2 -2
  58. package/addon/templates/connectivity/devices/index/details.hbs +15 -2
  59. package/addon/templates/connectivity/devices/index/edit.hbs +1 -1
  60. package/addon/templates/connectivity/events/index.hbs +1 -1
  61. package/addon/templates/connectivity/sensors/index/details/index.hbs +2 -2
  62. package/addon/templates/connectivity/sensors/index/details.hbs +15 -2
  63. package/addon/templates/connectivity/sensors/index/edit.hbs +1 -1
  64. package/addon/templates/connectivity/telematics/index/details/index.hbs +2 -2
  65. package/addon/templates/connectivity/telematics/index/details.hbs +14 -2
  66. package/addon/templates/connectivity/telematics/index/edit.hbs +1 -1
  67. package/addon/templates/management/vehicles/index/details/positions.hbs +1 -0
  68. package/addon/utils/fleet-ops-options.js +95 -0
  69. package/app/components/device/panel-header.js +1 -0
  70. package/app/components/map/drawer/device-event-listing.js +1 -0
  71. package/app/components/map/drawer/position-listing.js +1 -0
  72. package/app/components/positions-replay.js +1 -0
  73. package/app/components/sensor/panel-header.js +1 -0
  74. package/app/helpers/get-fleet-ops-option-label.js +1 -0
  75. package/app/routes/management/vehicles/index/details/positions.js +1 -0
  76. package/app/templates/management/vehicles/index/details/positions.js +1 -0
  77. package/composer.json +1 -1
  78. package/extension.json +1 -1
  79. package/package.json +4 -4
  80. package/server/config/telematics.php +111 -0
  81. package/server/migrations/2025_10_27_000001_add_telematics_integration_fields.php +70 -0
  82. package/server/migrations/2025_10_27_171322_fix_device_column_names.php +107 -0
  83. package/server/migrations/2025_10_27_203023_add_company_uuid_to_device_events_table.php +28 -0
  84. package/server/src/Console/Commands/ReplayVehicleLocations.php +225 -0
  85. package/server/src/Contracts/TelematicProviderDescriptor.php +72 -0
  86. package/server/src/Contracts/TelematicProviderInterface.php +119 -0
  87. package/server/src/Exceptions/TelematicProviderException.php +14 -0
  88. package/server/src/Exceptions/TelematicRateLimitExceededException.php +12 -0
  89. package/server/src/Http/Controllers/Api/v1/DriverController.php +24 -14
  90. package/server/src/Http/Controllers/Api/v1/VehicleController.php +27 -7
  91. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +22 -0
  92. package/server/src/Http/Controllers/Internal/v1/PositionController.php +240 -0
  93. package/server/src/Http/Controllers/Internal/v1/SensorController.php +11 -0
  94. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +141 -0
  95. package/server/src/Http/Controllers/Internal/v1/TelematicWebhookController.php +170 -0
  96. package/server/src/Http/Filter/DeviceEventFilter.php +68 -0
  97. package/server/src/Http/Filter/PositionFilter.php +35 -0
  98. package/server/src/Http/Resources/v1/Position.php +44 -0
  99. package/server/src/Jobs/ReplayPositions.php +64 -0
  100. package/server/src/Jobs/SendPositionReplay.php +65 -0
  101. package/server/src/Jobs/SyncTelematicDevicesJob.php +106 -0
  102. package/server/src/Jobs/TestTelematicConnectionJob.php +102 -0
  103. package/server/src/Models/Device.php +72 -10
  104. package/server/src/Models/DeviceEvent.php +7 -0
  105. package/server/src/Models/Driver.php +28 -1
  106. package/server/src/Models/Payload.php +0 -1
  107. package/server/src/Models/Place.php +4 -1
  108. package/server/src/Models/Position.php +17 -17
  109. package/server/src/Models/Sensor.php +78 -13
  110. package/server/src/Models/Telematic.php +116 -6
  111. package/server/src/Models/Vehicle.php +8 -11
  112. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  113. package/server/src/Support/Telematics/Providers/AbstractProvider.php +151 -0
  114. package/server/src/Support/Telematics/Providers/FlespiProvider.php +182 -0
  115. package/server/src/Support/Telematics/Providers/GeotabProvider.php +181 -0
  116. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +177 -0
  117. package/server/src/Support/Telematics/TelematicProviderRegistry.php +147 -0
  118. package/server/src/Support/Telematics/TelematicService.php +223 -0
  119. package/server/src/Support/Utils.php +1 -1
  120. package/server/src/routes.php +12 -1
@@ -0,0 +1,182 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Telematics\Providers;
4
+
5
+ /**
6
+ * Class FlespiProvider.
7
+ *
8
+ * Flespi telematics provider implementation.
9
+ * https://flespi.com/
10
+ */
11
+ class FlespiProvider extends AbstractProvider
12
+ {
13
+ protected string $baseUrl = 'https://flespi.io/gw';
14
+ protected int $requestsPerMinute = 100;
15
+
16
+ protected function prepareAuthentication(): void
17
+ {
18
+ $this->headers = [
19
+ 'Authorization' => 'FlespiToken ' . $this->credentials['token'],
20
+ 'Accept' => 'application/json',
21
+ ];
22
+ }
23
+
24
+ public function testConnection(array $credentials): array
25
+ {
26
+ try {
27
+ $this->credentials = $credentials;
28
+ $this->prepareAuthentication();
29
+
30
+ // Test by fetching channels
31
+ $response = $this->request('GET', '/channels/all');
32
+
33
+ return [
34
+ 'success' => true,
35
+ 'message' => 'Connection successful',
36
+ 'metadata' => [
37
+ 'channels_count' => count($response['result'] ?? []),
38
+ ],
39
+ ];
40
+ } catch (\Exception $e) {
41
+ return [
42
+ 'success' => false,
43
+ 'message' => $e->getMessage(),
44
+ 'metadata' => [],
45
+ ];
46
+ }
47
+ }
48
+
49
+ public function fetchDevices(array $options = []): array
50
+ {
51
+ $limit = $options['limit'] ?? 100;
52
+ $cursor = $options['cursor'] ?? null;
53
+
54
+ $params = ['count' => $limit];
55
+ if ($cursor) {
56
+ $params['offset'] = $cursor;
57
+ }
58
+
59
+ $response = $this->request('GET', '/devices/all', $params);
60
+
61
+ $devices = $response['result'] ?? [];
62
+ $nextCursor = count($devices) >= $limit ? ((int) ($cursor ?? 0) + $limit) : null;
63
+
64
+ return [
65
+ 'devices' => $devices,
66
+ 'next_cursor' => $nextCursor,
67
+ 'has_more' => $nextCursor !== null,
68
+ ];
69
+ }
70
+
71
+ public function fetchDeviceDetails(string $externalId): array
72
+ {
73
+ $response = $this->request('GET', "/devices/{$externalId}");
74
+
75
+ return $response['result'][0] ?? [];
76
+ }
77
+
78
+ public function normalizeDevice(array $payload): array
79
+ {
80
+ return [
81
+ 'external_id' => $payload['id'],
82
+ 'device_name' => $payload['name'] ?? 'Unknown Device',
83
+ 'device_provider' => 'flespi',
84
+ 'device_model' => $payload['device_type_id'] ?? null,
85
+ 'imei' => $payload['configuration']['ident'] ?? null,
86
+ 'phone' => $payload['configuration']['phone'] ?? null,
87
+ 'status' => isset($payload['telemetry']) ? 'active' : 'inactive',
88
+ 'location' => [
89
+ 'lat' => $payload['telemetry']['position.latitude'] ?? null,
90
+ 'lng' => $payload['telemetry']['position.longitude'] ?? null,
91
+ ],
92
+ 'meta' => $payload,
93
+ ];
94
+ }
95
+
96
+ public function normalizeEvent(array $payload): array
97
+ {
98
+ return [
99
+ 'external_id' => $payload['id'] ?? null,
100
+ 'event_type' => $payload['event.enum'] ?? 'telemetry_update',
101
+ 'occurred_at' => isset($payload['timestamp']) ? date('Y-m-d H:i:s', $payload['timestamp']) : now(),
102
+ 'location' => [
103
+ 'lat' => $payload['position.latitude'] ?? null,
104
+ 'lng' => $payload['position.longitude'] ?? null,
105
+ ],
106
+ 'meta' => $payload,
107
+ ];
108
+ }
109
+
110
+ public function normalizeSensor(array $payload): array
111
+ {
112
+ return [
113
+ 'sensor_type' => $payload['sensor_type'] ?? 'generic',
114
+ 'value' => $payload['value'] ?? null,
115
+ 'unit' => $payload['unit'] ?? null,
116
+ 'recorded_at' => isset($payload['timestamp']) ? date('Y-m-d H:i:s', $payload['timestamp']) : now(),
117
+ 'meta' => $payload,
118
+ ];
119
+ }
120
+
121
+ public function validateWebhookSignature(string $payload, string $signature, array $credentials): bool
122
+ {
123
+ if (!isset($credentials['webhook_secret'])) {
124
+ return true; // No secret configured, skip validation
125
+ }
126
+
127
+ $expectedSignature = hash_hmac('sha256', $payload, $credentials['webhook_secret']);
128
+
129
+ return hash_equals($expectedSignature, $signature);
130
+ }
131
+
132
+ public function processWebhook(array $payload, array $headers = []): array
133
+ {
134
+ $devices = [];
135
+ $events = [];
136
+
137
+ // Flespi sends array of messages
138
+ foreach ($payload as $message) {
139
+ if (isset($message['device.id'])) {
140
+ $devices[] = $this->normalizeDevice([
141
+ 'id' => $message['device.id'],
142
+ 'name' => $message['device.name'] ?? null,
143
+ 'telemetry' => $message,
144
+ ]);
145
+
146
+ $events[] = $this->normalizeEvent($message);
147
+ }
148
+ }
149
+
150
+ return [
151
+ 'devices' => $devices,
152
+ 'events' => $events,
153
+ 'sensors' => [],
154
+ ];
155
+ }
156
+
157
+ public function getCredentialSchema(): array
158
+ {
159
+ return [
160
+ [
161
+ 'name' => 'token',
162
+ 'label' => 'Flespi Token',
163
+ 'type' => 'password',
164
+ 'placeholder' => 'Enter your Flespi API token',
165
+ 'required' => true,
166
+ 'validation' => 'required|string|min:20',
167
+ ],
168
+ [
169
+ 'name' => 'webhook_secret',
170
+ 'label' => 'Webhook Secret (Optional)',
171
+ 'type' => 'password',
172
+ 'placeholder' => 'Enter webhook secret for signature validation',
173
+ 'required' => false,
174
+ ],
175
+ ];
176
+ }
177
+
178
+ public function supportsWebhooks(): bool
179
+ {
180
+ return true;
181
+ }
182
+ }
@@ -0,0 +1,181 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Telematics\Providers;
4
+
5
+ use Illuminate\Support\Facades\Http;
6
+
7
+ /**
8
+ * Class GeotabProvider.
9
+ *
10
+ * Geotab telematics provider implementation.
11
+ * https://www.geotab.com/
12
+ */
13
+ class GeotabProvider extends AbstractProvider
14
+ {
15
+ protected string $baseUrl = 'https://my.geotab.com/apiv1';
16
+ protected int $requestsPerMinute = 50;
17
+ protected ?string $sessionId = null;
18
+
19
+ protected function prepareAuthentication(): void
20
+ {
21
+ // Geotab uses session-based authentication
22
+ if (!$this->sessionId) {
23
+ $this->authenticate();
24
+ }
25
+
26
+ $this->headers = [
27
+ 'Content-Type' => 'application/json',
28
+ ];
29
+ }
30
+
31
+ /**
32
+ * Authenticate with Geotab and get session ID.
33
+ */
34
+ protected function authenticate(): void
35
+ {
36
+ $response = Http::post($this->baseUrl, [
37
+ 'method' => 'Authenticate',
38
+ 'params' => [
39
+ 'database' => $this->credentials['database'],
40
+ 'userName' => $this->credentials['username'],
41
+ 'password' => $this->credentials['password'],
42
+ ],
43
+ ])->json();
44
+
45
+ if (isset($response['result']['credentials']['sessionId'])) {
46
+ $this->sessionId = $response['result']['credentials']['sessionId'];
47
+ } else {
48
+ throw new \Exception('Geotab authentication failed');
49
+ }
50
+ }
51
+
52
+ public function testConnection(array $credentials): array
53
+ {
54
+ try {
55
+ $this->credentials = $credentials;
56
+ $this->authenticate();
57
+
58
+ return [
59
+ 'success' => true,
60
+ 'message' => 'Connection successful',
61
+ 'metadata' => [
62
+ 'session_id' => substr($this->sessionId, 0, 10) . '...',
63
+ ],
64
+ ];
65
+ } catch (\Exception $e) {
66
+ return [
67
+ 'success' => false,
68
+ 'message' => $e->getMessage(),
69
+ 'metadata' => [],
70
+ ];
71
+ }
72
+ }
73
+
74
+ public function fetchDevices(array $options = []): array
75
+ {
76
+ $limit = $options['limit'] ?? 100;
77
+
78
+ $response = Http::post($this->baseUrl, [
79
+ 'method' => 'Get',
80
+ 'params' => [
81
+ 'credentials' => [
82
+ 'database' => $this->credentials['database'],
83
+ 'sessionId' => $this->sessionId,
84
+ ],
85
+ 'typeName' => 'Device',
86
+ 'resultsLimit' => $limit,
87
+ ],
88
+ ])->json();
89
+
90
+ $devices = $response['result'] ?? [];
91
+
92
+ return [
93
+ 'devices' => $devices,
94
+ 'next_cursor' => null,
95
+ 'has_more' => false,
96
+ ];
97
+ }
98
+
99
+ public function fetchDeviceDetails(string $externalId): array
100
+ {
101
+ $response = Http::post($this->baseUrl, [
102
+ 'method' => 'Get',
103
+ 'params' => [
104
+ 'credentials' => [
105
+ 'database' => $this->credentials['database'],
106
+ 'sessionId' => $this->sessionId,
107
+ ],
108
+ 'typeName' => 'Device',
109
+ 'search' => ['id' => $externalId],
110
+ ],
111
+ ])->json();
112
+
113
+ return $response['result'][0] ?? [];
114
+ }
115
+
116
+ public function normalizeDevice(array $payload): array
117
+ {
118
+ return [
119
+ 'external_id' => $payload['id'],
120
+ 'device_name' => $payload['name'] ?? 'Unknown Device',
121
+ 'device_provider' => 'geotab',
122
+ 'device_model' => $payload['deviceType'] ?? null,
123
+ 'imei' => $payload['serialNumber'] ?? null,
124
+ 'vin' => $payload['vehicleIdentificationNumber'] ?? null,
125
+ 'status' => 'active',
126
+ 'meta' => $payload,
127
+ ];
128
+ }
129
+
130
+ public function normalizeEvent(array $payload): array
131
+ {
132
+ return [
133
+ 'external_id' => $payload['id'] ?? null,
134
+ 'event_type' => $payload['type'] ?? 'status_data',
135
+ 'occurred_at' => $payload['dateTime'] ?? now(),
136
+ 'meta' => $payload,
137
+ ];
138
+ }
139
+
140
+ public function normalizeSensor(array $payload): array
141
+ {
142
+ return [
143
+ 'sensor_type' => $payload['diagnosticType'] ?? 'generic',
144
+ 'value' => $payload['data'] ?? null,
145
+ 'recorded_at' => $payload['dateTime'] ?? now(),
146
+ 'meta' => $payload,
147
+ ];
148
+ }
149
+
150
+ public function getCredentialSchema(): array
151
+ {
152
+ return [
153
+ [
154
+ 'name' => 'database',
155
+ 'label' => 'Database Name',
156
+ 'type' => 'text',
157
+ 'placeholder' => 'Enter your Geotab database name',
158
+ 'required' => true,
159
+ ],
160
+ [
161
+ 'name' => 'username',
162
+ 'label' => 'Username',
163
+ 'type' => 'text',
164
+ 'placeholder' => 'Enter your Geotab username',
165
+ 'required' => true,
166
+ ],
167
+ [
168
+ 'name' => 'password',
169
+ 'label' => 'Password',
170
+ 'type' => 'password',
171
+ 'placeholder' => 'Enter your Geotab password',
172
+ 'required' => true,
173
+ ],
174
+ ];
175
+ }
176
+
177
+ public function supportsWebhooks(): bool
178
+ {
179
+ return false;
180
+ }
181
+ }
@@ -0,0 +1,177 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Telematics\Providers;
4
+
5
+ /**
6
+ * Class SamsaraProvider.
7
+ *
8
+ * Samsara telematics provider implementation.
9
+ * https://www.samsara.com/
10
+ */
11
+ class SamsaraProvider extends AbstractProvider
12
+ {
13
+ protected string $baseUrl = 'https://api.samsara.com';
14
+ protected int $requestsPerMinute = 60;
15
+
16
+ protected function prepareAuthentication(): void
17
+ {
18
+ $this->headers = [
19
+ 'Authorization' => 'Bearer ' . $this->credentials['api_token'],
20
+ 'Accept' => 'application/json',
21
+ ];
22
+ }
23
+
24
+ public function testConnection(array $credentials): array
25
+ {
26
+ try {
27
+ $this->credentials = $credentials;
28
+ $this->prepareAuthentication();
29
+
30
+ // Test by fetching user info
31
+ $response = $this->request('GET', '/fleet/users');
32
+
33
+ return [
34
+ 'success' => true,
35
+ 'message' => 'Connection successful',
36
+ 'metadata' => [
37
+ 'users_count' => count($response['data'] ?? []),
38
+ ],
39
+ ];
40
+ } catch (\Exception $e) {
41
+ return [
42
+ 'success' => false,
43
+ 'message' => $e->getMessage(),
44
+ 'metadata' => [],
45
+ ];
46
+ }
47
+ }
48
+
49
+ public function fetchDevices(array $options = []): array
50
+ {
51
+ $limit = $options['limit'] ?? 100;
52
+ $cursor = $options['cursor'] ?? null;
53
+
54
+ $params = ['limit' => $limit];
55
+ if ($cursor) {
56
+ $params['after'] = $cursor;
57
+ }
58
+
59
+ $response = $this->request('GET', '/fleet/vehicles', $params);
60
+
61
+ $devices = $response['data'] ?? [];
62
+ $nextCursor = $response['pagination']['endCursor'] ?? null;
63
+ $hasMore = $response['pagination']['hasNextPage'] ?? false;
64
+
65
+ return [
66
+ 'devices' => $devices,
67
+ 'next_cursor' => $hasMore ? $nextCursor : null,
68
+ 'has_more' => $hasMore,
69
+ ];
70
+ }
71
+
72
+ public function fetchDeviceDetails(string $externalId): array
73
+ {
74
+ $response = $this->request('GET', "/fleet/vehicles/{$externalId}");
75
+
76
+ return $response['data'] ?? [];
77
+ }
78
+
79
+ public function normalizeDevice(array $payload): array
80
+ {
81
+ return [
82
+ 'external_id' => $payload['id'],
83
+ 'device_name' => $payload['name'] ?? 'Unknown Device',
84
+ 'device_provider' => 'samsara',
85
+ 'device_model' => $payload['make'] ?? null,
86
+ 'vin' => $payload['vin'] ?? null,
87
+ 'license_plate' => $payload['licensePlate'] ?? null,
88
+ 'status' => 'active',
89
+ 'meta' => $payload,
90
+ ];
91
+ }
92
+
93
+ public function normalizeEvent(array $payload): array
94
+ {
95
+ return [
96
+ 'external_id' => $payload['id'] ?? null,
97
+ 'event_type' => $payload['eventType'] ?? 'vehicle_update',
98
+ 'occurred_at' => $payload['time'] ?? now(),
99
+ 'location' => [
100
+ 'lat' => $payload['location']['latitude'] ?? null,
101
+ 'lng' => $payload['location']['longitude'] ?? null,
102
+ ],
103
+ 'meta' => $payload,
104
+ ];
105
+ }
106
+
107
+ public function normalizeSensor(array $payload): array
108
+ {
109
+ return [
110
+ 'sensor_type' => $payload['sensorType'] ?? 'generic',
111
+ 'value' => $payload['value'] ?? null,
112
+ 'unit' => $payload['unit'] ?? null,
113
+ 'recorded_at' => $payload['time'] ?? now(),
114
+ 'meta' => $payload,
115
+ ];
116
+ }
117
+
118
+ public function validateWebhookSignature(string $payload, string $signature, array $credentials): bool
119
+ {
120
+ if (!isset($credentials['webhook_secret'])) {
121
+ return true;
122
+ }
123
+
124
+ $expectedSignature = hash_hmac('sha256', $payload, $credentials['webhook_secret']);
125
+
126
+ return hash_equals($expectedSignature, $signature);
127
+ }
128
+
129
+ public function processWebhook(array $payload, array $headers = []): array
130
+ {
131
+ $devices = [];
132
+ $events = [];
133
+
134
+ // Samsara webhook structure
135
+ if (isset($payload['data'])) {
136
+ foreach ($payload['data'] as $item) {
137
+ if (isset($item['vehicle'])) {
138
+ $devices[] = $this->normalizeDevice($item['vehicle']);
139
+ }
140
+
141
+ $events[] = $this->normalizeEvent($item);
142
+ }
143
+ }
144
+
145
+ return [
146
+ 'devices' => $devices,
147
+ 'events' => $events,
148
+ 'sensors' => [],
149
+ ];
150
+ }
151
+
152
+ public function getCredentialSchema(): array
153
+ {
154
+ return [
155
+ [
156
+ 'name' => 'api_token',
157
+ 'label' => 'API Token',
158
+ 'type' => 'password',
159
+ 'placeholder' => 'Enter your Samsara API token',
160
+ 'required' => true,
161
+ 'validation' => 'required|string|min:20',
162
+ ],
163
+ [
164
+ 'name' => 'webhook_secret',
165
+ 'label' => 'Webhook Secret (Optional)',
166
+ 'type' => 'password',
167
+ 'placeholder' => 'Enter webhook secret for signature validation',
168
+ 'required' => false,
169
+ ],
170
+ ];
171
+ }
172
+
173
+ public function supportsWebhooks(): bool
174
+ {
175
+ return true;
176
+ }
177
+ }
@@ -0,0 +1,147 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support\Telematics;
4
+
5
+ use Fleetbase\FleetOps\Contracts\TelematicProviderDescriptor;
6
+ use Fleetbase\FleetOps\Contracts\TelematicProviderInterface;
7
+ use Illuminate\Support\Collection;
8
+
9
+ /**
10
+ * Class ProviderRegistry.
11
+ *
12
+ * Central registry for all telematics providers.
13
+ * Manages provider discovery, registration, and instantiation.
14
+ */
15
+ class TelematicProviderRegistry
16
+ {
17
+ /**
18
+ * @var Collection<TelematicProviderDescriptor>
19
+ */
20
+ protected Collection $providers;
21
+
22
+ /**
23
+ * Create a new ProviderRegistry instance.
24
+ */
25
+ public function __construct()
26
+ {
27
+ $this->providers = collect();
28
+ $this->loadNativeProviders();
29
+ }
30
+
31
+ /**
32
+ * Load native providers from configuration.
33
+ */
34
+ protected function loadNativeProviders(): void
35
+ {
36
+ $config = config('telematics.providers', []);
37
+
38
+ foreach ($config as $providerConfig) {
39
+ $descriptor = new TelematicProviderDescriptor($providerConfig);
40
+ $this->register($descriptor);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Register a provider.
46
+ */
47
+ public function register(TelematicProviderDescriptor $descriptor): void
48
+ {
49
+ $this->providers->put($descriptor->key, $descriptor);
50
+ }
51
+
52
+ /**
53
+ * Get all registered providers.
54
+ *
55
+ * @return Collection<TelematicProviderDescriptor>
56
+ */
57
+ public function all(): Collection
58
+ {
59
+ return $this->providers;
60
+ }
61
+
62
+ /**
63
+ * Find a provider by key.
64
+ */
65
+ public function findByKey(string $key): ?TelematicProviderDescriptor
66
+ {
67
+ return $this->providers->get($key);
68
+ }
69
+
70
+ /**
71
+ * Check if a provider exists.
72
+ */
73
+ public function has(string $key): bool
74
+ {
75
+ return $this->providers->has($key);
76
+ }
77
+
78
+ /**
79
+ * Resolve a provider instance by key.
80
+ *
81
+ * @throws \InvalidArgumentException
82
+ */
83
+ public function resolve(string $key): TelematicProviderInterface
84
+ {
85
+ $descriptor = $this->findByKey($key);
86
+
87
+ if (!$descriptor) {
88
+ throw new \InvalidArgumentException("Provider '{$key}' not found in registry.");
89
+ }
90
+
91
+ if (!$descriptor->driverClass) {
92
+ throw new \InvalidArgumentException("Provider '{$key}' does not have a driver class.");
93
+ }
94
+
95
+ if (!class_exists($descriptor->driverClass)) {
96
+ throw new \InvalidArgumentException("Provider driver class '{$descriptor->driverClass}' does not exist.");
97
+ }
98
+
99
+ $provider = app($descriptor->driverClass);
100
+
101
+ if (!$provider instanceof TelematicProviderInterface) {
102
+ throw new \InvalidArgumentException('Provider driver class must implement TelematicProviderInterface.');
103
+ }
104
+
105
+ return $provider;
106
+ }
107
+
108
+ /**
109
+ * Get providers that support webhooks.
110
+ *
111
+ * @return Collection<TelematicProviderDescriptor>
112
+ */
113
+ public function getWebhookProviders(): Collection
114
+ {
115
+ return $this->providers->filter(fn ($p) => $p->supportsWebhooks);
116
+ }
117
+
118
+ /**
119
+ * Get providers that support discovery.
120
+ *
121
+ * @return Collection<TelematicProviderDescriptor>
122
+ */
123
+ public function getDiscoveryProviders(): Collection
124
+ {
125
+ return $this->providers->filter(fn ($p) => $p->supportsDiscovery);
126
+ }
127
+
128
+ /**
129
+ * Get native providers only.
130
+ *
131
+ * @return Collection<TelematicProviderDescriptor>
132
+ */
133
+ public function getNativeProviders(): Collection
134
+ {
135
+ return $this->providers->filter(fn ($p) => $p->type === 'native');
136
+ }
137
+
138
+ /**
139
+ * Get custom providers only.
140
+ *
141
+ * @return Collection<TelematicProviderDescriptor>
142
+ */
143
+ public function getCustomProviders(): Collection
144
+ {
145
+ return $this->providers->filter(fn ($p) => $p->type === 'custom');
146
+ }
147
+ }