@de./sdk 1.0.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4391 -0
  3. package/dist/allend/Access.d.ts +15 -0
  4. package/dist/allend/Access.js +164 -0
  5. package/dist/allend/Access.js.map +1 -0
  6. package/dist/allend/DAuth/index.d.ts +20 -0
  7. package/dist/allend/DAuth/index.js +92 -0
  8. package/dist/allend/DAuth/index.js.map +1 -0
  9. package/dist/allend/DClient/Client.d.ts +10 -0
  10. package/dist/allend/DClient/Client.js +144 -0
  11. package/dist/allend/DClient/Client.js.map +1 -0
  12. package/dist/allend/DClient/Event.d.ts +18 -0
  13. package/dist/allend/DClient/Event.js +100 -0
  14. package/dist/allend/DClient/Event.js.map +1 -0
  15. package/dist/allend/DClient/Order.d.ts +49 -0
  16. package/dist/allend/DClient/Order.js +601 -0
  17. package/dist/allend/DClient/Order.js.map +1 -0
  18. package/dist/allend/IoTClient.d.ts +29 -0
  19. package/dist/allend/IoTClient.js +59 -0
  20. package/dist/allend/IoTClient.js.map +1 -0
  21. package/dist/allend/MSI/Controls.d.ts +256 -0
  22. package/dist/allend/MSI/Controls.js +763 -0
  23. package/dist/allend/MSI/Controls.js.map +1 -0
  24. package/dist/allend/MSI/Handles.d.ts +88 -0
  25. package/dist/allend/MSI/Handles.js +445 -0
  26. package/dist/allend/MSI/Handles.js.map +1 -0
  27. package/dist/allend/MSI/Plugins.d.ts +22 -0
  28. package/dist/allend/MSI/Plugins.js +38 -0
  29. package/dist/allend/MSI/Plugins.js.map +1 -0
  30. package/dist/allend/MSI/index.d.ts +55 -0
  31. package/dist/allend/MSI/index.js +155 -0
  32. package/dist/allend/MSI/index.js.map +1 -0
  33. package/dist/allend/index.d.ts +12 -0
  34. package/dist/allend/index.js +18 -0
  35. package/dist/allend/index.js.map +1 -0
  36. package/dist/backend/Auth.d.ts +38 -0
  37. package/dist/backend/Auth.js +286 -0
  38. package/dist/backend/Auth.js.map +1 -0
  39. package/dist/baseUrl.d.ts +12 -0
  40. package/dist/baseUrl.js +16 -0
  41. package/dist/baseUrl.js.map +1 -0
  42. package/dist/bundle.js +1 -0
  43. package/dist/index.d.ts +13 -0
  44. package/dist/index.js +20 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/types/access.d.ts +8 -0
  47. package/dist/types/auth.d.ts +27 -0
  48. package/dist/types/index.d.ts +281 -0
  49. package/dist/utils/index.d.ts +5 -0
  50. package/dist/utils/index.js +8 -0
  51. package/dist/utils/index.js.map +1 -0
  52. package/dist/utils/stream.d.ts +20 -0
  53. package/dist/utils/stream.js +63 -0
  54. package/dist/utils/stream.js.map +1 -0
  55. package/package.json +66 -0
  56. package/src/allend/Access.ts +78 -0
  57. package/src/allend/DAuth/index.ts +39 -0
  58. package/src/allend/DClient/Client.ts +76 -0
  59. package/src/allend/DClient/Event.ts +76 -0
  60. package/src/allend/DClient/Order.ts +452 -0
  61. package/src/allend/IoTClient.ts +68 -0
  62. package/src/allend/MSI/Controls.ts +760 -0
  63. package/src/allend/MSI/Handles.ts +387 -0
  64. package/src/allend/MSI/Plugins.ts +52 -0
  65. package/src/allend/MSI/index.ts +160 -0
  66. package/src/allend/index.ts +18 -0
  67. package/src/backend/Auth.ts +201 -0
  68. package/src/baseUrl.ts +13 -0
  69. package/src/index.ts +19 -0
  70. package/src/types/access.d.ts +8 -0
  71. package/src/types/auth.d.ts +27 -0
  72. package/src/types/index.d.ts +281 -0
  73. package/src/utils/index.ts +4 -0
  74. package/src/utils/stream.ts +68 -0
package/README.md ADDED
@@ -0,0 +1,4391 @@
1
+ # De. Platform Developer Documentation
2
+
3
+ ## Overview
4
+
5
+ De. (Dedot) is a supply chain coordination platform that connects all the moving pieces of delivery—logistics companies, commerce platforms, developers, and end users—through one unified system. This documentation covers the complete SDK for integrating De.'s mapping, routing, and logistics coordination capabilities into your applications.
6
+
7
+ **Think of De. as the operating system for logistics** - similar to how Stripe revolutionized payments or how electrical wiring connects all appliances in a house, De. provides the invisible backbone that makes delivery and logistics work seamlessly.
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Getting Started](#getting-started)
12
+ 2. [Architecture Overview](#architecture-overview)
13
+ 3. [Installation & Setup](#installation--setup)
14
+ 4. [Core Components](#core-components)
15
+ 5. [API Reference](#api-reference)
16
+ 6. [Code Examples](#code-examples)
17
+ 7. [Plugin Development](#plugin-development)
18
+ 8. [Advanced Usage Patterns](#advanced-usage-patterns)
19
+ 9. [Error Handling](#error-handling)
20
+ 10. [Performance Optimization](#performance-optimization)
21
+ 11. [Testing](#testing)
22
+
23
+ ---
24
+
25
+ ## Getting Started
26
+
27
+ The De. SDK provides three main modules:
28
+
29
+ - **MSI (Map System Interface)**: Complete map integration with iframe-based communication for visual delivery tracking
30
+ - **DClient**: Client-side operations for orders, events, and customer management
31
+ - **Access**: API authentication and HTTP request management
32
+
33
+ ### Who Uses De.?
34
+
35
+ **Logistics Service Providers (LSP)**
36
+ - Carriers, 3PLs, 4PLs, Forwarders, Brokers
37
+ - Get better coordination tools, more business opportunities
38
+ - Real-time fleet tracking and route optimization
39
+
40
+ **Commerce Service Providers (CSP)**
41
+ - Retailers, Manufacturers, E-commerce platforms
42
+ - Seamless delivery integration for customers
43
+ - Multi-carrier coordination without building from scratch
44
+
45
+ **Developers (DEV)**
46
+ - Build delivery features into applications
47
+ - Access comprehensive logistics network
48
+ - Extensible plugin system for custom functionality
49
+
50
+ **End User Service (EUS)**
51
+ - Customers tracking their deliveries
52
+ - Real-time visibility into package location
53
+ - Delivery preference management
54
+
55
+ ### Prerequisites
56
+
57
+ - Node.js 14+ or modern browser environment
58
+ - TypeScript 4.0+ (recommended)
59
+ - Valid De. workspace and access token
60
+ - Basic understanding of Promises and async/await
61
+
62
+ ---
63
+
64
+ ## Architecture Overview
65
+
66
+ ```
67
+ ┌─────────────────────────────────────────────────────────┐
68
+ │ Your Application │
69
+ │ (E-commerce, Logistics Dashboard, Mobile App, etc.) │
70
+ └────────────────────┬────────────────────────────────────┘
71
+ │
72
+ ┌────────────┴───────────────┐
73
+ │ De. SDK │
74
+ ├────────────────────────────┤
75
+ │ │
76
+ │ ┌──────────────────────┐ │
77
+ │ │ MSI (Map System) │ │
78
+ │ │ ┌────────────────┐ │ │
79
+ │ │ │ Controls │ │ │ ← Promise-based direct operations
80
+ │ │ ├────────────────┤ │ │
81
+ │ │ │ Handles │ │ │ ← Stream-based high-level API
82
+ │ │ ├────────────────┤ │ │
83
+ │ │ │ Plugins │ │ │ ← Extensibility system
84
+ │ │ └────────────────┘ │ │
85
+ │ └──────────────────────┘ │
86
+ │ │
87
+ │ ┌──────────────────────┐ │
88
+ │ │ DClient │ │
89
+ │ │ - Order │ │ ← Order management
90
+ │ │ - Event │ │ ← Event tracking
91
+ │ │ - Client │ │ ← Customer operations
92
+ │ └──────────────────────┘ │
93
+ │ │
94
+ │ ┌──────────────────────┐ │
95
+ │ │ Access │ │ ← API authentication
96
+ │ └──────────────────────┘ │
97
+ │ │
98
+ │ ┌──────────────────────┐ │
99
+ │ │ Utils │ │ ← Utility functions
100
+ │ └──────────────────────┘ │
101
+ └────────────────────────────┘
102
+ │
103
+ ▼
104
+ ┌──────────────────┐
105
+ │ Iframe (MSI) │
106
+ │ Map Engine │ ← Isolated rendering environment
107
+ │ (MapBox/Google) │
108
+ └──────────────────┘
109
+ │
110
+ ▼
111
+ ┌──────────────────┐
112
+ │ De. Platform │
113
+ │ - Logistics API │
114
+ │ - Route Engine │
115
+ │ - Coordination │
116
+ └──────────────────┘
117
+ ```
118
+
119
+ ### Component Responsibilities
120
+
121
+ | Component | Purpose | Use Cases |
122
+ |-----------|---------|-----------|
123
+ | **MSI** | Map visualization & interaction | Delivery tracking, route planning, location selection |
124
+ | **Controls** | Direct map manipulation (Promise-based) | Set routes, update markers, control navigation |
125
+ | **Handles** | High-level workflows (Stream-based) | Real-time tracking, fleet management, live updates |
126
+ | **Plugins** | Custom extensions | Analytics, custom markers, third-party integrations |
127
+ | **DClient** | Order & customer management | Create orders, track status, manage clients |
128
+ | **Access** | API communication | Authenticated requests, token management |
129
+ | **Utils** | Helper functions | Distance calculation, formatting, validation |
130
+
131
+ ### Communication Flow
132
+
133
+ ```
134
+ Your App → Access → De. API → Backend Services
135
+ ↓
136
+ Your App → MSI → Iframe → Map Engine
137
+ ↓ ↓
138
+ Your App ← Controls/Handles ← Map Events
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Installation & Setup
144
+
145
+ ### NPM Installation
146
+
147
+ ```bash
148
+ npm install @de./sdk
149
+ ```
150
+
151
+ ### CDN Installation
152
+
153
+ ```html
154
+ <script src="https://cdn.dedot.io/sdk/v2/dedot.min.js"></script>
155
+ ```
156
+
157
+ ### Basic Initialization
158
+
159
+ ```typescript
160
+ import De from '@de./sdk';
161
+
162
+ // Initialize MSI for map visualization
163
+ const msi = new De.MSI({
164
+ element: 'map-container', // DOM element ID
165
+ accessToken: 'your-access-token',
166
+ env: 'prod', // or 'dev'
167
+ version: 1 // API version (optional)
168
+ });
169
+
170
+ // Load and get access to all components
171
+ const { controls, handles, plugins } = await msi.load();
172
+
173
+ console.log('De. SDK initialized successfully!');
174
+ ```
175
+
176
+ ### Full Stack Setup
177
+
178
+ ```typescript
179
+ import De from '@de./sdk';
180
+
181
+ // 1. Initialize Access for API operations
182
+ const access = new De.Access({
183
+ workspace: 'your-workspace-id',
184
+ accessToken: 'your-access-token',
185
+ remoteOrigin: window.location.origin,
186
+ env: 'prod', // 'dev' | 'prod'
187
+ version: 1 // API version (optional)
188
+ });
189
+
190
+ // 2. Initialize MSI for map interface
191
+ const msi = new De.MSI({
192
+ element: 'map-container',
193
+ accessToken: 'your-access-token',
194
+ env: 'prod',
195
+ version: 1
196
+ });
197
+
198
+ // 3. Load MSI components
199
+ const { controls, handles, plugins } = await msi.load();
200
+
201
+ // 4. Initialize DClient for order management
202
+ const { Client, Order, Event } = De.DClient;
203
+
204
+ // Now you have full access to:
205
+ // - controls: Direct map operations
206
+ // - handles: Stream-based high-level API
207
+ // - plugins: Custom extensions
208
+ // - access: Authenticated API calls
209
+ // - DClient: Order/customer management
210
+ ```
211
+
212
+ ### Configuration Options
213
+
214
+ ```typescript
215
+ interface MapOptions {
216
+ element: string; // Required: DOM element ID
217
+ accessToken: string; // Required: Authentication token
218
+ env?: 'dev' | 'prod'; // Environment (default: 'prod')
219
+ version?: number; // API version (default: 1)
220
+ }
221
+
222
+ interface AccessOptions {
223
+ workspace: string; // Required: Workspace identifier
224
+ accessToken: string; // Required: Authentication token
225
+ remoteOrigin?: string; // Optional: Origin for CORS
226
+ env?: 'dev' | 'prod'; // Environment (default: 'prod')
227
+ version?: number; // API version (default: 1)
228
+ }
229
+ ```
230
+
231
+ ### Quick Start Example
232
+
233
+ ```typescript
234
+ // Initialize
235
+ const msi = new De.MSI({
236
+ element: 'map-container',
237
+ accessToken: 'your-token',
238
+ env: 'prod'
239
+ });
240
+
241
+ const { controls, handles } = await msi.load();
242
+
243
+ // Set delivery route
244
+ await handles.pickupPoint(
245
+ { lng: -74.0060, lat: 40.7128 },
246
+ { label: 'Warehouse A', duration: 5, unit: 'min' }
247
+ );
248
+
249
+ await handles.dropoffPoint(
250
+ { lng: -73.9855, lat: 40.7580 },
251
+ { label: 'Customer' }
252
+ );
253
+
254
+ // Track delivery vehicle
255
+ const vehicleStream = handles.nearby([{
256
+ id: 'vehicle-1',
257
+ type: 'car',
258
+ status: 'ACTIVE',
259
+ grade: '2H',
260
+ currentLocation: { lng: -74.0050, lat: 40.7200 }
261
+ }]);
262
+
263
+ console.log('Delivery tracking active!');
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Core Components
269
+
270
+ ### 1. MSI (Map System Interface)
271
+
272
+ The main orchestrator that embeds the map interface via iframe and provides access to all sub-components.
273
+
274
+ #### Constructor
275
+
276
+ ```typescript
277
+ const msi = new De.MSI(options: MapOptions);
278
+ ```
279
+
280
+ #### Key Methods
281
+
282
+ **`load(): Promise<MSIInterface>`**
283
+
284
+ Initializes the iframe, establishes communication, and returns all components.
285
+
286
+ ```typescript
287
+ const { controls, handles, plugins } = await msi.load();
288
+
289
+ // Returns:
290
+ interface MSIInterface {
291
+ controls: Controls; // Direct map operations
292
+ handles: Handles; // Stream-based workflows
293
+ plugins: Plugins; // Extension system
294
+ }
295
+ ```
296
+
297
+ **`isReady(): boolean`**
298
+
299
+ Check if MSI is loaded and ready for operations.
300
+
301
+ ```typescript
302
+ if (msi.isReady()) {
303
+ await controls.getCurrentLocation();
304
+ }
305
+ ```
306
+
307
+ **`plugin<T>(name: string, fn: Plugin<T>)`**
308
+
309
+ Register custom plugins before loading MSI.
310
+
311
+ ```typescript
312
+ // Register plugin
313
+ msi.plugin('deliveryAnalytics', (hooks, options) => {
314
+ return {
315
+ trackDelivery: (orderId) => {
316
+ // Implementation
317
+ }
318
+ };
319
+ });
320
+
321
+ // Use after loading
322
+ const { plugins } = await msi.load();
323
+ const analytics = plugins.use('deliveryAnalytics');
324
+ await analytics.trackDelivery('order-123');
325
+ ```
326
+
327
+ #### Events
328
+
329
+ ```typescript
330
+ // Listen to MSI lifecycle events
331
+ msi.on('ready', () => {
332
+ console.log('MSI is ready for interaction');
333
+ });
334
+
335
+ msi.on('error', (error: Error) => {
336
+ console.error('MSI error:', error);
337
+ // Handle initialization errors
338
+ });
339
+
340
+ msi.on('loaded', (channel: IOF) => {
341
+ console.log('Iframe loaded and communication established');
342
+ });
343
+ ```
344
+
345
+ #### Best Practices
346
+
347
+ 1. **Always wait for load** before using controls/handles
348
+ 2. **Handle errors** during initialization
349
+ 3. **Register plugins** before calling `load()`
350
+ 4. **Clean up** on component unmount
351
+
352
+ ```typescript
353
+ // React example
354
+ useEffect(() => {
355
+ const msi = new De.MSI(config);
356
+ let api: MSIInterface;
357
+
358
+ msi.on('error', handleError);
359
+
360
+ msi.load()
361
+ .then(loadedApi => {
362
+ api = loadedApi;
363
+ // Use controls, handles, plugins
364
+ })
365
+ .catch(handleError);
366
+
367
+ return () => {
368
+ // Cleanup
369
+ api?.handles.removeListeners();
370
+ };
371
+ }, []);
372
+ ```
373
+
374
+ ---
375
+
376
+ ### 2. Controls
377
+
378
+ Promise-based API for direct map manipulation. All operations return Promises with 12-second timeout protection.
379
+
380
+ #### Location Services
381
+
382
+ **`getCurrentLocation(): Promise<RTLocation | null>`**
383
+
384
+ Get user's current GPS location with accuracy, heading, and speed.
385
+
386
+ ```typescript
387
+ const location = await controls.getCurrentLocation();
388
+ console.log(location);
389
+ // {
390
+ // latitude: 40.7128,
391
+ // longitude: -74.0060,
392
+ // accuracy: 10, // meters
393
+ // heading: 180, // degrees (0-360)
394
+ // speed: 15 // meters/second
395
+ // }
396
+ ```
397
+
398
+ **`pinCurrentLocation(): Promise<Coordinates | null>`**
399
+
400
+ Display user's location on map with a marker.
401
+
402
+ ```typescript
403
+ const coords = await controls.pinCurrentLocation();
404
+ // Returns: [latitude, longitude] or null
405
+ ```
406
+
407
+ **`trackLiveLocation(): Promise<void>`**
408
+
409
+ Start continuous location tracking.
410
+
411
+ ```typescript
412
+ // Configure tracking
413
+ await controls.setLiveLocationOptions({
414
+ accuracy: 'high', // 'high' | 'medium' | 'low'
415
+ updateInterval: 5000, // milliseconds
416
+ showAccuracyCircle: true
417
+ });
418
+
419
+ // Start tracking
420
+ await controls.trackLiveLocation();
421
+
422
+ // Stop when done
423
+ await controls.untrackLiveLocation();
424
+ ```
425
+
426
+ #### Map Styling
427
+
428
+ **`setMapStyle(style: MapLayerStyle): Promise<void>`**
429
+
430
+ Change map appearance.
431
+
432
+ ```typescript
433
+ await controls.setMapStyle('dark');
434
+ // Options: 'streets' | 'outdoors' | 'light' | 'dark' | 'satellite'
435
+ ```
436
+
437
+ #### Search & Geocoding
438
+
439
+ **`searchQuery(input: string): Promise<string[]>`**
440
+
441
+ Search for places or addresses.
442
+
443
+ ```typescript
444
+ const suggestions = await controls.searchQuery('coffee shops near me');
445
+ // Returns: ['Starbucks - 123 Main St', 'Dunkin - 456 Park Ave', ...]
446
+ ```
447
+
448
+ **`searchSelect(index: number): Promise<SearchPlace | null>`**
449
+
450
+ Get details of a search result.
451
+
452
+ ```typescript
453
+ const place = await controls.searchSelect(0);
454
+ console.log(place);
455
+ // {
456
+ // name: 'Starbucks',
457
+ // location: { lng: -73.985428, lat: 40.748817 },
458
+ // address: '123 Main St, New York, NY'
459
+ // }
460
+ ```
461
+
462
+ **`resolvePlace(name: string): Promise<Coordinates | null>`**
463
+
464
+ Get coordinates from place name (geocoding).
465
+
466
+ ```typescript
467
+ const coords = await controls.resolvePlace('Empire State Building');
468
+ // Returns: { lng: -73.985428, lat: 40.748817 }
469
+ ```
470
+
471
+ **`resolveCoordinates(coords: Coordinates): Promise<any | null>`**
472
+
473
+ Get place details from coordinates (reverse geocoding).
474
+
475
+ ```typescript
476
+ const place = await controls.resolveCoordinates({ lng: -74.0060, lat: 40.7128 });
477
+ console.log(place);
478
+ // {
479
+ // name: 'Lower Manhattan',
480
+ // address: 'Manhattan, New York, NY',
481
+ // postalCode: '10007',
482
+ // ...
483
+ // }
484
+ ```
485
+
486
+ #### Drag & Pick Location
487
+
488
+ **`enableDragPickLocation(initialLocation?: Coordinates): Promise<void>`**
489
+
490
+ Enable interactive location picker.
491
+
492
+ ```typescript
493
+ // Enable with initial position
494
+ await controls.enableDragPickLocation({ lng: -74.0060, lat: 40.7128 });
495
+
496
+ // Listen for picked location
497
+ controls.on('pick:location', (location) => {
498
+ console.log('User picked:', location.coordinates);
499
+ console.log('Point:', location.point);
500
+ });
501
+
502
+ // Disable when done
503
+ await controls.disableDragPickLocation();
504
+ ```
505
+
506
+ **`setDragPickContent(type: DragPickContentType, content: DragPickContent): Promise<void>`**
507
+
508
+ Customize the drag picker content display.
509
+
510
+ ```typescript
511
+ // Show duration
512
+ await controls.setDragPickContent('duration', {
513
+ time: 15,
514
+ unit: 'min'
515
+ });
516
+
517
+ // Show distance
518
+ await controls.setDragPickContent('distance', {
519
+ distance: 5.2,
520
+ unit: 'km'
521
+ });
522
+
523
+ // Show preloader
524
+ await controls.setDragPickContent('preloader', {
525
+ preloader: true
526
+ });
527
+ ```
528
+
529
+ #### Route Management
530
+
531
+ **`setRoute(journey: Journey): Promise<void>`**
532
+
533
+ Create a complete route with origin, waypoints, and destination.
534
+
535
+ ```typescript
536
+ await controls.setRoute({
537
+ routeId: 'delivery-route-1',
538
+ origin: {
539
+ coords: { lng: -74.0060, lat: 40.7128 },
540
+ caption: {
541
+ label: 'Warehouse',
542
+ duration: 5,
543
+ unit: 'min'
544
+ }
545
+ },
546
+ destination: {
547
+ coords: { lng: -73.9855, lat: 40.7580 },
548
+ caption: {
549
+ label: 'Customer',
550
+ duration: 15,
551
+ unit: 'min'
552
+ }
553
+ },
554
+ waypoints: [
555
+ {
556
+ coords: { lng: -73.9680, lat: 40.7489 },
557
+ caption: { label: 'Stop 1: Package Pickup' },
558
+ index: 0
559
+ }
560
+ ],
561
+ options: {
562
+ mode: 'navigation',
563
+ profile: 'driving-traffic',
564
+ unit: 'metric',
565
+ preference: 'TRAFFIC_AWARE',
566
+ animation: 'dot:flow'
567
+ }
568
+ });
569
+ ```
570
+
571
+ **Route Options Explained:**
572
+
573
+ ```typescript
574
+ interface RouteOptions {
575
+ id?: string | number;
576
+ mode?: 'default' | 'navigation';
577
+
578
+ // Profile determines routing algorithm
579
+ profile?: 'driving-traffic' // Real-time traffic (recommended)
580
+ | 'driving' // Standard driving
581
+ | 'cycling' // Bicycle routes
582
+ | 'biking' // Bike paths
583
+ | 'walking' // Pedestrian routes
584
+ | 'transit'; // Public transport
585
+
586
+ unit?: 'metric' | 'imperial';
587
+
588
+ // Traffic preference
589
+ preference?: 'TRAFFIC_AWARE' // Use real-time traffic
590
+ | 'TRAFFIC_UNAWARE'; // Ignore traffic
591
+
592
+ pointless?: boolean; // Hide waypoint markers
593
+ styles?: any; // Custom route styling
594
+
595
+ // Animated route options
596
+ animation?: 'dot:flow' // Flowing dots
597
+ | 'dot:fade' // Fading dots
598
+ | 'dot:pulse' // Pulsing dots
599
+ | 'dot:directional' // Directional dots
600
+ | 'solid:flow' // Flowing solid line
601
+ | 'solid:fade' // Fading solid line
602
+ | 'solid:pulse' // Pulsing solid line
603
+ | 'solid:directional' // Directional solid line
604
+ | AnimatedRouteCustomOptions;
605
+ }
606
+
607
+ // Custom animation options
608
+ interface AnimatedRouteCustomOptions {
609
+ handler?: new (engine: Engine, path: Coordinates[], rules?: AnimatedRouteRules) => AnimatedRoute;
610
+ method?: string;
611
+ rules?: {
612
+ styles?: any;
613
+ speed?: number; // pixels per frame
614
+ fadeLength?: number; // fade effect length in pixels
615
+ };
616
+ }
617
+ ```
618
+
619
+ **`setRouteOrigin(routeId: string, point: MapWaypoint): Promise<void>`**
620
+
621
+ Set or update route starting point.
622
+
623
+ ```typescript
624
+ await controls.setRouteOrigin('route-1', {
625
+ coords: [40.7128, -74.0060],
626
+ caption: { label: 'Warehouse A' }
627
+ });
628
+ ```
629
+
630
+ **`setRouteDestination(routeId: string, point: MapWaypoint): Promise<void>`**
631
+
632
+ Set or update route ending point.
633
+
634
+ ```typescript
635
+ await controls.setRouteDestination('route-1', {
636
+ coords: [40.7580, -73.9855],
637
+ caption: { label: 'Customer Delivery' }
638
+ });
639
+ ```
640
+
641
+ **`addRouteWaypoint(routeId: string, point: MapWaypoint): Promise<void>`**
642
+
643
+ Add stop to existing route.
644
+
645
+ ```typescript
646
+ await controls.addRouteWaypoint('route-1', {
647
+ coords: [40.7489, -73.9680],
648
+ caption: {
649
+ label: 'Additional Stop',
650
+ description: 'Package pickup at retail store'
651
+ }
652
+ });
653
+ ```
654
+
655
+ **`updateRouteWaypoint(routeId: string, point: MapWaypoint): Promise<void>`**
656
+
657
+ Update existing waypoint.
658
+
659
+ ```typescript
660
+ await controls.updateRouteWaypoint('route-1', {
661
+ index: 0,
662
+ coords: [40.7500, -73.9700],
663
+ caption: { label: 'Updated Stop Location' }
664
+ });
665
+ ```
666
+
667
+ **`removeRouteWaypoint(routeId: string, index: number): Promise<void>`**
668
+
669
+ Remove waypoint from route.
670
+
671
+ ```typescript
672
+ await controls.removeRouteWaypoint('route-1', 0);
673
+ ```
674
+
675
+ **`fitRouteBounds(routeId: string, margin?: number): Promise<void>`**
676
+
677
+ Adjust map view to show entire route.
678
+
679
+ ```typescript
680
+ await controls.fitRouteBounds('route-1', 100); // 100px margin
681
+ ```
682
+
683
+ **`fitRoutesBounds(options: RoutesFitBoundsOptions): Promise<void>`**
684
+
685
+ Fit multiple routes in view simultaneously.
686
+
687
+ ```typescript
688
+ await controls.fitRoutesBounds({
689
+ includes: ['route-1', 'route-2', 'route-3'],
690
+ margin: 80
691
+ });
692
+ ```
693
+
694
+ #### Nearby Entities
695
+
696
+ **`showNearby(entities: EntitySpecs[]): Promise<void>`**
697
+
698
+ Display multiple entities (vehicles, warehouses, etc.) on map.
699
+
700
+ ```typescript
701
+ await controls.showNearby([
702
+ {
703
+ id: 'vehicle-1',
704
+ type: 'car',
705
+ status: 'ACTIVE',
706
+ grade: '2H',
707
+ currentLocation: { lng: -74.0060, lat: 40.7128 }
708
+ },
709
+ {
710
+ id: 'warehouse-1',
711
+ type: 'warehouse',
712
+ status: 'ACTIVE',
713
+ grade: '1H',
714
+ currentLocation: { lng: -74.0100, lat: 40.7200 },
715
+ static: true
716
+ }
717
+ ]);
718
+ ```
719
+
720
+ **`addNearbyEntity(entity: EntitySpecs): Promise<void>`**
721
+
722
+ Add single entity to map.
723
+
724
+ ```typescript
725
+ await controls.addNearbyEntity({
726
+ id: 'truck-5',
727
+ type: 'truck',
728
+ status: 'BUSY',
729
+ grade: '3H',
730
+ currentLocation: { lng: -74.0200, lat: 40.7300 }
731
+ });
732
+ ```
733
+
734
+ **`moveNearbyEntity(update: ActivePosition): Promise<void>`**
735
+
736
+ Update entity's position (for real-time tracking).
737
+
738
+ ```typescript
739
+ await controls.moveNearbyEntity({
740
+ id: 'vehicle-1',
741
+ position: { lng: -74.0065, lat: 40.7130, heading: 90 },
742
+ caption: { label: 'Van #101', duration: 5, unit: 'min' }
743
+ });
744
+ ```
745
+
746
+ **`removeNearbyEntity(id: string): Promise<void>`**
747
+
748
+ Remove specific entity from map.
749
+
750
+ ```typescript
751
+ await controls.removeNearbyEntity('vehicle-1');
752
+ ```
753
+
754
+ **`removeNearby(): Promise<void>`**
755
+
756
+ Clear all nearby entities.
757
+
758
+ ```typescript
759
+ await controls.removeNearby();
760
+ ```
761
+
762
+ #### Navigation
763
+
764
+ **`mountNavigation(routeId: string): Promise<void>`**
765
+
766
+ Prepare route for turn-by-turn navigation.
767
+
768
+ ```typescript
769
+ await controls.mountNavigation('delivery-route-1');
770
+ ```
771
+
772
+ **`loadNavigation(): Promise<void>`**
773
+
774
+ Load navigation instructions and UI.
775
+
776
+ ```typescript
777
+ await controls.loadNavigation();
778
+ ```
779
+
780
+ **`setInitialNavigationPosition(position: RTLocation): Promise<void>`**
781
+
782
+ Set starting position for navigation.
783
+
784
+ ```typescript
785
+ await controls.setInitialNavigationPosition({
786
+ latitude: 40.7128,
787
+ longitude: -74.0060,
788
+ accuracy: 10,
789
+ heading: 90,
790
+ speed: 0
791
+ });
792
+ ```
793
+
794
+ **`navigate(position: RTLocation): Promise<void>`**
795
+
796
+ Update navigation with current position.
797
+
798
+ ```typescript
799
+ // In GPS update loop
800
+ await controls.navigate({
801
+ latitude: 40.7130,
802
+ longitude: -74.0062,
803
+ accuracy: 8,
804
+ heading: 95,
805
+ speed: 15
806
+ });
807
+ ```
808
+
809
+ **`casting(routeId: string, direction: any, position?: RTLocation, options?: RouteOptions): Promise<void>`**
810
+
811
+ Upsert navigation direction data (for receiving external navigation).
812
+
813
+ ```typescript
814
+ await controls.casting(
815
+ 'route-1',
816
+ externalNavigationData,
817
+ currentPosition,
818
+ { mode: 'driving' }
819
+ );
820
+ ```
821
+
822
+ **`dismissNavigation(): Promise<void>`**
823
+
824
+ End navigation session.
825
+
826
+ ```typescript
827
+ await controls.dismissNavigation();
828
+ ```
829
+
830
+ **`unmountNavigation(): Promise<void>`**
831
+
832
+ Remove navigation from route.
833
+
834
+ ```typescript
835
+ await controls.unmountNavigation();
836
+ ```
837
+
838
+ #### Waypoint Captions
839
+
840
+ **`setWaypointCaption(routeId: string, id: string | number, caption: Caption): Promise<void>`**
841
+
842
+ Add caption to waypoint.
843
+
844
+ ```typescript
845
+ await controls.setWaypointCaption('route-1', 0, {
846
+ label: 'Priority Delivery',
847
+ sublabel: 'Fragile items',
848
+ description: 'Handle with care - Glass contents'
849
+ });
850
+ ```
851
+
852
+ **`updateWaypointCaption(routeId: string, id: string | number, caption: Caption): Promise<void>`**
853
+
854
+ Update existing waypoint caption.
855
+
856
+ ```typescript
857
+ await controls.updateWaypointCaption('route-1', 0, {
858
+ label: 'Completed',
859
+ sublabel: 'Delivered at 2:30 PM'
860
+ });
861
+ ```
862
+
863
+ **`removeWaypointCaption(routeId: string, id: string | number): Promise<void>`**
864
+
865
+ Remove waypoint caption.
866
+
867
+ ```typescript
868
+ await controls.removeWaypointCaption('route-1', 0);
869
+ ```
870
+
871
+ #### Event Listeners
872
+
873
+ **`on(event: string, listener: Function)`**
874
+
875
+ Subscribe to map events.
876
+
877
+ ```typescript
878
+ controls.on('route:updated', (data) => {
879
+ console.log('Route updated:', data);
880
+ });
881
+ ```
882
+
883
+ **`off(event: string, listener: Function)`**
884
+
885
+ Unsubscribe from specific event.
886
+
887
+ ```typescript
888
+ const handler = (data) => console.log(data);
889
+ controls.on('route:updated', handler);
890
+ controls.off('route:updated', handler);
891
+ ```
892
+
893
+ **`removeListeners(listener: Function)`**
894
+
895
+ Remove all instances of a listener.
896
+
897
+ ```typescript
898
+ controls.removeListeners(myHandler);
899
+ ```
900
+
901
+ ---
902
+
903
+ ### 3. Handles
904
+
905
+ High-level, stream-based API for real-time data workflows. Handles abstract complex operations into simple, composable streams.
906
+
907
+ #### Location Streams
908
+
909
+ **`myLocation(usertype?: 'client' | 'agent'): Stream`**
910
+
911
+ Create stream of user's current location with continuous updates.
912
+
913
+ ```typescript
914
+ const locationStream = handles.myLocation('agent');
915
+
916
+ // Listen to location updates
917
+ locationStream.pipe(location => {
918
+ console.log('Current position:', location.latitude, location.longitude);
919
+ console.log('Speed:', location.speed, 'm/s');
920
+ console.log('Heading:', location.heading, 'degrees');
921
+ });
922
+
923
+ // Handle errors
924
+ locationStream.onerror(error => {
925
+ console.error('Location error:', error);
926
+ });
927
+
928
+ // Clean up when done
929
+ locationStream.onclose(() => {
930
+ console.log('Location tracking stopped');
931
+ });
932
+
933
+ // Stop tracking
934
+ locationStream.close();
935
+ ```
936
+
937
+ **`peerLocation(position: RTLocation, caption?: Caption): Stream`**
938
+
939
+ Stream for displaying peer's location (e.g., driver tracking for customer).
940
+
941
+ ```typescript
942
+ const driverStream = handles.peerLocation(
943
+ {
944
+ latitude: 40.7128,
945
+ longitude: -74.0060,
946
+ accuracy: 10,
947
+ heading: 180,
948
+ speed: 25
949
+ },
950
+ {
951
+ label: 'Your Driver',
952
+ sublabel: 'Mike Johnson',
953
+ description: 'White Ford Transit - ABC-123'
954
+ }
955
+ );
956
+
957
+ // Update driver position
958
+ driverStream.write({
959
+ position: {
960
+ latitude: 40.7130,
961
+ longitude: -74.0062,
962
+ accuracy: 8,
963
+ heading: 175,
964
+ speed: 30
965
+ },
966
+ caption: {
967
+ label: 'Your Driver',
968
+ sublabel: 'Mike Johnson',
969
+ description: '5 minutes away'
970
+ }
971
+ });
972
+
973
+ // Close when delivery complete
974
+ driverStream.close();
975
+ ```
976
+
977
+ **`onPickLocation(fn: (location: PickedLocation) => void)`**
978
+
979
+ Listen for user-selected locations on map.
980
+
981
+ ```typescript
982
+ handles.onPickLocation(location => {
983
+ console.log('Coordinates:', location.coordinates);
984
+ console.log('Address:', location.address);
985
+ console.log('Place:', location.placeName);
986
+
987
+ // Use the picked location
988
+ saveDeliveryAddress(location);
989
+ });
990
+ ```
991
+
992
+ #### Entity Management
993
+
994
+ **`nearby(entities: Entity[]): NearbyStream`**
995
+
996
+ Create live stream for managing fleet/nearby entities with real-time controls.
997
+
998
+ ```typescript
999
+ // Initialize with current fleet
1000
+ const fleetStream = handles.nearby([
1001
+ {
1002
+ id: 'van-1',
1003
+ type: 'delivery-van',
1004
+ position: [40.7128, -74.0060],
1005
+ caption: { label: 'Van #101', sublabel: '5 deliveries pending' }
1006
+ },
1007
+ {
1008
+ id: 'truck-1',
1009
+ type: 'freight-truck',
1010
+ position: [40.7200, -74.0100],
1011
+ caption: { label: 'Truck #201', sublabel: '2 pickups remaining' }
1012
+ }
1013
+ ]);
1014
+
1015
+ // Get live control interface
1016
+ fleetStream.live(async controls => {
1017
+ // Add new vehicle
1018
+ await controls.add({
1019
+ id: 'van-2',
1020
+ type: 'delivery-van',
1021
+ position: [40.7300, -74.0150],
1022
+ caption: { label: 'Van #102', sublabel: 'Just dispatched' }
1023
+ });
1024
+
1025
+ // Update vehicle position (real-time tracking)
1026
+ setInterval(async () => {
1027
+ await controls.move({
1028
+ id: 'van-1',
1029
+ position: getLatestVehiclePosition('van-1')
1030
+ });
1031
+ }, 5000);
1032
+
1033
+ // Remove vehicle when done
1034
+ await controls.remove('truck-1');
1035
+ });
1036
+
1037
+ // Monitor all fleet changes
1038
+ fleetStream.pipe(update => {
1039
+ console.log(`Fleet ${update.action}:`, update.dataset);
1040
+ console.log('Total vehicles:', update.list.length);
1041
+
1042
+ // Update dashboard
1043
+ updateFleetDashboard(update.list);
1044
+ });
1045
+
1046
+ // Close stream
1047
+ fleetStream.close();
1048
+ ```
1049
+
1050
+ #### Route Helpers
1051
+
1052
+ **`pickupPoint(location: Coordinates, caption?: Caption): Promise<void>`**
1053
+
1054
+ Set pickup location with visual marker.
1055
+
1056
+ ```typescript
1057
+ await handles.pickupPoint(
1058
+ { lng: -74.0060, lat: 40.7128 },
1059
+ {
1060
+ label: 'Warehouse A',
1061
+ duration: 5,
1062
+ unit: 'min'
1063
+ }
1064
+ );
1065
+ ```
1066
+
1067
+ **`dropoffPoint(location: Coordinates, caption?: Caption): Promise<void>`**
1068
+
1069
+ Set delivery destination with marker.
1070
+
1071
+ ```typescript
1072
+ await handles.dropoffPoint(
1073
+ { lng: -73.9855, lat: 40.7580 },
1074
+ {
1075
+ label: 'Customer Address',
1076
+ duration: 15,
1077
+ unit: 'min'
1078
+ }
1079
+ );
1080
+ ```
1081
+
1082
+ #### Navigation Streams
1083
+
1084
+ **`navigation(journey: Journey): Promise<Stream>`**
1085
+
1086
+ Start turn-by-turn navigation with real-time position updates.
1087
+
1088
+ ```typescript
1089
+ // Start navigation
1090
+ const navStream = await handles.navigation({
1091
+ routeId: 'delivery-route-1',
1092
+ origin: {
1093
+ coords: await controls.getCurrentLocation(),
1094
+ caption: { label: 'Current Location' }
1095
+ },
1096
+ destination: {
1097
+ coords: [40.7580, -73.9855],
1098
+ caption: { label: 'Delivery Address' }
1099
+ },
1100
+ waypoints: [
1101
+ {
1102
+ coords: [40.7489, -73.9680],
1103
+ caption: { label: 'Package Pickup' },
1104
+ index: 0
1105
+ }
1106
+ ],
1107
+ options: {
1108
+ mode: 'driving',
1109
+ avoidTolls: false
1110
+ }
1111
+ });
1112
+
1113
+ // Update with GPS positions
1114
+ const locationStream = handles.myLocation('agent');
1115
+ locationStream.pipe(location => {
1116
+ navStream.write({ position: location });
1117
+ });
1118
+
1119
+ // Listen for navigation events
1120
+ handles.on('pe:started', () => console.log('Navigation started'));
1121
+ handles.on('pe:nearby', () => console.log('Approaching destination'));
1122
+ handles.on('pe:arrived', () => {
1123
+ console.log('Arrived at destination!');
1124
+ navStream.close();
1125
+ locationStream.close();
1126
+ });
1127
+
1128
+ // Handle navigation errors
1129
+ handles.on('pe:closed', () => {
1130
+ console.log('Navigation ended');
1131
+ });
1132
+ ```
1133
+
1134
+ **`peerDirection(options?: RouteOptions): Stream`**
1135
+
1136
+ Stream for displaying peer's navigation (e.g., showing driver's route to customer).
1137
+
1138
+ ```typescript
1139
+ const driverNavStream = handles.peerDirection({
1140
+ mode: 'driving',
1141
+ avoidTolls: false,
1142
+ announceInstructions: false
1143
+ });
1144
+
1145
+ // Receive driver navigation updates from backend
1146
+ websocket.onmessage = (event) => {
1147
+ const { status, direction, position } = JSON.parse(event.data);
1148
+
1149
+ driverNavStream.write({
1150
+ status,
1151
+ direction,
1152
+ position
1153
+ });
1154
+ };
1155
+
1156
+ // Listen to driver proximity events
1157
+ handles.on('pe:nearby', () => {
1158
+ notifyCustomer('Driver is nearby!');
1159
+ });
1160
+
1161
+ handles.on('pe:arrived', () => {
1162
+ notifyCustomer('Driver has arrived!');
1163
+ driverNavStream.close();
1164
+ });
1165
+ ```
1166
+
1167
+ #### Navigation Status Events
1168
+
1169
+ ```typescript
1170
+ // All available navigation events
1171
+ handles.on('pe:started', () => {}); // Navigation began
1172
+ handles.on('pe:stale', () => {}); // No movement detected
1173
+ handles.on('pe:long_stop', () => {}); // Stopped for extended period
1174
+ handles.on('pe:low_traffic', () => {}); // Light traffic conditions
1175
+ handles.on('pe:moderate_traffic', () => {}); // Moderate traffic
1176
+ handles.on('pe:high_traffic', () => {}); // Heavy traffic
1177
+ handles.on('pe:speed_warning', () => {}); // Speeding detected
1178
+ handles.on('pe:nearby', () => {}); // Approaching destination
1179
+ handles.on('pe:arrived', () => {}); // Reached destination
1180
+ handles.on('pe:closed', () => {}); // Navigation ended
1181
+ ```
1182
+
1183
+ ---
1184
+
1185
+ ### 4. Plugins
1186
+
1187
+ Extensible system for adding custom functionality with access to all SDK hooks.
1188
+
1189
+ #### Plugin Architecture
1190
+
1191
+ ```typescript
1192
+ type Plugin<API, Options = {}> = (
1193
+ hooks: PluginHook,
1194
+ options?: Options
1195
+ ) => API;
1196
+
1197
+ interface PluginHook {
1198
+ handles: Handles; // Stream-based operations
1199
+ controls: Controls; // Direct map operations
1200
+ map: MapOptions; // Map configuration
1201
+ utils: typeof Utils; // Utility functions
1202
+ }
1203
+ ```
1204
+
1205
+ #### Creating a Plugin
1206
+
1207
+ ```typescript
1208
+ // Define plugin interface
1209
+ interface DeliveryAnalyticsAPI {
1210
+ trackRoute: (routeId: string) => Promise<void>;
1211
+ getRouteMetrics: (routeId: string) => Promise<RouteMetrics>;
1212
+ exportData: () => Promise<string>;
1213
+ }
1214
+
1215
+ interface DeliveryAnalyticsOptions {
1216
+ apiKey?: string;
1217
+ debug?: boolean;
1218
+ }
1219
+
1220
+ // Implement plugin
1221
+ const deliveryAnalyticsPlugin: Plugin<DeliveryAnalyticsAPI, DeliveryAnalyticsOptions> = (
1222
+ hooks,
1223
+ options
1224
+ ) => {
1225
+ const { controls, handles, map, utils } = hooks;
1226
+ const routeData = new Map();
1227
+
1228
+ return {
1229
+ async trackRoute(routeId: string) {
1230
+ const startTime = Date.now();
1231
+
1232
+ // Use handles to listen for completion
1233
+ handles.on('pe:arrived', () => {
1234
+ const endTime = Date.now();
1235
+ const duration = endTime - startTime;
1236
+
1237
+ routeData.set(routeId, {
1238
+ duration,
1239
+ timestamp: new Date(),
1240
+ distance: calculateDistance()
1241
+ });
1242
+
1243
+ if (options?.debug) {
1244
+ console.log(`Route ${routeId} completed in ${duration}ms`);
1245
+ }
1246
+ });
1247
+ },
1248
+
1249
+ async getRouteMetrics(routeId: string) {
1250
+ const data = routeData.get(routeId);
1251
+ if (!data) throw new Error('Route not tracked');
1252
+
1253
+ return {
1254
+ duration: data.duration,
1255
+ distance: data.distance,
1256
+ averageSpeed: data.distance / (data.duration / 1000)
1257
+ };
1258
+ },
1259
+
1260
+ async exportData() {
1261
+ return JSON.stringify(Array.from(routeData.entries()));
1262
+ }
1263
+ };
1264
+ };
1265
+
1266
+ // Register plugin
1267
+ msi.plugin('deliveryAnalytics', deliveryAnalyticsPlugin);
1268
+
1269
+ // Use plugin after MSI loads
1270
+ const { plugins } = await msi.load();
1271
+ const analytics = plugins.use('deliveryAnalytics', {
1272
+ apiKey: 'your-api-key',
1273
+ debug: true
1274
+ });
1275
+
1276
+ await analytics.trackRoute('route-123');
1277
+ ```
1278
+
1279
+ #### Plugin Methods
1280
+
1281
+ **`plugins.mount(list: Record<string, Plugin<any>>)`**
1282
+
1283
+ Mount multiple plugins at once.
1284
+
1285
+ ```typescript
1286
+ plugins.mount({
1287
+ analytics: deliveryAnalyticsPlugin,
1288
+ notifications: deliveryNotificationsPlugin,
1289
+ customMarkers: customMarkersPlugin
1290
+ });
1291
+ ```
1292
+
1293
+ **`plugins.use<API, Options>(name: string, options?: Options): API`**
1294
+
1295
+ Activate and get plugin interface.
1296
+
1297
+ ```typescript
1298
+ const analytics = plugins.use('analytics', { debug: true });
1299
+ const notifications = plugins.use('notifications');
1300
+ ```
1301
+
1302
+ #### Example: Fleet Management Plugin
1303
+
1304
+ ```typescript
1305
+ const fleetManagementPlugin = (hooks, options) => {
1306
+ const { controls, handles, utils } = hooks;
1307
+ const vehicles = new Map();
1308
+
1309
+ return {
1310
+ async registerVehicle(vehicle) {
1311
+ vehicles.set(vehicle.id, {
1312
+ ...vehicle,
1313
+ lastUpdate: Date.now()
1314
+ });
1315
+
1316
+ await controls.addNearbyEntity({
1317
+ id: vehicle.id,
1318
+ type: 'vehicle',
1319
+ position: vehicle.position,
1320
+ caption: {
1321
+ label: vehicle.name,
1322
+ sublabel: `${vehicle.capacity} capacity`
1323
+ }
1324
+ });
1325
+ },
1326
+
1327
+ async updateVehiclePosition(vehicleId, position) {
1328
+ const vehicle = vehicles.get(vehicleId);
1329
+ if (!vehicle) throw new Error('Vehicle not found');
1330
+
1331
+ vehicle.position = position;
1332
+ vehicle.lastUpdate = Date.now();
1333
+
1334
+ await controls.moveNearbyEntity({
1335
+ id: vehicleId,
1336
+ position
1337
+ });
1338
+ },
1339
+
1340
+ async getVehicleStatus(vehicleId) {
1341
+ const vehicle = vehicles.get(vehicleId);
1342
+ return vehicle || null;
1343
+ },
1344
+
1345
+ async optimizeRoutes(vehicleIds) {
1346
+ // Use controls to calculate optimal routes
1347
+ const routes = [];
1348
+
1349
+ for (const id of vehicleIds) {
1350
+ const vehicle = vehicles.get(id);
1351
+ if (vehicle && vehicle.destination) {
1352
+ routes.push({
1353
+ vehicleId: id,
1354
+ route: await calculateOptimalRoute(
1355
+ vehicle.position,
1356
+ vehicle.destination
1357
+ )
1358
+ });
1359
+ }
1360
+ }
1361
+
1362
+ return routes;
1363
+ },
1364
+
1365
+ getFleetSummary() {
1366
+ return {
1367
+ totalVehicles: vehicles.size,
1368
+ activeVehicles: Array.from(vehicles.values()).filter(
1369
+ v => Date.now() - v.lastUpdate < 60000
1370
+ ).length
1371
+ };
1372
+ }
1373
+ };
1374
+ };
1375
+
1376
+ // Usage
1377
+ msi.plugin('fleetManagement', fleetManagementPlugin);
1378
+ const { plugins } = await msi.load();
1379
+ const fleet = plugins.use('fleetManagement');
1380
+
1381
+ await fleet.registerVehicle({
1382
+ id: 'van-1',
1383
+ name: 'Delivery Van #101',
1384
+ position: [40.7128, -74.0060],
1385
+ capacity: '1500kg'
1386
+ });
1387
+
1388
+ const summary = fleet.getFleetSummary();
1389
+ console.log('Fleet summary:', summary);
1390
+ ```
1391
+
1392
+ ---
1393
+
1394
+ ### 5. Access
1395
+
1396
+ Manages authentication and HTTP requests to De. platform API.
1397
+
1398
+ #### Constructor
1399
+
1400
+ ```typescript
1401
+ const access = new De.Access({
1402
+ workspace: 'your-workspace-id', // Required
1403
+ accessToken: 'your-access-token', // Required
1404
+ remoteOrigin: window.location.origin,
1405
+ env: 'prod', // 'dev' | 'prod'
1406
+ version: 1 // API version
1407
+ });
1408
+ ```
1409
+
1410
+ #### Making Requests
1411
+
1412
+ **`request<Response>(options: HTTPRequestOptions): Promise<Response>`**
1413
+
1414
+ Execute authenticated HTTP request.
1415
+
1416
+ ```typescript
1417
+ // GET request
1418
+ const vehicles = await access.request<VehicleResponse[]>({
1419
+ url: '/fleet/vehicles',
1420
+ method: 'GET'
1421
+ });
1422
+
1423
+ // POST request
1424
+ const order = await access.request<OrderResponse>({
1425
+ url: '/orders',
1426
+ method: 'POST',
1427
+ body: {
1428
+ pickupLocation: [40.7128, -74.0060],
1429
+ deliveryLocation: [40.7580, -73.9855],
1430
+ items: [
1431
+ { name: 'Package A', weight: 5 }
1432
+ ]
1433
+ }
1434
+ });
1435
+
1436
+ // PUT request
1437
+ const updated = await access.request({
1438
+ url: '/orders/order-123',
1439
+ method: 'PUT',
1440
+ body: { status: 'in-transit' }
1441
+ });
1442
+
1443
+ // DELETE request
1444
+ await access.request({
1445
+ url: '/orders/order-123',
1446
+ method: 'DELETE'
1447
+ });
1448
+
1449
+ // Custom headers
1450
+ const data = await access.request({
1451
+ url: '/analytics/reports',
1452
+ method: 'GET',
1453
+ headers: {
1454
+ 'De-Report-Format': 'json',
1455
+ 'De-Include-Metrics': 'true'
1456
+ }
1457
+ });
1458
+ ```
1459
+
1460
+ #### Token Management
1461
+
1462
+ **`setToken(token: string): void`**
1463
+
1464
+ Update access token (e.g., after refresh).
1465
+
1466
+ ```typescript
1467
+ // Refresh token flow
1468
+ const newToken = await refreshAuthToken();
1469
+ access.setToken(newToken);
1470
+
1471
+ // Also update MSI if needed
1472
+ controls.refreshToken(newToken);
1473
+ ```
1474
+
1475
+ #### Request Configuration
1476
+
1477
+ ```typescript
1478
+ interface HTTPRequestOptions {
1479
+ url: string; // API endpoint
1480
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1481
+ body?: any; // Auto-stringified if object
1482
+ headers?: Record<string, string>; // Custom headers
1483
+ }
1484
+ ```
1485
+
1486
+ #### Best Practices
1487
+
1488
+ ```typescript
1489
+ class DeliveryService {
1490
+ private access: De.Access;
1491
+
1492
+ constructor(config) {
1493
+ this.access = new De.Access(config);
1494
+ }
1495
+
1496
+ async createOrder(orderData) {
1497
+ try {
1498
+ const order = await this.access.request({
1499
+ url: '/orders',
1500
+ method: 'POST',
1501
+ body: orderData
1502
+ });
1503
+
1504
+ return order;
1505
+ } catch (error) {
1506
+ console.error('Order creation failed:', error);
1507
+ throw error;
1508
+ }
1509
+ }
1510
+
1511
+ async getOrderStatus(orderId) {
1512
+ return await this.access.request({
1513
+ url: `/orders/${orderId}/status`,
1514
+ method: 'GET'
1515
+ });
1516
+ }
1517
+
1518
+ async updateOrderLocation(orderId, location) {
1519
+ return await this.access.request({
1520
+ url: `/orders/${orderId}/location`,
1521
+ method: 'PATCH',
1522
+ body: { location }
1523
+ });
1524
+ }
1525
+ }
1526
+ ```
1527
+
1528
+ ---
1529
+
1530
+ ### 6. DClient
1531
+
1532
+ Client-side operations for orders, events, and customer management (separate from MSI).
1533
+
1534
+ ```typescript
1535
+ import { DClient } from '@de./sdk';
1536
+
1537
+ const { Client, Order, Event } = DClient;
1538
+
1539
+ // Client management
1540
+ const client = new Client(accessConfig);
1541
+
1542
+ // Order management
1543
+ const order = new Order(accessConfig);
1544
+
1545
+ // Event tracking
1546
+ const event = new Event(accessConfig);
1547
+ ```
1548
+
1549
+ **Note**: DClient documentation is maintained separately. See the DClient API reference at https://docs.dedot.io/dclient
1550
+
1551
+ ---
1552
+
1553
+ ## Code Examples
1554
+
1555
+ ### Example 1: Complete E-commerce Delivery Integration
1556
+
1557
+ ```typescript
1558
+ import De from '@de./sdk';
1559
+
1560
+ class EcommerceDeliveryIntegration {
1561
+ private msi: De.MSI;
1562
+ private access: De.Access;
1563
+ private controls?: De.Controls;
1564
+ private handles?: De.Handles;
1565
+
1566
+ constructor(config) {
1567
+ this.access = new De.Access(config);
1568
+ this.msi = new De.MSI(config);
1569
+ }
1570
+
1571
+ async initialize() {
1572
+ const api = await this.msi.load();
1573
+ this.controls = api.controls;
1574
+ this.handles = api.handles;
1575
+ }
1576
+
1577
+ async processCheckout(cartItems, customerAddress) {
1578
+ // 1. Create order via API
1579
+ const order = await this.access.request({
1580
+ url: '/orders',
1581
+ method: 'POST',
1582
+ body: {
1583
+ items: cartItems,
1584
+ deliveryAddress: customerAddress,
1585
+ timestamp: Date.now()
1586
+ }
1587
+ });
1588
+
1589
+ // 2. Get warehouse location
1590
+ const warehouse = await this.access.request({
1591
+ url: '/warehouses/nearest',
1592
+ method: 'POST',
1593
+ body: { address: customerAddress }
1594
+ });
1595
+
1596
+ // 3. Display route on map
1597
+ await this.handles.pickupPoint(
1598
+ warehouse.location,
1599
+ {
1600
+ label: warehouse.name,
1601
+ description: `Order #${order.id} origin`
1602
+ }
1603
+ );
1604
+
1605
+ await this.handles.dropoffPoint(
1606
+ customerAddress.coordinates,
1607
+ {
1608
+ label: 'Your Address',
1609
+ sublabel: customerAddress.street,
1610
+ description: `Delivery for order #${order.id}`
1611
+ }
1612
+ );
1613
+
1614
+ // 4. Assign delivery vehicle
1615
+ const vehicle = await this.assignVehicle(order.id);
1616
+
1617
+ // 5. Track vehicle in real-time
1618
+ const trackingStream = this.trackDeliveryVehicle(vehicle);
1619
+
1620
+ return {
1621
+ order,
1622
+ warehouse,
1623
+ vehicle,
1624
+ trackingStream
1625
+ };
1626
+ }
1627
+
1628
+ async assignVehicle(orderId) {
1629
+ return await this.access.request({
1630
+ url: `/orders/${orderId}/assign-vehicle`,
1631
+ method: 'POST'
1632
+ });
1633
+ }
1634
+
1635
+ trackDeliveryVehicle(vehicle) {
1636
+ const vehicleStream = this.handles.peerLocation(
1637
+ vehicle.currentLocation,
1638
+ {
1639
+ label: 'Your Delivery',
1640
+ sublabel: `Driver: ${vehicle.driverName}`,
1641
+ description: `${vehicle.vehicleType} - ETA: ${vehicle.eta}`
1642
+ }
1643
+ );
1644
+
1645
+ // Connect to real-time updates
1646
+ const ws = new WebSocket(`wss://api.dedot.io/vehicles/${vehicle.id}`);
1647
+
1648
+ ws.onmessage = (event) => {
1649
+ const update = JSON.parse(event.data);
1650
+ vehicleStream.write({
1651
+ position: update.location,
1652
+ caption: {
1653
+ label: 'Your Delivery',
1654
+ sublabel: `Driver: ${vehicle.driverName}`,
1655
+ description: `${update.distance} away - ETA: ${update.eta}`
1656
+ }
1657
+ });
1658
+ };
1659
+
1660
+ // Listen for delivery events
1661
+ this.handles.on('pe:nearby', () => {
1662
+ this.notifyCustomer('Driver is nearby!', 'warning');
1663
+ });
1664
+
1665
+ this.handles.on('pe:arrived', () => {
1666
+ this.notifyCustomer('Driver has arrived!', 'success');
1667
+ vehicleStream.close();
1668
+ ws.close();
1669
+ });
1670
+
1671
+ return vehicleStream;
1672
+ }
1673
+
1674
+ notifyCustomer(message, type) {
1675
+ // Send push notification, SMS, etc.
1676
+ console.log(`[${type.toUpperCase()}] ${message}`);
1677
+ }
1678
+ }
1679
+
1680
+ // Usage
1681
+ const delivery = new EcommerceDeliveryIntegration({
1682
+ element: 'map-container',
1683
+ accessToken: 'your-token',
1684
+ workspace: 'your-workspace',
1685
+ env: 'prod'
1686
+ });
1687
+
1688
+ await delivery.initialize();
1689
+
1690
+ const result = await delivery.processCheckout(
1691
+ [{ id: 'item-1', name: 'Product A', quantity: 2 }],
1692
+ {
1693
+ street: '456 Park Ave, Apt 12B',
1694
+ coordinates: [40.7580, -73.9855]
1695
+ }
1696
+ );
1697
+
1698
+ console.log('Order created:', result.order.id);
1699
+ ```
1700
+
1701
+ ### Example 2: Fleet Management Dashboard
1702
+
1703
+ ```typescript
1704
+ class FleetManagementDashboard {
1705
+ private msi: De.MSI;
1706
+ private access: De.Access;
1707
+ private fleetStream?: any;
1708
+ private wsConnection?: WebSocket;
1709
+
1710
+ constructor(config) {
1711
+ this.msi = new De.MSI(config);
1712
+ this.access = new De.Access(config);
1713
+ }
1714
+
1715
+ async initialize() {
1716
+ const { controls, handles } = await this.msi.load();
1717
+
1718
+ // Get all active vehicles from API
1719
+ const { vehicles } = await this.access.request({
1720
+ url: '/fleet/active',
1721
+ method: 'GET'
1722
+ });
1723
+
1724
+ // Display vehicles on map
1725
+ this.fleetStream = handles.nearby(
1726
+ vehicles.map(v => ({
1727
+ id: v.id,
1728
+ type: v.vehicleType,
1729
+ position: v.currentLocation,
1730
+ caption: {
1731
+ label: v.name,
1732
+ sublabel: `Driver: ${v.driverName}`,
1733
+ description: `Status: ${v.status} | Speed: ${v.speed}km/h`
1734
+ }
1735
+ }))
1736
+ );
1737
+
1738
+ // Setup real-time updates
1739
+ this.setupRealtimeTracking();
1740
+
1741
+ // Monitor fleet changes
1742
+ this.fleetStream.pipe(update => {
1743
+ this.updateDashboardStats(update.list);
1744
+ });
1745
+
1746
+ return { controls, handles };
1747
+ }
1748
+
1749
+ setupRealtimeTracking() {
1750
+ this.wsConnection = new WebSocket('wss://api.dedot.io/fleet/live');
1751
+
1752
+ this.wsConnection.onmessage = async (event) => {
1753
+ const update = JSON.parse(event.data);
1754
+
1755
+ this.fleetStream.live(async controls => {
1756
+ switch (update.type) {
1757
+ case 'position_update':
1758
+ await controls.move({
1759
+ id: update.vehicleId,
1760
+ position: update.location
1761
+ });
1762
+ break;
1763
+
1764
+ case 'vehicle_online':
1765
+ await controls.add({
1766
+ id: update.vehicle.id,
1767
+ type: update.vehicle.type,
1768
+ position: update.vehicle.location,
1769
+ caption: {
1770
+ label: update.vehicle.name,
1771
+ sublabel: `Driver: ${update.vehicle.driverName}`
1772
+ }
1773
+ });
1774
+ break;
1775
+
1776
+ case 'vehicle_offline':
1777
+ await controls.remove(update.vehicleId);
1778
+ break;
1779
+ }
1780
+ });
1781
+ };
1782
+
1783
+ this.wsConnection.onerror = () => {
1784
+ console.error('WebSocket connection lost, reconnecting...');
1785
+ setTimeout(() => this.setupRealtimeTracking(), 5000);
1786
+ };
1787
+ }
1788
+
1789
+ updateDashboardStats(vehicles) {
1790
+ const stats = {
1791
+ total: vehicles.length,
1792
+ active: vehicles.filter(v => v.status === 'active').length,
1793
+ idle: vehicles.filter(v => v.status === 'idle').length,
1794
+ offline: vehicles.filter(v => v.status === 'offline').length
1795
+ };
1796
+
1797
+ // Update UI
1798
+ this.renderDashboardStats(stats);
1799
+ }
1800
+
1801
+ renderDashboardStats(stats) {
1802
+ document.getElementById('total-vehicles').textContent = stats.total;
1803
+ document.getElementById('active-vehicles').textContent = stats.active;
1804
+ document.getElementById('idle-vehicles').textContent = stats.idle;
1805
+ document.getElementById('offline-vehicles').textContent = stats.offline;
1806
+ }
1807
+
1808
+ async optimizeRoutes() {
1809
+ const { routes } = await this.access.request({
1810
+ url: '/fleet/optimize-routes',
1811
+ method: 'POST'
1812
+ });
1813
+
1814
+ console.log('Routes optimized:', routes);
1815
+ return routes;
1816
+ }
1817
+
1818
+ cleanup() {
1819
+ this.wsConnection?.close();
1820
+ this.fleetStream?.close();
1821
+ }
1822
+ }
1823
+
1824
+ // Usage
1825
+ const dashboard = new FleetManagementDashboard({
1826
+ element: 'map-container',
1827
+ accessToken: 'your-token',
1828
+ workspace: 'your-workspace'
1829
+ });
1830
+
1831
+ await dashboard.initialize();
1832
+ ```
1833
+
1834
+ ### Example 3: Multi-Stop Route Planning
1835
+
1836
+ ```typescript
1837
+ async function planMultiStopDeliveryRoute() {
1838
+ const msi = new De.MSI({
1839
+ element: 'map-container',
1840
+ accessToken: 'your-token',
1841
+ workspace: 'your-workspace'
1842
+ });
1843
+
1844
+ const { controls, handles } = await msi.load();
1845
+
1846
+ // Define delivery stops
1847
+ const stops = [
1848
+ {
1849
+ address: 'Warehouse - 123 Industrial Blvd',
1850
+ coords: [40.7128, -74.0060],
1851
+ type: 'pickup',
1852
+ items: ['Package A', 'Package B', 'Package C']
1853
+ },
1854
+ {
1855
+ address: 'Customer 1 - 456 Park Ave',
1856
+ coords: [40.7489, -73.9680],
1857
+ type: 'delivery',
1858
+ items: ['Package A']
1859
+ },
1860
+ {
1861
+ address: 'Customer 2 - 789 Broadway',
1862
+ coords: [40.7300, -73.9950],
1863
+ type: 'delivery',
1864
+ items: ['Package B']
1865
+ },
1866
+ {
1867
+ address: 'Customer 3 - 321 5th Ave',
1868
+ coords: [40.7580, -73.9855],
1869
+ type: 'delivery',
1870
+ items: ['Package C']
1871
+ }
1872
+ ];
1873
+
1874
+ // Create route with all stops
1875
+ await controls.setRoute({
1876
+ routeId: 'multi-stop-delivery',
1877
+ origin: {
1878
+ coords: stops[0].coords,
1879
+ caption: {
1880
+ label: stops[0].address,
1881
+ sublabel: 'Pickup Point',
1882
+ description: stops[0].items.join(', ')
1883
+ }
1884
+ },
1885
+ destination: {
1886
+ coords: stops[stops.length - 1].coords,
1887
+ caption: {
1888
+ label: stops[stops.length - 1].address,
1889
+ sublabel: 'Final Delivery',
1890
+ description: `Items: ${stops[stops.length - 1].items.join(', ')}`
1891
+ }
1892
+ },
1893
+ waypoints: stops.slice(1, -1).map((stop, index) => ({
1894
+ coords: stop.coords,
1895
+ caption: {
1896
+ label: `Stop ${index + 1}`,
1897
+ sublabel: stop.address,
1898
+ description: `Deliver: ${stop.items.join(', ')}`
1899
+ },
1900
+ index
1901
+ })),
1902
+ options: {
1903
+ mode: 'driving',
1904
+ optimize: true,
1905
+ avoidTolls: false
1906
+ }
1907
+ });
1908
+
1909
+ // Fit all stops in view
1910
+ await controls.fitRouteBounds('multi-stop-delivery', 80);
1911
+
1912
+ // Start navigation
1913
+ const navStream = await handles.navigation({
1914
+ routeId: 'multi-stop-delivery',
1915
+ origin: {
1916
+ coords: stops[0].coords,
1917
+ caption: { label: 'Start' }
1918
+ },
1919
+ destination: {
1920
+ coords: stops[stops.length - 1].coords,
1921
+ caption: { label: 'End' }
1922
+ },
1923
+ waypoints: stops.slice(1, -1).map((stop, index) => ({
1924
+ coords: stop.coords,
1925
+ caption: { label: `Stop ${index + 1}` },
1926
+ index
1927
+ }))
1928
+ });
1929
+
1930
+ // Track progress
1931
+ let currentStop = 0;
1932
+ handles.on('pe:arrived', async () => {
1933
+ currentStop++;
1934
+ console.log(`Arrived at stop ${currentStop} of ${stops.length}`);
1935
+
1936
+ if (currentStop < stops.length) {
1937
+ // Update waypoint caption to mark as completed
1938
+ await controls.updateWaypointCaption(
1939
+ 'multi-stop-delivery',
1940
+ currentStop - 1,
1941
+ {
1942
+ label: `Stop ${currentStop} - Completed ✓`,
1943
+ sublabel: stops[currentStop].address
1944
+ }
1945
+ );
1946
+ } else {
1947
+ console.log('All deliveries completed!');
1948
+ navStream.close();
1949
+ }
1950
+ });
1951
+
1952
+ return { navStream, stops };
1953
+ }
1954
+
1955
+ // Usage
1956
+ const { navStream, stops } = await planMultiStopDeliveryRoute();
1957
+ console.log(`Route planned with ${stops.length} stops`);
1958
+ ```
1959
+
1960
+ ### Example 4: Real-time Order Tracking for Customers
1961
+
1962
+ ```typescript
1963
+ class CustomerOrderTracking {
1964
+ private msi: De.MSI;
1965
+ private access: De.Access;
1966
+
1967
+ constructor(config) {
1968
+ this.msi = new De.MSI(config);
1969
+ this.access = new De.Access(config);
1970
+ }
1971
+
1972
+ async trackOrder(orderId: string) {
1973
+ // Initialize MSI
1974
+ const { controls, handles } = await this.msi.load();
1975
+
1976
+ // Get order details
1977
+ const order = await this.access.request({
1978
+ url: `/orders/${orderId}`,
1979
+ method: 'GET'
1980
+ });
1981
+
1982
+ // Show delivery route
1983
+ await handles.pickupPoint(
1984
+ order.pickupLocation,
1985
+ { label: 'Pickup', description: order.originName }
1986
+ );
1987
+
1988
+ await handles.dropoffPoint(
1989
+ order.deliveryLocation,
1990
+ { label: 'Your Address', description: order.deliveryAddress }
1991
+ );
1992
+
1993
+ // Show delivery vehicle if assigned
1994
+ if (order.vehicleId) {
1995
+ const vehicle = await this.access.request({
1996
+ url: `/vehicles/${order.vehicleId}`,
1997
+ method: 'GET'
1998
+ });
1999
+
2000
+ // Track driver's location
2001
+ const driverStream = handles.peerLocation(
2002
+ vehicle.currentLocation,
2003
+ {
2004
+ label: 'Your Driver',
2005
+ sublabel: `${vehicle.driverName} - ${vehicle.vehicleModel}`,
2006
+ description: `ETA: ${vehicle.eta}`
2007
+ }
2008
+ );
2009
+
2010
+ // Show driver's navigation
2011
+ const driverNavStream = handles.peerDirection({
2012
+ mode: 'driving'
2013
+ });
2014
+
2015
+ // Connect to real-time updates
2016
+ const ws = new WebSocket(`wss://api.dedot.io/orders/${orderId}/live`);
2017
+
2018
+ ws.onmessage = (event) => {
2019
+ const update = JSON.parse(event.data);
2020
+
2021
+ // Update driver position
2022
+ driverStream.write({
2023
+ position: update.vehicleLocation,
2024
+ caption: {
2025
+ label: 'Your Driver',
2026
+ sublabel: `${vehicle.driverName} - ${vehicle.vehicleModel}`,
2027
+ description: `${update.distanceToDelivery} away - ETA: ${update.eta}`
2028
+ }
2029
+ });
2030
+
2031
+ // Update driver's navigation
2032
+ if (update.navigationData) {
2033
+ driverNavStream.write({
2034
+ status: update.status,
2035
+ direction: update.navigationData,
2036
+ position: update.vehicleLocation
2037
+ });
2038
+ }
2039
+ };
2040
+
2041
+ // Listen for proximity alerts
2042
+ handles.on('pe:nearby', () => {
2043
+ this.showNotification('Driver is nearby! Prepare to receive your delivery.');
2044
+ });
2045
+
2046
+ handles.on('pe:arrived', () => {
2047
+ this.showNotification('Driver has arrived at your location!');
2048
+ ws.close();
2049
+ driverStream.close();
2050
+ driverNavStream.close();
2051
+ });
2052
+
2053
+ return {
2054
+ order,
2055
+ vehicle,
2056
+ streams: { driverStream, driverNavStream },
2057
+ websocket: ws
2058
+ };
2059
+ }
2060
+
2061
+ return { order };
2062
+ }
2063
+
2064
+ showNotification(message: string) {
2065
+ // Show browser notification
2066
+ if ('Notification' in window && Notification.permission === 'granted') {
2067
+ new Notification('De. Delivery', {
2068
+ body: message,
2069
+ icon: '/delivery-icon.png'
2070
+ });
2071
+ }
2072
+
2073
+ // Also show in-app notification
2074
+ const notification = document.createElement('div');
2075
+ notification.className = 'notification';
2076
+ notification.textContent = message;
2077
+ document.body.appendChild(notification);
2078
+
2079
+ setTimeout(() => notification.remove(), 5000);
2080
+ }
2081
+ }
2082
+
2083
+ // Usage
2084
+ const tracking = new CustomerOrderTracking({
2085
+ element: 'map-container',
2086
+ accessToken: 'customer-token',
2087
+ workspace: 'your-workspace'
2088
+ });
2089
+
2090
+ const result = await tracking.trackOrder('order-12345');
2091
+ console.log('Tracking order:', result.order.id);
2092
+ ```
2093
+
2094
+ ### Example 5: Warehouse Management Plugin
2095
+
2096
+ ```typescript
2097
+ const warehouseManagementPlugin = (hooks, options) => {
2098
+ const { controls, handles, utils } = hooks;
2099
+ const warehouses = new Map();
2100
+ const inventory = new Map();
2101
+
2102
+ return {
2103
+ async registerWarehouse(warehouse) {
2104
+ warehouses.set(warehouse.id, warehouse);
2105
+
2106
+ await controls.addNearbyEntity({
2107
+ id: warehouse.id,
2108
+ type: 'warehouse',
2109
+ position: warehouse.location,
2110
+ caption: {
2111
+ label: warehouse.name,
2112
+ sublabel: `Capacity: ${warehouse.capacity}`,
2113
+ description: `Items: ${warehouse.currentStock}`
2114
+ }
2115
+ });
2116
+
2117
+ return warehouse.id;
2118
+ },
2119
+
2120
+ async updateInventory(warehouseId, items) {
2121
+ inventory.set(warehouseId, items);
2122
+
2123
+ const warehouse = warehouses.get(warehouseId);
2124
+ if (warehouse) {
2125
+ await controls.moveNearbyEntity({
2126
+ id: warehouseId,
2127
+ position: warehouse.location
2128
+ });
2129
+ }
2130
+ },
2131
+
2132
+ async findNearestWarehouse(location, itemsNeeded) {
2133
+ let nearest = null;
2134
+ let minDistance = Infinity;
2135
+
2136
+ for (const [id, warehouse] of warehouses) {
2137
+ const items = inventory.get(id) || [];
2138
+ const hasAllItems = itemsNeeded.every(item =>
2139
+ items.some(i => i.id === item.id && i.quantity >= item.quantity)
2140
+ );
2141
+
2142
+ if (hasAllItems) {
2143
+ const distance = utils.calculateDistance(
2144
+ location,
2145
+ warehouse.location
2146
+ );
2147
+
2148
+ if (distance < minDistance) {
2149
+ minDistance = distance;
2150
+ nearest = { warehouse, distance };
2151
+ }
2152
+ }
2153
+ }
2154
+
2155
+ return nearest;
2156
+ },
2157
+
2158
+ async optimizeStockDistribution() {
2159
+ const distributionPlan = [];
2160
+
2161
+ for (const [id, warehouse] of warehouses) {
2162
+ const items = inventory.get(id) || [];
2163
+ const lowStock = items.filter(i => i.quantity < i.minQuantity);
2164
+
2165
+ if (lowStock.length > 0) {
2166
+ distributionPlan.push({
2167
+ warehouseId: id,
2168
+ warehouse: warehouse.name,
2169
+ needsRestocking: lowStock
2170
+ });
2171
+ }
2172
+ }
2173
+
2174
+ return distributionPlan;
2175
+ },
2176
+
2177
+ getWarehouseStats() {
2178
+ return {
2179
+ total: warehouses.size,
2180
+ averageCapacity: Array.from(warehouses.values())
2181
+ .reduce((sum, w) => sum + w.capacity, 0) / warehouses.size,
2182
+ totalItems: Array.from(inventory.values())
2183
+ .reduce((sum, items) => sum + items.length, 0)
2184
+ };
2185
+ }
2186
+ };
2187
+ };
2188
+
2189
+ // Usage
2190
+ msi.plugin('warehouseManagement', warehouseManagementPlugin);
2191
+
2192
+ const { plugins } = await msi.load();
2193
+ const wms = plugins.use('warehouseManagement');
2194
+
2195
+ // Register warehouses
2196
+ await wms.registerWarehouse({
2197
+ id: 'wh-001',
2198
+ name: 'Brooklyn Distribution Center',
2199
+ location: [40.7128, -74.0060],
2200
+ capacity: 10000,
2201
+ currentStock: 7500
2202
+ });
2203
+
2204
+ // Find nearest warehouse with items
2205
+ const nearest = await wms.findNearestWarehouse(
2206
+ [40.7580, -73.9855],
2207
+ [{ id: 'item-a', quantity: 5 }]
2208
+ );
2209
+
2210
+ console.log('Nearest warehouse:', nearest.warehouse.name);
2211
+ console.log('Distance:', nearest.distance, 'km');
2212
+ ```
2213
+
2214
+ ---
2215
+
2216
+ ## Advanced Usage Patterns
2217
+
2218
+ ### Pattern 1: Dynamic Route Optimization
2219
+
2220
+ ```typescript
2221
+ class DynamicRouteOptimizer {
2222
+ private controls: De.Controls;
2223
+ private access: De.Access;
2224
+
2225
+ constructor(controls: De.Controls, access: De.Access) {
2226
+ this.controls = controls;
2227
+ this.access = access;
2228
+ }
2229
+
2230
+ async optimizeDeliveryRoute(deliveries: Delivery[]) {
2231
+ // Get traffic data from API
2232
+ const trafficData = await this.access.request({
2233
+ url: '/traffic/current',
2234
+ method: 'POST',
2235
+ body: {
2236
+ locations: deliveries.map(d => d.location)
2237
+ }
2238
+ });
2239
+
2240
+ // Sort deliveries by priority and traffic conditions
2241
+ const optimized = this.sortByOptimalSequence(
2242
+ deliveries,
2243
+ trafficData
2244
+ );
2245
+
2246
+ // Create route with optimized stops
2247
+ await this.controls.setRoute({
2248
+ routeId: 'optimized-route',
2249
+ origin: {
2250
+ coords: optimized[0].location,
2251
+ caption: { label: 'Start' }
2252
+ },
2253
+ destination: {
2254
+ coords: optimized[optimized.length - 1].location,
2255
+ caption: { label: 'End' }
2256
+ },
2257
+ waypoints: optimized.slice(1, -1).map((delivery, index) => ({
2258
+ coords: delivery.location,
2259
+ caption: {
2260
+ label: `Stop ${index + 1}`,
2261
+ description: `Priority: ${delivery.priority}`
2262
+ },
2263
+ index
2264
+ })),
2265
+ options: {
2266
+ mode: 'driving',
2267
+ optimize: true,
2268
+ avoidHighTraffic: true
2269
+ }
2270
+ });
2271
+
2272
+ return optimized;
2273
+ }
2274
+
2275
+ private sortByOptimalSequence(
2276
+ deliveries: Delivery[],
2277
+ trafficData: any
2278
+ ): Delivery[] {
2279
+ // Complex optimization algorithm
2280
+ // Consider: priority, traffic, distance, time windows
2281
+
2282
+ return deliveries.sort((a, b) => {
2283
+ const scoreA = this.calculateDeliveryScore(a, trafficData);
2284
+ const scoreB = this.calculateDeliveryScore(b, trafficData);
2285
+ return scoreB - scoreA; // Higher score = higher priority
2286
+ });
2287
+ }
2288
+
2289
+ private calculateDeliveryScore(delivery: Delivery, trafficData: any): number {
2290
+ let score = 0;
2291
+
2292
+ // Priority weight (0-100)
2293
+ score += delivery.priority * 10;
2294
+
2295
+ // Traffic conditions (lower traffic = higher score)
2296
+ const traffic = trafficData.find(t =>
2297
+ t.location[0] === delivery.location[0] &&
2298
+ t.location[1] === delivery.location[1]
2299
+ );
2300
+ if (traffic) {
2301
+ score += (100 - traffic.congestionLevel);
2302
+ }
2303
+
2304
+ // Time window urgency
2305
+ const timeToDeadline = delivery.deadline - Date.now();
2306
+ if (timeToDeadline < 3600000) { // Less than 1 hour
2307
+ score += 50;
2308
+ }
2309
+
2310
+ return score;
2311
+ }
2312
+ }
2313
+
2314
+ // Usage
2315
+ const optimizer = new DynamicRouteOptimizer(controls, access);
2316
+
2317
+ const deliveries = [
2318
+ { location: [40.7128, -74.0060], priority: 8, deadline: Date.now() + 7200000 },
2319
+ { location: [40.7580, -73.9855], priority: 10, deadline: Date.now() + 3600000 },
2320
+ { location: [40.7489, -73.9680], priority: 5, deadline: Date.now() + 14400000 }
2321
+ ];
2322
+
2323
+ const optimized = await optimizer.optimizeDeliveryRoute(deliveries);
2324
+ console.log('Optimized delivery sequence:', optimized);
2325
+ ```
2326
+
2327
+ ### Pattern 2: Real-time Fleet Coordination
2328
+
2329
+ ```typescript
2330
+ class FleetCoordinator {
2331
+ private handles: De.Handles;
2332
+ private access: De.Access;
2333
+ private activeVehicles: Map<string, VehicleState> = new Map();
2334
+ private orders: Map<string, Order> = new Map();
2335
+
2336
+ constructor(handles: De.Handles, access: De.Access) {
2337
+ this.handles = handles;
2338
+ this.access = access;
2339
+ }
2340
+
2341
+ async initialize() {
2342
+ // Get all active vehicles
2343
+ const { vehicles } = await this.access.request({
2344
+ url: '/fleet/active',
2345
+ method: 'GET'
2346
+ });
2347
+
2348
+ // Display vehicles on map
2349
+ const fleetStream = this.handles.nearby(
2350
+ vehicles.map(v => this.formatVehicleEntity(v))
2351
+ );
2352
+
2353
+ // Setup real-time coordination
2354
+ fleetStream.live(async controls => {
2355
+ this.setupWebSocketConnection(controls);
2356
+ this.monitorVehicleStatus();
2357
+ });
2358
+
2359
+ return fleetStream;
2360
+ }
2361
+
2362
+ private formatVehicleEntity(vehicle: any) {
2363
+ return {
2364
+ id: vehicle.id,
2365
+ type: vehicle.type,
2366
+ position: vehicle.currentLocation,
2367
+ caption: {
2368
+ label: vehicle.name,
2369
+ sublabel: `Status: ${vehicle.status}`,
2370
+ description: vehicle.currentOrder
2371
+ ? `Delivering order #${vehicle.currentOrder}`
2372
+ : 'Available'
2373
+ }
2374
+ };
2375
+ }
2376
+
2377
+ private setupWebSocketConnection(controls: any) {
2378
+ const ws = new WebSocket('wss://api.dedot.io/fleet/coordination');
2379
+
2380
+ ws.onmessage = async (event) => {
2381
+ const message = JSON.parse(event.data);
2382
+
2383
+ switch (message.type) {
2384
+ case 'vehicle_update':
2385
+ await this.handleVehicleUpdate(controls, message.data);
2386
+ break;
2387
+
2388
+ case 'new_order':
2389
+ await this.handleNewOrder(controls, message.data);
2390
+ break;
2391
+
2392
+ case 'order_completed':
2393
+ await this.handleOrderCompletion(controls, message.data);
2394
+ break;
2395
+ }
2396
+ };
2397
+ }
2398
+
2399
+ private async handleVehicleUpdate(controls: any, data: any) {
2400
+ this.activeVehicles.set(data.vehicleId, {
2401
+ location: data.location,
2402
+ status: data.status,
2403
+ lastUpdate: Date.now()
2404
+ });
2405
+
2406
+ await controls.move({
2407
+ id: data.vehicleId,
2408
+ position: data.location
2409
+ });
2410
+ }
2411
+
2412
+ private async handleNewOrder(controls: any, order: Order) {
2413
+ this.orders.set(order.id, order);
2414
+
2415
+ // Find best vehicle for this order
2416
+ const vehicle = await this.assignBestVehicle(order);
2417
+
2418
+ if (vehicle) {
2419
+ console.log(`Assigned order ${order.id} to vehicle ${vehicle.id}`);
2420
+
2421
+ // Notify vehicle via API
2422
+ await this.access.request({
2423
+ url: `/vehicles/${vehicle.id}/assign-order`,
2424
+ method: 'POST',
2425
+ body: { orderId: order.id }
2426
+ });
2427
+ }
2428
+ }
2429
+
2430
+ private async handleOrderCompletion(controls: any, data: any) {
2431
+ this.orders.delete(data.orderId);
2432
+
2433
+ // Update vehicle status
2434
+ const vehicle = this.activeVehicles.get(data.vehicleId);
2435
+ if (vehicle) {
2436
+ vehicle.status = 'available';
2437
+ this.activeVehicles.set(data.vehicleId, vehicle);
2438
+ }
2439
+ }
2440
+
2441
+ private async assignBestVehicle(order: Order): Promise<any | null> {
2442
+ let bestVehicle = null;
2443
+ let minDistance = Infinity;
2444
+
2445
+ for (const [id, state] of this.activeVehicles) {
2446
+ if (state.status === 'available') {
2447
+ const distance = this.calculateDistance(
2448
+ state.location,
2449
+ order.pickupLocation
2450
+ );
2451
+
2452
+ if (distance < minDistance) {
2453
+ minDistance = distance;
2454
+ bestVehicle = { id, ...state };
2455
+ }
2456
+ }
2457
+ }
2458
+
2459
+ return bestVehicle;
2460
+ }
2461
+
2462
+ private monitorVehicleStatus() {
2463
+ setInterval(() => {
2464
+ const now = Date.now();
2465
+
2466
+ for (const [id, state] of this.activeVehicles) {
2467
+ const timeSinceUpdate = now - state.lastUpdate;
2468
+
2469
+ // Alert if vehicle hasn't updated in 5 minutes
2470
+ if (timeSinceUpdate > 300000) {
2471
+ console.warn(`Vehicle ${id} connection lost`);
2472
+ this.alertDispatcher(`Vehicle ${id} offline`);
2473
+ }
2474
+ }
2475
+ }, 60000); // Check every minute
2476
+ }
2477
+
2478
+ private calculateDistance(from: number[], to: number[]): number {
2479
+ const R = 6371; // Earth radius in km
2480
+ const dLat = this.toRad(to[0] - from[0]);
2481
+ const dLon = this.toRad(to[1] - from[1]);
2482
+
2483
+ const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
2484
+ Math.cos(this.toRad(from[0])) * Math.cos(this.toRad(to[0])) *
2485
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
2486
+
2487
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
2488
+ return R * c;
2489
+ }
2490
+
2491
+ private toRad(deg: number): number {
2492
+ return deg * (Math.PI / 180);
2493
+ }
2494
+
2495
+ private alertDispatcher(message: string) {
2496
+ console.log('[ALERT]', message);
2497
+ // Send notification to dispatcher
2498
+ }
2499
+
2500
+ getFleetMetrics() {
2501
+ const available = Array.from(this.activeVehicles.values())
2502
+ .filter(v => v.status === 'available').length;
2503
+
2504
+ return {
2505
+ totalVehicles: this.activeVehicles.size,
2506
+ available,
2507
+ busy: this.activeVehicles.size - available,
2508
+ pendingOrders: this.orders.size
2509
+ };
2510
+ }
2511
+ }
2512
+
2513
+ // Usage
2514
+ const coordinator = new FleetCoordinator(handles, access);
2515
+ const fleetStream = await coordinator.initialize();
2516
+
2517
+ setInterval(() => {
2518
+ const metrics = coordinator.getFleetMetrics();
2519
+ console.log('Fleet metrics:', metrics);
2520
+ }, 30000); // Every 30 seconds
2521
+ ```
2522
+
2523
+ ### Pattern 3: Geofencing and Zone Management
2524
+
2525
+ ```typescript
2526
+ class GeofenceManager {
2527
+ private controls: De.Controls;
2528
+ private handles: De.Handles;
2529
+ private zones: Map<string, Zone> = new Map();
2530
+
2531
+ constructor(controls: De.Controls, handles: De.Handles) {
2532
+ this.controls = controls;
2533
+ this.handles = handles;
2534
+ }
2535
+
2536
+ async createDeliveryZone(zone: Zone) {
2537
+ this.zones.set(zone.id, zone);
2538
+
2539
+ // Display zone on map
2540
+ await this.controls.addNearbyEntity({
2541
+ id: zone.id,
2542
+ type: 'zone',
2543
+ position: zone.center,
2544
+ radius: zone.radius,
2545
+ caption: {
2546
+ label: zone.name,
2547
+ description: `Delivery zone - ${zone.restrictions || 'No restrictions'}`
2548
+ }
2549
+ });
2550
+ }
2551
+
2552
+ async monitorVehicleInZones(vehicleId: string) {
2553
+ const locationStream = this.handles.myLocation('agent');
2554
+
2555
+ locationStream.pipe(location => {
2556
+ for (const [zoneId, zone] of this.zones) {
2557
+ const isInside = this.isLocationInZone(location, zone);
2558
+
2559
+ if (isInside && !zone.activeVehicles?.includes(vehicleId)) {
2560
+ this.handleVehicleEnterZone(vehicleId, zone);
2561
+ } else if (!isInside && zone.activeVehicles?.includes(vehicleId)) {
2562
+ this.handleVehicleExitZone(vehicleId, zone);
2563
+ }
2564
+ }
2565
+ });
2566
+
2567
+ return locationStream;
2568
+ }
2569
+
2570
+ private isLocationInZone(location: RTLocation, zone: Zone): boolean {
2571
+ const distance = this.calculateDistance(
2572
+ [location.latitude, location.longitude],
2573
+ zone.center
2574
+ );
2575
+
2576
+ return distance <= zone.radius;
2577
+ }
2578
+
2579
+ private handleVehicleEnterZone(vehicleId: string, zone: Zone) {
2580
+ console.log(`Vehicle ${vehicleId} entered zone ${zone.name}`);
2581
+
2582
+ if (!zone.activeVehicles) {
2583
+ zone.activeVehicles = [];
2584
+ }
2585
+ zone.activeVehicles.push(vehicleId);
2586
+
2587
+ // Apply zone restrictions
2588
+ if (zone.speedLimit) {
2589
+ this.notifyDriver(vehicleId, `Speed limit: ${zone.speedLimit}km/h`);
2590
+ }
2591
+
2592
+ if (zone.restrictions) {
2593
+ this.notifyDriver(vehicleId, `Zone restrictions: ${zone.restrictions}`);
2594
+ }
2595
+ }
2596
+
2597
+ private handleVehicleExitZone(vehicleId: string, zone: Zone) {
2598
+ console.log(`Vehicle ${vehicleId} exited zone ${zone.name}`);
2599
+
2600
+ if (zone.activeVehicles) {
2601
+ zone.activeVehicles = zone.activeVehicles.filter(id => id !== vehicleId);
2602
+ }
2603
+ }
2604
+
2605
+ private calculateDistance(from: number[], to: number[]): number {
2606
+ const R = 6371000; // Earth radius in meters
2607
+ const dLat = this.toRad(to[0] - from[0]);
2608
+ const dLon = this.toRad(to[1] - from[1]);
2609
+
2610
+ const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
2611
+ Math.cos(this.toRad(from[0])) * Math.cos(this.toRad(to[0])) *
2612
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
2613
+
2614
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
2615
+ return R * c;
2616
+ }
2617
+
2618
+ private toRad(deg: number): number {
2619
+ return deg * (Math.PI / 180);
2620
+ }
2621
+
2622
+ private notifyDriver(vehicleId: string, message: string) {
2623
+ console.log(`[Notification to ${vehicleId}]: ${message}`);
2624
+ // Send push notification or in-app message
2625
+ }
2626
+
2627
+ getZoneStatistics(zoneId: string) {
2628
+ const zone = this.zones.get(zoneId);
2629
+ if (!zone) return null;
2630
+
2631
+ return {
2632
+ name: zone.name,
2633
+ activeVehicles: zone.activeVehicles?.length || 0,
2634
+ capacity: zone.capacity,
2635
+ utilization: ((zone.activeVehicles?.length || 0) / zone.capacity) * 100
2636
+ };
2637
+ }
2638
+ }
2639
+
2640
+ // Usage
2641
+ const geofence = new GeofenceManager(controls, handles);
2642
+
2643
+ // Create delivery zones
2644
+ await geofence.createDeliveryZone({
2645
+ id: 'zone-downtown',
2646
+ name: 'Downtown District',
2647
+ center: [40.7128, -74.0060],
2648
+ radius: 2000, // meters
2649
+ speedLimit: 40,
2650
+ restrictions: 'No large trucks between 7AM-9AM',
2651
+ capacity: 50
2652
+ });
2653
+
2654
+ await geofence.createDeliveryZone({
2655
+ id: 'zone-residential',
2656
+ name: 'Residential Area',
2657
+ center: [40.7580, -73.9855],
2658
+ radius: 1500,
2659
+ speedLimit: 30,
2660
+ restrictions: 'Quiet zone - No honking',
2661
+ capacity: 30
2662
+ });
2663
+
2664
+ // Monitor vehicle
2665
+ const vehicleStream = await geofence.monitorVehicleInZones('vehicle-123');
2666
+ ```
2667
+
2668
+ ---
2669
+
2670
+ ## Plugin Development
2671
+
2672
+ ### Plugin Best Practices
2673
+
2674
+ 1. **Use hooks efficiently** - Cache expensive operations
2675
+
2676
+ ```typescript
2677
+ const efficientPlugin = (hooks, options) => {
2678
+ const { controls, handles, utils } = hooks;
2679
+ const cache = new Map();
2680
+
2681
+ return {
2682
+ async getDataWithCache(key: string) {
2683
+ if (cache.has(key)) {
2684
+ return cache.get(key);
2685
+ }
2686
+
2687
+ const data = await controls.someExpensiveOperation(key);
2688
+ cache.set(key, data);
2689
+
2690
+ // Auto-expire after 5 minutes
2691
+ setTimeout(() => cache.delete(key), 300000);
2692
+
2693
+ return data;
2694
+ }
2695
+ };
2696
+ };
2697
+ ```
2698
+
2699
+ 2. **Clean up resources** - Always remove listeners and close streams
2700
+
2701
+ ```typescript
2702
+ const cleanupPlugin = (hooks, options) => {
2703
+ const { handles } = hooks;
2704
+ const listeners = new Map();
2705
+
2706
+ return {
2707
+ startMonitoring(eventName: string, handler: Function) {
2708
+ handles.on(eventName, handler);
2709
+ listeners.set(eventName, handler);
2710
+ },
2711
+
2712
+ stopMonitoring(eventName: string) {
2713
+ const handler = listeners.get(eventName);
2714
+ if (handler) {
2715
+ handles.off(eventName, handler);
2716
+ listeners.delete(eventName);
2717
+ }
2718
+ },
2719
+
2720
+ cleanup() {
2721
+ for (const [event, handler] of listeners) {
2722
+ handles.off(event, handler);
2723
+ }
2724
+ listeners.clear();
2725
+ }
2726
+ };
2727
+ };
2728
+ ```
2729
+
2730
+ 3. **Handle errors gracefully** - Never let plugin errors crash the app
2731
+
2732
+ ```typescript
2733
+ const safePlugin = (hooks, options) => {
2734
+ const { controls } = hooks;
2735
+
2736
+ return {
2737
+ async safeOperation(params: any) {
2738
+ try {
2739
+ const result = await controls.performOperation(params);
2740
+ return { success: true, data: result };
2741
+ } catch (error) {
2742
+ console.error('[Plugin Error]', error);
2743
+ return { success: false, error: error.message };
2744
+ }
2745
+ }
2746
+ };
2747
+ };
2748
+ ```
2749
+
2750
+ 4. **Provide configuration options** - Make plugins flexible
2751
+
2752
+ ```typescript
2753
+ interface AnalyticsPluginOptions {
2754
+ trackingId: string;
2755
+ debug?: boolean;
2756
+ sampleRate?: number;
2757
+ endpoint?: string;
2758
+ }
2759
+
2760
+ const analyticsPlugin = (hooks, options: AnalyticsPluginOptions) => {
2761
+ const debug = options.debug ?? false;
2762
+ const sampleRate = options.sampleRate ?? 1.0;
2763
+
2764
+ const log = (...args: any[]) => {
2765
+ if (debug) console.log('[Analytics]', ...args);
2766
+ };
2767
+
2768
+ return {
2769
+ track(event: string, data: any) {
2770
+ if (Math.random() > sampleRate) return; // Sampling
2771
+
2772
+ log('Tracking event:', event, data);
2773
+
2774
+ // Send to analytics endpoint
2775
+ fetch(options.endpoint || 'https://analytics.dedot.io', {
2776
+ method: 'POST',
2777
+ body: JSON.stringify({
2778
+ trackingId: options.trackingId,
2779
+ event,
2780
+ data,
2781
+ timestamp: Date.now()
2782
+ })
2783
+ }).catch(error => log('Failed to send event:', error));
2784
+ }
2785
+ };
2786
+ };
2787
+ ```
2788
+
2789
+ ### Complete Plugin Template
2790
+
2791
+ ```typescript
2792
+ import type { Plugin, PluginHook } from '@de./sdk';
2793
+
2794
+ /**
2795
+ * Plugin Configuration Options
2796
+ */
2797
+ interface MyPluginOptions {
2798
+ apiKey?: string;
2799
+ endpoint?: string;
2800
+ debug?: boolean;
2801
+ retries?: number;
2802
+ }
2803
+
2804
+ /**
2805
+ * Plugin Public API
2806
+ */
2807
+ interface MyPluginAPI {
2808
+ initialize: () => Promise<void>;
2809
+ performAction: (params: any) => Promise<any>;
2810
+ getStatus: () => PluginStatus;
2811
+ cleanup: () => void;
2812
+ }
2813
+
2814
+ interface PluginStatus {
2815
+ initialized: boolean;
2816
+ activeOperations: number;
2817
+ lastError?: string;
2818
+ }
2819
+
2820
+ /**
2821
+ * My Custom Plugin
2822
+ */
2823
+ const myCustomPlugin: Plugin<MyPluginAPI, MyPluginOptions> = (
2824
+ hooks: PluginHook,
2825
+ options?: MyPluginOptions
2826
+ ) => {
2827
+ // Destructure hooks
2828
+ const { controls, handles, map, utils } = hooks;
2829
+
2830
+ // Plugin configuration with defaults
2831
+ const config = {
2832
+ apiKey: options?.apiKey || '',
2833
+ endpoint: options?.endpoint || 'https://api.example.com',
2834
+ debug: options?.debug ?? false,
2835
+ retries: options?.retries ?? 3
2836
+ };
2837
+
2838
+ // Plugin state
2839
+ let initialized = false;
2840
+ let activeOperations = 0;
2841
+ let lastError: string | undefined;
2842
+
2843
+ // Internal logging
2844
+ const log = (...args: any[]) => {
2845
+ if (config.debug) {
2846
+ console.log('[MyPlugin]', ...args);
2847
+ }
2848
+ };
2849
+
2850
+ // Internal error handling
2851
+ const handleError = (error: any, context: string) => {
2852
+ lastError = error.message || String(error);
2853
+ log('Error in', context, ':', error);
2854
+ };
2855
+
2856
+ // Return public API
2857
+ return {
2858
+ async initialize() {
2859
+ if (initialized) {
2860
+ log('Already initialized');
2861
+ return;
2862
+ }
2863
+
2864
+ try {
2865
+ log('Initializing plugin...');
2866
+
2867
+ // Setup plugin (e.g., fetch config, register handlers)
2868
+ await controls.setMapStyle('streets');
2869
+
2870
+ handles.on('pe:arrived', () => {
2871
+ log('Arrival event detected');
2872
+ });
2873
+
2874
+ initialized = true;
2875
+ log('Plugin initialized successfully');
2876
+ } catch (error) {
2877
+ handleError(error, 'initialize');
2878
+ throw error;
2879
+ }
2880
+ },
2881
+
2882
+ async performAction(params: any) {
2883
+ if (!initialized) {
2884
+ throw new Error('Plugin not initialized');
2885
+ }
2886
+
2887
+ activeOperations++;
2888
+
2889
+ try {
2890
+ log('Performing action with params:', params);
2891
+
2892
+ // Implement your plugin logic here
2893
+ const result = await someAsyncOperation(params);
2894
+
2895
+ return result;
2896
+ } catch (error) {
2897
+ handleError(error, 'performAction');
2898
+ throw error;
2899
+ } finally {
2900
+ activeOperations--;
2901
+ }
2902
+ },
2903
+
2904
+ getStatus(): PluginStatus {
2905
+ return {
2906
+ initialized,
2907
+ activeOperations,
2908
+ lastError
2909
+ };
2910
+ },
2911
+
2912
+ cleanup() {
2913
+ log('Cleaning up plugin resources');
2914
+
2915
+ // Remove event listeners
2916
+ handles.removeListeners(() => {});
2917
+
2918
+ // Clear state
2919
+ initialized = false;
2920
+ activeOperations = 0;
2921
+ lastError = undefined;
2922
+
2923
+ log('Plugin cleanup complete');
2924
+ }
2925
+ };
2926
+ };
2927
+
2928
+ // Helper function (not exposed in API)
2929
+ async function someAsyncOperation(params: any): Promise<any> {
2930
+ // Implementation
2931
+ return { success: true };
2932
+ }
2933
+
2934
+ export default myCustomPlugin;
2935
+ ```
2936
+
2937
+ ---
2938
+
2939
+ ## Error Handling
2940
+
2941
+ ### Common Errors and Solutions
2942
+
2943
+ #### 1. Timeout Errors
2944
+
2945
+ All Controls methods have a 12-second timeout.
2946
+
2947
+ ```typescript
2948
+ try {
2949
+ await controls.setRoute(journey);
2950
+ } catch (error) {
2951
+ if (error === 'Event timeout') {
2952
+ console.error('Operation timed out after 12 seconds');
2953
+
2954
+ // Retry with exponential backoff
2955
+ await retryWithBackoff(() => controls.setRoute(journey));
2956
+ }
2957
+ }
2958
+
2959
+ async function retryWithBackoff(
2960
+ operation: () => Promise<any>,
2961
+ maxRetries: number = 3,
2962
+ baseDelay: number = 1000
2963
+ ) {
2964
+ for (let i = 0; i < maxRetries; i++) {
2965
+ try {
2966
+ return await operation();
2967
+ } catch (error) {
2968
+ if (i === maxRetries - 1) throw error;
2969
+
2970
+ const delay = baseDelay * Math.pow(2, i);
2971
+ console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
2972
+ await new Promise(resolve => setTimeout(resolve, delay));
2973
+ }
2974
+ }
2975
+ }
2976
+ ```
2977
+
2978
+ #### 2. Location Permission Errors
2979
+
2980
+ ```typescript
2981
+ try {
2982
+ const location = await controls.getCurrentLocation();
2983
+ } catch (error) {
2984
+ console.error('Location access denied:', error);
2985
+
2986
+ // Show UI prompt
2987
+ showLocationPermissionDialog({
2988
+ title: 'Location Access Required',
2989
+ message: 'Please enable location services to continue',
2990
+ onEnable: async () => {
2991
+ // Retry after user enables location
2992
+ const location = await controls.getCurrentLocation();
2993
+ handleLocation(location);
2994
+ }
2995
+ });
2996
+ }
2997
+ ```
2998
+
2999
+ #### 3. Invalid Coordinates
3000
+
3001
+ ```typescript
3002
+ function validateCoordinates(coords: [number, number]): boolean {
3003
+ const [lat, lng] = coords;
3004
+
3005
+ if (typeof lat !== 'number' || typeof lng !== 'number') {
3006
+ return false;
3007
+ }
3008
+
3009
+ if (lat < -90 || lat > 90) {
3010
+ return false;
3011
+ }
3012
+
3013
+ if (lng < -180 || lng > 180) {
3014
+ return false;
3015
+ }
3016
+
3017
+ return true;
3018
+ }
3019
+
3020
+ // Usage
3021
+ if (!validateCoordinates(userCoords)) {
3022
+ throw new Error(`Invalid coordinates: ${userCoords}`);
3023
+ }
3024
+
3025
+ await controls.pinCurrentLocation();
3026
+ ```
3027
+
3028
+ #### 4. Stream Errors
3029
+
3030
+ ```typescript
3031
+ const stream = handles.myLocation('agent');
3032
+
3033
+ stream
3034
+ .pipe(location => {
3035
+ // Process location
3036
+ updateDriverPosition(location);
3037
+ })
3038
+ .onerror(error => {
3039
+ console.error('Stream error:', error);
3040
+
3041
+ // Attempt to restart stream
3042
+ setTimeout(() => {
3043
+ const newStream = handles.myLocation('agent');
3044
+ setupStreamHandlers(newStream);
3045
+ }, 5000);
3046
+ })
3047
+ .onclose(() => {
3048
+ console.log('Stream closed gracefully');
3049
+ });
3050
+
3051
+ // Always close streams when component unmounts
3052
+ onComponentUnmount(() => {
3053
+ if (stream.isActive()) {
3054
+ stream.close();
3055
+ }
3056
+ });
3057
+ ```
3058
+
3059
+ #### 5. API Request Errors
3060
+
3061
+ ```typescript
3062
+ async function makeRequest<T>(
3063
+ access: De.Access,
3064
+ options: any
3065
+ ): Promise<T> {
3066
+ try {
3067
+ return await access.request<T>(options);
3068
+ } catch (error) {
3069
+ // Handle specific HTTP errors
3070
+ if (error.status === 401) {
3071
+ console.error('Unauthorized - refresh token');
3072
+ const newToken = await refreshAccessToken();
3073
+ access.setToken(newToken);
3074
+
3075
+ // Retry request
3076
+ return await access.request<T>(options);
3077
+ }
3078
+
3079
+ if (error.status === 429) {
3080
+ console.error('Rate limited - wait and retry');
3081
+ await new Promise(resolve => setTimeout(resolve, 5000));
3082
+ return await access.request<T>(options);
3083
+ }
3084
+
3085
+ if (error.status >= 500) {
3086
+ console.error('Server error - retry later');
3087
+ throw new Error('Service temporarily unavailable');
3088
+ }
3089
+
3090
+ throw error;
3091
+ }
3092
+ }
3093
+
3094
+ // Usage
3095
+ const vehicles = await makeRequest<Vehicle[]>(access, {
3096
+ url: '/fleet/vehicles',
3097
+ method: 'GET'
3098
+ });
3099
+ ```
3100
+
3101
+ #### 6. MSI Load Errors
3102
+
3103
+ ```typescript
3104
+ const msi = new De.MSI({
3105
+ element: 'map-container',
3106
+ accessToken: 'your-token',
3107
+ workspace: 'your-workspace',
3108
+ env: 'prod'
3109
+ });
3110
+
3111
+ // Handle MSI errors
3112
+ msi.on('error', (error: Error) => {
3113
+ console.error('MSI initialization error:', error);
3114
+
3115
+ if (error.message.includes('Element')) {
3116
+ showError('Map container not found. Please check your HTML.');
3117
+ } else if (error.message.includes('network')) {
3118
+ showError('Network error. Please check your internet connection.');
3119
+ } else {
3120
+ showError('Failed to load map. Please refresh the page.');
3121
+ }
3122
+ });
3123
+
3124
+ try {
3125
+ const { controls, handles, plugins } = await msi.load();
3126
+ console.log('MSI loaded successfully');
3127
+ } catch (error) {
3128
+ console.error('Failed to load MSI:', error);
3129
+
3130
+ // Show user-friendly error message
3131
+ showErrorDialog({
3132
+ title: 'Map Loading Failed',
3133
+ message: 'Unable to load the map interface. Please try again.',
3134
+ actions: [
3135
+ { label: 'Retry', onClick: () => window.location.reload() },
3136
+ { label: 'Contact Support', onClick: () => openSupportChat() }
3137
+ ]
3138
+ });
3139
+ }
3140
+ ```
3141
+
3142
+ ### Error Handling Best Practices
3143
+
3144
+ #### 1. Always Use Try-Catch with Async Operations
3145
+
3146
+ ```typescript
3147
+ async function setupDeliveryRoute() {
3148
+ try {
3149
+ await controls.setRoute(journey);
3150
+ await controls.fitRouteBounds('route-1');
3151
+ console.log('Route setup successful');
3152
+ } catch (error) {
3153
+ console.error('Route setup failed:', error);
3154
+ notifyUser('Unable to setup route. Please try again.');
3155
+ }
3156
+ }
3157
+ ```
3158
+
3159
+ #### 2. Handle Stream Lifecycle Properly
3160
+
3161
+ ```typescript
3162
+ function setupLocationTracking() {
3163
+ const stream = handles.myLocation('agent');
3164
+
3165
+ // Always attach error handler
3166
+ stream.onerror(error => {
3167
+ console.error('Location tracking error:', error);
3168
+ // Attempt recovery
3169
+ attemptStreamRecovery();
3170
+ });
3171
+
3172
+ // Always attach close handler
3173
+ stream.onclose(() => {
3174
+ console.log('Location tracking stopped');
3175
+ cleanupResources();
3176
+ });
3177
+
3178
+ // Process data
3179
+ stream.pipe(location => {
3180
+ updateUI(location);
3181
+ });
3182
+
3183
+ // Return cleanup function
3184
+ return () => {
3185
+ if (stream.isActive()) {
3186
+ stream.close();
3187
+ }
3188
+ };
3189
+ }
3190
+
3191
+ // In React/Vue/Angular
3192
+ useEffect(() => {
3193
+ const cleanup = setupLocationTracking();
3194
+ return cleanup; // Cleanup on unmount
3195
+ }, []);
3196
+ ```
3197
+
3198
+ #### 3. Validate Input Before API Calls
3199
+
3200
+ ```typescript
3201
+ function validateJourney(journey: Journey): string[] {
3202
+ const errors: string[] = [];
3203
+
3204
+ if (!journey.origin) {
3205
+ errors.push('Origin is required');
3206
+ } else if (!validateCoordinates(journey.origin.coords)) {
3207
+ errors.push('Invalid origin coordinates');
3208
+ }
3209
+
3210
+ if (!journey.destination) {
3211
+ errors.push('Destination is required');
3212
+ } else if (!validateCoordinates(journey.destination.coords)) {
3213
+ errors.push('Invalid destination coordinates');
3214
+ }
3215
+
3216
+ if (journey.waypoints) {
3217
+ journey.waypoints.forEach((wp, index) => {
3218
+ if (!validateCoordinates(wp.coords)) {
3219
+ errors.push(`Invalid coordinates for waypoint ${index + 1}`);
3220
+ }
3221
+ });
3222
+ }
3223
+
3224
+ return errors;
3225
+ }
3226
+
3227
+ // Usage
3228
+ const errors = validateJourney(myJourney);
3229
+ if (errors.length > 0) {
3230
+ console.error('Journey validation failed:', errors);
3231
+ showValidationErrors(errors);
3232
+ } else {
3233
+ await controls.setRoute(myJourney);
3234
+ }
3235
+ ```
3236
+
3237
+ #### 4. Implement Retry Logic for Critical Operations
3238
+
3239
+ ```typescript
3240
+ async function retryOperation<T>(
3241
+ operation: () => Promise<T>,
3242
+ options: {
3243
+ maxRetries?: number;
3244
+ delay?: number;
3245
+ backoff?: boolean;
3246
+ onRetry?: (attempt: number, error: any) => void;
3247
+ } = {}
3248
+ ): Promise<T> {
3249
+ const {
3250
+ maxRetries = 3,
3251
+ delay = 1000,
3252
+ backoff = true,
3253
+ onRetry
3254
+ } = options;
3255
+
3256
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
3257
+ try {
3258
+ return await operation();
3259
+ } catch (error) {
3260
+ if (attempt === maxRetries) {
3261
+ throw error;
3262
+ }
3263
+
3264
+ const waitTime = backoff ? delay * Math.pow(2, attempt - 1) : delay;
3265
+
3266
+ if (onRetry) {
3267
+ onRetry(attempt, error);
3268
+ }
3269
+
3270
+ console.log(`Retry ${attempt}/${maxRetries} after ${waitTime}ms`);
3271
+ await new Promise(resolve => setTimeout(resolve, waitTime));
3272
+ }
3273
+ }
3274
+
3275
+ throw new Error('Max retries exceeded');
3276
+ }
3277
+
3278
+ // Usage
3279
+ const location = await retryOperation(
3280
+ () => controls.getCurrentLocation(),
3281
+ {
3282
+ maxRetries: 3,
3283
+ delay: 2000,
3284
+ backoff: true,
3285
+ onRetry: (attempt, error) => {
3286
+ console.log(`Attempt ${attempt} failed:`, error.message);
3287
+ }
3288
+ }
3289
+ );
3290
+ ```
3291
+
3292
+ #### 5. Proactive Token Refresh
3293
+
3294
+ ```typescript
3295
+ class TokenManager {
3296
+ private access: De.Access;
3297
+ private controls?: De.Controls;
3298
+ private refreshInterval?: NodeJS.Timeout;
3299
+ private tokenExpiryTime: number;
3300
+
3301
+ constructor(access: De.Access, tokenExpiryTime: number) {
3302
+ this.access = access;
3303
+ this.tokenExpiryTime = tokenExpiryTime;
3304
+ }
3305
+
3306
+ setControls(controls: De.Controls) {
3307
+ this.controls = controls;
3308
+ }
3309
+
3310
+ startAutoRefresh() {
3311
+ // Refresh token 5 minutes before expiry
3312
+ const refreshTime = this.tokenExpiryTime - 300000;
3313
+
3314
+ this.refreshInterval = setInterval(async () => {
3315
+ try {
3316
+ console.log('Refreshing access token...');
3317
+ const newToken = await this.fetchNewToken();
3318
+
3319
+ // Update token in both access and controls
3320
+ this.access.setToken(newToken);
3321
+ if (this.controls) {
3322
+ this.controls.refreshToken(newToken);
3323
+ }
3324
+
3325
+ console.log('Token refreshed successfully');
3326
+ } catch (error) {
3327
+ console.error('Token refresh failed:', error);
3328
+ this.handleRefreshFailure(error);
3329
+ }
3330
+ }, refreshTime);
3331
+ }
3332
+
3333
+ stopAutoRefresh() {
3334
+ if (this.refreshInterval) {
3335
+ clearInterval(this.refreshInterval);
3336
+ this.refreshInterval = undefined;
3337
+ }
3338
+ }
3339
+
3340
+ private async fetchNewToken(): Promise<string> {
3341
+ const response = await fetch('https://api.dedot.io/auth/refresh', {
3342
+ method: 'POST',
3343
+ headers: {
3344
+ 'Content-Type': 'application/json'
3345
+ },
3346
+ body: JSON.stringify({
3347
+ // Refresh token payload
3348
+ })
3349
+ });
3350
+
3351
+ if (!response.ok) {
3352
+ throw new Error('Token refresh failed');
3353
+ }
3354
+
3355
+ const data = await response.json();
3356
+ return data.accessToken;
3357
+ }
3358
+
3359
+ private handleRefreshFailure(error: any) {
3360
+ // Log user out or redirect to login
3361
+ console.error('Unable to refresh token. User needs to re-authenticate.');
3362
+ redirectToLogin();
3363
+ }
3364
+ }
3365
+
3366
+ // Usage
3367
+ const tokenManager = new TokenManager(access, 3600000); // 1 hour expiry
3368
+ tokenManager.setControls(controls);
3369
+ tokenManager.startAutoRefresh();
3370
+
3371
+ // Cleanup on app exit
3372
+ onAppExit(() => {
3373
+ tokenManager.stopAutoRefresh();
3374
+ });
3375
+ ```
3376
+
3377
+ ---
3378
+
3379
+ ## Performance Optimization
3380
+
3381
+ ### 1. Batch Operations
3382
+
3383
+ Avoid multiple individual operations when you can batch them.
3384
+
3385
+ ```typescript
3386
+ // ❌ Bad: Multiple individual operations
3387
+ for (const vehicle of vehicles) {
3388
+ await controls.addNearbyEntity(vehicle);
3389
+ }
3390
+
3391
+ // ✅ Good: Single batch operation
3392
+ await controls.showNearby(vehicles);
3393
+ ```
3394
+
3395
+ ### 2. Debounce Frequent Updates
3396
+
3397
+ ```typescript
3398
+ import { debounce } from 'lodash';
3399
+
3400
+ // Debounce vehicle position updates
3401
+ const debouncedUpdatePosition = debounce(
3402
+ async (vehicleId: string, position: Coordinates) => {
3403
+ await controls.moveNearbyEntity({ id: vehicleId, position });
3404
+ },
3405
+ 500, // Update at most every 500ms
3406
+ { leading: true, trailing: true }
3407
+ );
3408
+
3409
+ // In WebSocket message handler
3410
+ websocket.onmessage = (event) => {
3411
+ const { vehicleId, position } = JSON.parse(event.data);
3412
+ debouncedUpdatePosition(vehicleId, position);
3413
+ };
3414
+ ```
3415
+
3416
+ ### 3. Throttle High-Frequency Events
3417
+
3418
+ ```typescript
3419
+ import { throttle } from 'lodash';
3420
+
3421
+ // Throttle navigation updates
3422
+ const throttledNavigate = throttle(
3423
+ async (position: RTLocation) => {
3424
+ await controls.navigate(position);
3425
+ },
3426
+ 1000, // At most once per second
3427
+ { leading: true, trailing: false }
3428
+ );
3429
+
3430
+ // In GPS update loop
3431
+ gpsStream.pipe(position => {
3432
+ throttledNavigate(position);
3433
+ });
3434
+ ```
3435
+
3436
+ ### 4. Cleanup Streams Properly
3437
+
3438
+ ```typescript
3439
+ class StreamManager {
3440
+ private streams: Map<string, Stream> = new Map();
3441
+
3442
+ registerStream(id: string, stream: Stream) {
3443
+ this.streams.set(id, stream);
3444
+
3445
+ // Auto-cleanup on close
3446
+ stream.onclose(() => {
3447
+ this.streams.delete(id);
3448
+ });
3449
+ }
3450
+
3451
+ closeStream(id: string) {
3452
+ const stream = this.streams.get(id);
3453
+ if (stream && stream.isActive()) {
3454
+ stream.close();
3455
+ }
3456
+ this.streams.delete(id);
3457
+ }
3458
+
3459
+ closeAllStreams() {
3460
+ for (const [id, stream] of this.streams) {
3461
+ if (stream.isActive()) {
3462
+ stream.close();
3463
+ }
3464
+ }
3465
+ this.streams.clear();
3466
+ }
3467
+
3468
+ getActiveStreamCount(): number {
3469
+ return Array.from(this.streams.values())
3470
+ .filter(s => s.isActive()).length;
3471
+ }
3472
+ }
3473
+
3474
+ // Usage
3475
+ const streamManager = new StreamManager();
3476
+
3477
+ const locationStream = handles.myLocation('agent');
3478
+ streamManager.registerStream('location', locationStream);
3479
+
3480
+ // Cleanup on component unmount
3481
+ onComponentUnmount(() => {
3482
+ streamManager.closeAllStreams();
3483
+ });
3484
+ ```
3485
+
3486
+ ### 5. Cache Expensive Operations
3487
+
3488
+ ```typescript
3489
+ class LocationCache {
3490
+ private cache = new Map<string, {
3491
+ data: any;
3492
+ timestamp: number;
3493
+ }>();
3494
+ private ttl: number = 300000; // 5 minutes
3495
+
3496
+ async get<T>(
3497
+ key: string,
3498
+ fetcher: () => Promise<T>
3499
+ ): Promise<T> {
3500
+ // Check cache
3501
+ const cached = this.cache.get(key);
3502
+ if (cached && Date.now() - cached.timestamp < this.ttl) {
3503
+ console.log(`Cache hit for ${key}`);
3504
+ return cached.data as T;
3505
+ }
3506
+
3507
+ // Fetch and cache
3508
+ console.log(`Cache miss for ${key}, fetching...`);
3509
+ const data = await fetcher();
3510
+
3511
+ this.cache.set(key, {
3512
+ data,
3513
+ timestamp: Date.now()
3514
+ });
3515
+
3516
+ return data;
3517
+ }
3518
+
3519
+ invalidate(key: string) {
3520
+ this.cache.delete(key);
3521
+ }
3522
+
3523
+ clear() {
3524
+ this.cache.clear();
3525
+ }
3526
+
3527
+ // Auto-cleanup expired entries
3528
+ startAutoCleanup() {
3529
+ setInterval(() => {
3530
+ const now = Date.now();
3531
+ for (const [key, value] of this.cache) {
3532
+ if (now - value.timestamp > this.ttl) {
3533
+ this.cache.delete(key);
3534
+ }
3535
+ }
3536
+ }, 60000); // Every minute
3537
+ }
3538
+ }
3539
+
3540
+ // Usage
3541
+ const cache = new LocationCache();
3542
+ cache.startAutoCleanup();
3543
+
3544
+ // Cache geocoding results
3545
+ const location = await cache.get(
3546
+ `geocode:${address}`,
3547
+ () => controls.resolvePlace(address)
3548
+ );
3549
+
3550
+ // Cache route calculations
3551
+ const route = await cache.get(
3552
+ `route:${origin}-${destination}`,
3553
+ () => calculateRoute(origin, destination)
3554
+ );
3555
+ ```
3556
+
3557
+ ### 6. Lazy Load Components
3558
+
3559
+ ```typescript
3560
+ // Only load MSI when needed
3561
+ class LazyMapLoader {
3562
+ private msiPromise?: Promise<any>;
3563
+
3564
+ async loadWhenNeeded() {
3565
+ if (this.msiPromise) {
3566
+ return this.msiPromise;
3567
+ }
3568
+
3569
+ this.msiPromise = this.initializeMSI();
3570
+ return this.msiPromise;
3571
+ }
3572
+
3573
+ private async initializeMSI() {
3574
+ console.log('Initializing MSI...');
3575
+
3576
+ const msi = new De.MSI({
3577
+ element: 'map-container',
3578
+ accessToken: 'your-token',
3579
+ workspace: 'your-workspace'
3580
+ });
3581
+
3582
+ return await msi.load();
3583
+ }
3584
+ }
3585
+
3586
+ // Usage
3587
+ const mapLoader = new LazyMapLoader();
3588
+
3589
+ // Only load map when user clicks "Track Delivery"
3590
+ button.onclick = async () => {
3591
+ const { controls, handles } = await mapLoader.loadWhenNeeded();
3592
+ // Use controls and handles
3593
+ };
3594
+ ```
3595
+
3596
+ ### 7. Optimize WebSocket Connections
3597
+
3598
+ ```typescript
3599
+ class OptimizedWebSocket {
3600
+ private ws?: WebSocket;
3601
+ private reconnectAttempts = 0;
3602
+ private maxReconnectAttempts = 5;
3603
+ private reconnectDelay = 1000;
3604
+ private messageQueue: any[] = [];
3605
+
3606
+ connect(url: string) {
3607
+ this.ws = new WebSocket(url);
3608
+
3609
+ this.ws.onopen = () => {
3610
+ console.log('WebSocket connected');
3611
+ this.reconnectAttempts = 0;
3612
+
3613
+ // Send queued messages
3614
+ while (this.messageQueue.length > 0) {
3615
+ const message = this.messageQueue.shift();
3616
+ this.send(message);
3617
+ }
3618
+ };
3619
+
3620
+ this.ws.onerror = (error) => {
3621
+ console.error('WebSocket error:', error);
3622
+ };
3623
+
3624
+ this.ws.onclose = () => {
3625
+ console.log('WebSocket closed');
3626
+ this.attemptReconnect(url);
3627
+ };
3628
+ }
3629
+
3630
+ send(message: any) {
3631
+ if (this.ws?.readyState === WebSocket.OPEN) {
3632
+ this.ws.send(JSON.stringify(message));
3633
+ } else {
3634
+ // Queue message for later
3635
+ this.messageQueue.push(message);
3636
+ }
3637
+ }
3638
+
3639
+ private attemptReconnect(url: string) {
3640
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
3641
+ console.error('Max reconnection attempts reached');
3642
+ return;
3643
+ }
3644
+
3645
+ this.reconnectAttempts++;
3646
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
3647
+
3648
+ console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
3649
+
3650
+ setTimeout(() => {
3651
+ this.connect(url);
3652
+ }, delay);
3653
+ }
3654
+
3655
+ close() {
3656
+ if (this.ws) {
3657
+ this.ws.close();
3658
+ this.ws = undefined;
3659
+ }
3660
+ }
3661
+ }
3662
+
3663
+ // Usage
3664
+ const ws = new OptimizedWebSocket();
3665
+ ws.connect('wss://api.dedot.io/fleet/live');
3666
+ ```
3667
+
3668
+ ---
3669
+
3670
+ ## Testing
3671
+
3672
+ ### Unit Testing with Jest
3673
+
3674
+ ```typescript
3675
+ import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
3676
+ import De from '@de./sdk';
3677
+
3678
+ describe('De. SDK - MSI', () => {
3679
+ let msi: De.MSI;
3680
+
3681
+ beforeEach(() => {
3682
+ // Mock DOM element
3683
+ document.body.innerHTML = '<div id="map-container"></div>';
3684
+
3685
+ // Mock fetch for iframe loading
3686
+ global.fetch = jest.fn(() =>
3687
+ Promise.resolve({
3688
+ ok: true,
3689
+ json: () => Promise.resolve({})
3690
+ } as Response)
3691
+ );
3692
+
3693
+ msi = new De.MSI({
3694
+ element: 'map-container',
3695
+ accessToken: 'test-token',
3696
+ workspace: 'test-workspace',
3697
+ env: 'dev'
3698
+ });
3699
+ });
3700
+
3701
+ afterEach(() => {
3702
+ jest.clearAllMocks();
3703
+ });
3704
+
3705
+ it('should initialize MSI with correct options', () => {
3706
+ expect(msi).toBeDefined();
3707
+ expect(msi.isReady()).toBe(false);
3708
+ });
3709
+
3710
+ it('should load MSI and return API components', async () => {
3711
+ const api = await msi.load();
3712
+
3713
+ expect(api.controls).toBeDefined();
3714
+ expect(api.handles).toBeDefined();
3715
+ expect(api.plugins).toBeDefined();
3716
+ });
3717
+
3718
+ it('should emit error event on initialization failure', (done) => {
3719
+ const failingMsi = new De.MSI({
3720
+ element: 'non-existent',
3721
+ accessToken: 'test-token',
3722
+ workspace: 'test-workspace'
3723
+ });
3724
+
3725
+ failingMsi.on('error', (error) => {
3726
+ expect(error).toBeDefined();
3727
+ expect(error.message).toContain('Not Found');
3728
+ done();
3729
+ });
3730
+
3731
+ failingMsi.load().catch(() => {});
3732
+ });
3733
+ });
3734
+
3735
+ describe('De. SDK - Controls', () => {
3736
+ let controls: De.Controls;
3737
+
3738
+ beforeEach(async () => {
3739
+ document.body.innerHTML = '<div id="map-container"></div>';
3740
+
3741
+ const msi = new De.MSI({
3742
+ element: 'map-container',
3743
+ accessToken: 'test-token',
3744
+ workspace: 'test-workspace',
3745
+ env: 'dev'
3746
+ });
3747
+
3748
+ const api = await msi.load();
3749
+ controls = api.controls;
3750
+ });
3751
+
3752
+ it('should get current location', async () => {
3753
+ // Mock getCurrentLocation
3754
+ const mockLocation = {
3755
+ latitude: 40.7128,
3756
+ longitude: -74.0060,
3757
+ accuracy: 10,
3758
+ heading: 180,
3759
+ speed: 0
3760
+ };
3761
+
3762
+ jest.spyOn(controls, 'getCurrentLocation')
3763
+ .mockResolvedValue(mockLocation);
3764
+
3765
+ const location = await controls.getCurrentLocation();
3766
+
3767
+ expect(location).toEqual(mockLocation);
3768
+ expect(location?.latitude).toBe(40.7128);
3769
+ });
3770
+
3771
+ it('should set route with journey', async () => {
3772
+ const journey: De.Journey = {
3773
+ routeId: 'test-route',
3774
+ origin: {
3775
+ coords: [40.7128, -74.0060],
3776
+ caption: { label: 'Origin' }
3777
+ },
3778
+ destination: {
3779
+ coords: [40.7580, -73.9855],
3780
+ caption: { label: 'Destination' }
3781
+ }
3782
+ };
3783
+
3784
+ jest.spyOn(controls, 'setRoute').mockResolvedValue();
3785
+
3786
+ await controls.setRoute(journey);
3787
+
3788
+ expect(controls.setRoute).toHaveBeenCalledWith(journey);
3789
+ });
3790
+ });
3791
+
3792
+ describe('De. SDK - Handles', () => {
3793
+ let handles: De.Handles;
3794
+
3795
+ beforeEach(async () => {
3796
+ document.body.innerHTML = '<div id="map-container"></div>';
3797
+
3798
+ const msi = new De.MSI({
3799
+ element: 'map-container',
3800
+ accessToken: 'test-token',
3801
+ workspace: 'test-workspace',
3802
+ env: 'dev'
3803
+ });
3804
+
3805
+ const api = await msi.load();
3806
+ handles = api.handles;
3807
+ });
3808
+
3809
+ it('should create location stream', () => {
3810
+ const stream = handles.myLocation('agent');
3811
+
3812
+ expect(stream).toBeDefined();
3813
+ expect(typeof stream.pipe).toBe('function');
3814
+ expect(typeof stream.close).toBe('function');
3815
+ });
3816
+
3817
+ it('should handle location updates in stream', (done) => {
3818
+ const stream = handles.myLocation('agent');
3819
+ const mockLocation = {
3820
+ latitude: 40.7128,
3821
+ longitude: -74.0060,
3822
+ accuracy: 10
3823
+ };
3824
+
3825
+ stream.pipe(location => {
3826
+ expect(location).toEqual(mockLocation);
3827
+ stream.close();
3828
+ done();
3829
+ });
3830
+
3831
+ // Simulate location update
3832
+ stream.write(mockLocation);
3833
+ });
3834
+ });
3835
+
3836
+ describe('De. SDK - Access', () => {
3837
+ let access: De.Access;
3838
+
3839
+ beforeEach(() => {
3840
+ access = new De.Access({
3841
+ workspace: 'test-workspace',
3842
+ accessToken: 'test-token',
3843
+ env: 'dev',
3844
+ version: 1
3845
+ });
3846
+
3847
+ // Mock fetch
3848
+ global.fetch = jest.fn();
3849
+ });
3850
+
3851
+ it('should make GET request', async () => {
3852
+ const mockResponse = { vehicles: [] };
3853
+
3854
+ (global.fetch as jest.Mock).mockResolvedValue({
3855
+ ok: true,
3856
+ json: () => Promise.resolve(mockResponse)
3857
+ });
3858
+
3859
+ const result = await access.request({
3860
+ url: '/fleet/vehicles',
3861
+ method: 'GET'
3862
+ });
3863
+
3864
+ expect(result).toEqual(mockResponse);
3865
+ expect(global.fetch).toHaveBeenCalledWith(
3866
+ expect.stringContaining('/fleet/vehicles'),
3867
+ expect.objectContaining({
3868
+ method: 'GET'
3869
+ })
3870
+ );
3871
+ });
3872
+
3873
+ it('should make POST request with body', async () => {
3874
+ const mockBody = { name: 'Test Order' };
3875
+ const mockResponse = { id: 'order-123' };
3876
+
3877
+ (global.fetch as jest.Mock).mockResolvedValue({
3878
+ ok: true,
3879
+ json: () => Promise.resolve(mockResponse)
3880
+ });
3881
+
3882
+ const result = await access.request({
3883
+ url: '/orders',
3884
+ method: 'POST',
3885
+ body: mockBody
3886
+ });
3887
+
3888
+ expect(result).toEqual(mockResponse);
3889
+ expect(global.fetch).toHaveBeenCalledWith(
3890
+ expect.any(String),
3891
+ expect.objectContaining({
3892
+ method: 'POST',
3893
+ body: JSON.stringify(mockBody)
3894
+ })
3895
+ );
3896
+ });
3897
+
3898
+ it('should update access token', () => {
3899
+ const newToken = 'new-test-token';
3900
+ access.setToken(newToken);
3901
+
3902
+ // Verify token was updated (implementation-specific test)
3903
+ expect(access).toBeDefined();
3904
+ });
3905
+ });
3906
+ ```
3907
+
3908
+ ### Integration Testing
3909
+
3910
+ ```typescript
3911
+ describe('De. SDK - Integration Tests', () => {
3912
+ let msi: De.MSI;
3913
+ let access: De.Access;
3914
+ let controls: De.Controls;
3915
+ let handles: De.Handles;
3916
+
3917
+ beforeAll(async () => {
3918
+ // Setup full environment
3919
+ document.body.innerHTML = '<div id="map-container"></div>';
3920
+
3921
+ access = new De.Access({
3922
+ workspace: 'test-workspace',
3923
+ accessToken: 'test-token',
3924
+ env: 'dev'
3925
+ });
3926
+
3927
+ msi = new De.MSI({
3928
+ element: 'map-container',
3929
+ accessToken: 'test-token',
3930
+ workspace: 'test-workspace',
3931
+ env: 'dev'
3932
+ });
3933
+
3934
+ const api = await msi.load();
3935
+ controls = api.controls;
3936
+ handles = api.handles;
3937
+ });
3938
+
3939
+ it('should complete full delivery flow', async () => {
3940
+ // 1. Create order via API
3941
+ const order = await access.request({
3942
+ url: '/orders',
3943
+ method: 'POST',
3944
+ body: {
3945
+ pickupLocation: [40.7128, -74.0060],
3946
+ deliveryLocation: [40.7580, -73.9855]
3947
+ }
3948
+ });
3949
+
3950
+ expect(order).toBeDefined();
3951
+
3952
+ // 2. Set route on map
3953
+ await handles.pickupPoint(
3954
+ [40.7128, -74.0060],
3955
+ { label: 'Pickup' }
3956
+ );
3957
+
3958
+ await handles.dropoffPoint(
3959
+ [40.7580, -73.9855],
3960
+ { label: 'Delivery' }
3961
+ );
3962
+
3963
+ // 3. Track vehicle
3964
+ const vehicle = { id: 'test-vehicle', location: [40.7128, -74.0060] };
3965
+ const vehicleStream = handles.peerLocation(
3966
+ vehicle.location,
3967
+ { label: 'Driver' }
3968
+ );
3969
+
3970
+ expect(vehicleStream).toBeDefined();
3971
+
3972
+ // Cleanup
3973
+ vehicleStream.close();
3974
+ });
3975
+
3976
+ afterAll(() => {
3977
+ // Cleanup
3978
+ if (msi) {
3979
+ // Close all streams and cleanup
3980
+ }
3981
+ });
3982
+ });
3983
+ ```
3984
+
3985
+ ---
3986
+
3987
+ ## TypeScript Types Reference
3988
+
3989
+ ### Core Types
3990
+
3991
+ ```typescript
3992
+ // HTTP Request Types
3993
+ interface HTTPRequestOptions {
3994
+ url: string;
3995
+ method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
3996
+ headers?: { [index: string]: string };
3997
+ body?: any;
3998
+ }
3999
+
4000
+ interface HTTPResponse {
4001
+ error: boolean;
4002
+ message?: string;
4003
+ }
4004
+
4005
+ // Coordinates
4006
+ type LngLat = [number, number]; // [longitude, latitude]
4007
+
4008
+ interface Coordinates {
4009
+ lng: number; // longitude
4010
+ lat: number; // latitude
4011
+ }
4012
+
4013
+ interface RTLocation extends Coordinates {
4014
+ heading?: number; // degrees (0-360)
4015
+ }
4016
+
4017
+ interface PickedLocation {
4018
+ point: {
4019
+ x: number;
4020
+ y: number;
4021
+ };
4022
+ coordinates: Coordinates;
4023
+ }
4024
+
4025
+ interface ActivePosition {
4026
+ id: string;
4027
+ position: RTLocation;
4028
+ caption?: Caption;
4029
+ }
4030
+
4031
+ // Captions
4032
+ interface Caption {
4033
+ duration?: number;
4034
+ unit?: string;
4035
+ label?: string;
4036
+ }
4037
+
4038
+ // Waypoints
4039
+ interface MapWaypoint {
4040
+ index?: number;
4041
+ coords: Coordinates;
4042
+ caption?: Caption;
4043
+ }
4044
+
4045
+ interface Waypoint {
4046
+ no: number;
4047
+ type: 'pickup' | 'dropoff';
4048
+ description: string;
4049
+ coordinates: Coordinates;
4050
+ address?: string;
4051
+ contact: {
4052
+ type: string;
4053
+ reference: string;
4054
+ phone?: string;
4055
+ email?: string;
4056
+ };
4057
+ }
4058
+
4059
+ type WaypointIndex = 'origin' | 'destination' | number;
4060
+
4061
+ interface WaypointOptions {
4062
+ no?: number;
4063
+ type?: 'pickup' | 'dropoff';
4064
+ description?: string;
4065
+ coordinates?: Coordinates;
4066
+ address?: string;
4067
+ 'contact.type'?: string;
4068
+ 'contact.reference'?: string;
4069
+ 'contact.phone'?: string;
4070
+ 'contact.email'?: string;
4071
+ }
4072
+
4073
+ // Journey & Routes
4074
+ interface Journey {
4075
+ routeId: string | number;
4076
+ origin?: MapWaypoint;
4077
+ destination?: MapWaypoint;
4078
+ waypoints?: MapWaypoint[];
4079
+ options?: RouteOptions;
4080
+ }
4081
+
4082
+ interface ActiveDirection {
4083
+ routeId: string | number;
4084
+ profile: string;
4085
+ origin?: Coordinates;
4086
+ destination?: Coordinates;
4087
+ waypoints: Coordinates[];
4088
+ route: any;
4089
+ }
4090
+
4091
+ type RouteID = string | number;
4092
+
4093
+ interface RoutesFitBoundsOptions {
4094
+ includes?: RouteID[];
4095
+ margin?: number;
4096
+ }
4097
+
4098
+ interface RouteOptions {
4099
+ id?: string | number;
4100
+ mode?: 'default' | 'navigation';
4101
+ profile?: 'driving-traffic' | 'driving' | 'cycling' | 'biking' | 'walking' | 'transit';
4102
+ unit?: 'metric' | 'imperial';
4103
+ preference?: 'TRAFFIC_AWARE' | 'TRAFFIC_UNAWARE';
4104
+ pointless?: boolean;
4105
+ styles?: any;
4106
+ animation?: AnimatedRouteOptions;
4107
+ }
4108
+
4109
+ // Animated Routes
4110
+ type AnimatedRouteNativePathType = 'dot' | 'solid';
4111
+
4112
+ type AnimatedRouteNativeMethods =
4113
+ | 'dot:flow'
4114
+ | 'dot:fade'
4115
+ | 'dot:pulse'
4116
+ | 'dot:directional'
4117
+ | 'solid:flow'
4118
+ | 'solid:fade'
4119
+ | 'solid:pulse'
4120
+ | 'solid:directional';
4121
+
4122
+ interface AnimatedRouteRules {
4123
+ styles?: any;
4124
+ speed?: number; // pixels per frame
4125
+ fadeLength?: number; // length of fade effect in pixels
4126
+ }
4127
+
4128
+ interface AnimatedRoute {
4129
+ key: number | null;
4130
+ currentOffset: number;
4131
+ rules: Required<AnimatedRouteRules>;
4132
+ startTime?: number;
4133
+ polyline?: any;
4134
+ engine: any;
4135
+ path: Coordinates[];
4136
+
4137
+ create(pathType?: AnimatedRouteNativePathType): void;
4138
+ apply: Record<string, () => void>;
4139
+ stop(): void;
4140
+ remove(): void;
4141
+ }
4142
+
4143
+ type AnimatedRouteOptions = AnimatedRouteNativeMethods | {
4144
+ handler?: new (engine: Engine, path: Coordinates[], rules?: AnimatedRouteRules) => AnimatedRoute;
4145
+ method?: string;
4146
+ rules?: AnimatedRouteRules;
4147
+ };
4148
+
4149
+ // Search
4150
+ interface SearchPlace {
4151
+ name: string;
4152
+ location: Coordinates;
4153
+ address: string;
4154
+ }
4155
+
4156
+ // Entities
4157
+ interface Entity {
4158
+ id: string;
4159
+ status: 'ACTIVE' | 'BUSY';
4160
+ grade: '1H' | '2H' | '3H';
4161
+ currentLocation: RTLocation;
4162
+ static?: boolean;
4163
+ type: 'moto' | 'car' | 'bike' | 'truck' | 'plane' | 'ship' | 'restaurant' | 'hotel' | 'store' | 'office' | 'warehouse';
4164
+ }
4165
+
4166
+ interface EntitySpecs {
4167
+ id: string;
4168
+ status: 'ACTIVE' | 'BUSY';
4169
+ grade: '1H' | '2H' | '3H';
4170
+ currentLocation: RTLocation;
4171
+ static?: boolean;
4172
+ type: 'moto' | 'car' | 'bike' | 'truck' | 'plane' | 'ship' | 'restaurant' | 'hotel' | 'store' | 'office' | 'warehouse';
4173
+ }
4174
+
4175
+ // Map
4176
+ interface MapOptions {
4177
+ element: string;
4178
+ accessToken: string;
4179
+ version?: number;
4180
+ env?: 'dev' | 'prod';
4181
+ }
4182
+
4183
+ type MapLayerStyle = 'streets' | 'outdoors' | 'light' | 'dark' | 'satellite';
4184
+
4185
+ // User Location
4186
+ interface UserLocationOptions {
4187
+ // Base point styling options
4188
+ borderRadius?: number;
4189
+ borderColor?: string;
4190
+ borderOpacity?: number;
4191
+ dotColor?: string;
4192
+ showInnerDot?: boolean;
4193
+ noRing?: boolean;
4194
+
4195
+ // User location specific options
4196
+ showDirectionArrow?: boolean;
4197
+ arrowColor?: string;
4198
+ arrowSize?: number;
4199
+ pulseAnimation?: boolean;
4200
+ accuracyCircle?: boolean;
4201
+ accuracyColor?: string;
4202
+ accuracyOpacity?: number;
4203
+
4204
+ // Callbacks
4205
+ onLocationUpdate?: (location: RTLocation) => void;
4206
+ onLocationError?: (error: GeolocationPositionError) => void;
4207
+ }
4208
+
4209
+ // Drag Pick
4210
+ interface DragPickOptions {
4211
+ snapToRoad?: boolean;
4212
+ pinPoints?: boolean;
4213
+ pointOptions?: CustomPointOptions;
4214
+ }
4215
+
4216
+ type DragPickContentType = 'duration' | 'distance' | 'preloader';
4217
+
4218
+ interface DragPickContent {
4219
+ time?: number;
4220
+ unit?: 'min' | 'sec' | 'hr' | 'km' | 'mi' | 'm';
4221
+ distance?: number;
4222
+ preloader?: boolean;
4223
+ }
4224
+
4225
+ type DragPickEvent = 'dragstart' | 'dragend' | 'zoom_changed' | 'idle';
4226
+
4227
+ interface DragPickInterface {
4228
+ enable(origin?: Coordinates): void;
4229
+ disable(): void;
4230
+ content(type: DragPickContentType, content: DragPickContent): void;
4231
+ }
4232
+
4233
+ // Package
4234
+ interface Package {
4235
+ waypointNo: number;
4236
+ careLevel: number;
4237
+ category: string;
4238
+ weight: number;
4239
+ note?: string;
4240
+ }
4241
+
4242
+ interface PackageOptions {
4243
+ waypointNo?: number;
4244
+ careLevel?: number;
4245
+ category?: string;
4246
+ weight?: number;
4247
+ note?: string;
4248
+ }
4249
+
4250
+ // Order Service
4251
+ type PaymentMode = 'cash' | 'card' | 'momo' | 'wigo';
4252
+
4253
+ interface OrderService {
4254
+ fees: {
4255
+ total: {
4256
+ amount: number;
4257
+ currency: string;
4258
+ };
4259
+ tax: number;
4260
+ discount: number;
4261
+ };
4262
+ payment: {
4263
+ mode: PaymentMode;
4264
+ paid: boolean;
4265
+ };
4266
+ xpress: string;
4267
+ }
4268
+
4269
+ interface OrderServiceOptions {
4270
+ 'fees.total.amount'?: number;
4271
+ 'fees.total.currency'?: string;
4272
+ 'fees.tax'?: string;
4273
+ 'fees.discount'?: string;
4274
+ 'payment.mode'?: PaymentMode;
4275
+ 'payment.option'?: string;
4276
+ 'payment.paid'?: boolean;
4277
+ xpress?: string;
4278
+ }
4279
+
4280
+ interface OrderOperator {}
4281
+
4282
+ interface OrderStage {
4283
+ current: string;
4284
+ status: string;
4285
+ }
4286
+
4287
+ // Messages
4288
+ interface Message {
4289
+ type: 'text' | 'location' | 'media';
4290
+ sender: string;
4291
+ content: string;
4292
+ timestamp: string;
4293
+ }
4294
+
4295
+ interface Peer {
4296
+ utype: string;
4297
+ id: string;
4298
+ }
4299
+ ```
4300
+
4301
+ ### Authentication Types
4302
+
4303
+ ```typescript
4304
+ interface AuthOptions {
4305
+ env?: 'dev' | 'prod';
4306
+ version?: number;
4307
+ autorefresh?: boolean;
4308
+ onNewToken?: (token: string) => void;
4309
+ }
4310
+
4311
+ interface AuthCredentials {
4312
+ workspace: string;
4313
+ remoteOrigin: string;
4314
+ cid: string;
4315
+ secret: string;
4316
+ }
4317
+
4318
+ interface SocketAuthCredentials {
4319
+ utype: string;
4320
+ id: string;
4321
+ remoteOrigin: string;
4322
+ accessToken: string;
4323
+ }
4324
+
4325
+ interface AuthRequestOptions {
4326
+ url: string;
4327
+ method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
4328
+ headers?: { [index: string]: string };
4329
+ body?: any;
4330
+ }
4331
+
4332
+ interface AccessOptions {
4333
+ env?: 'dev' | 'prod';
4334
+ version?: number;
4335
+ workspace: string;
4336
+ accessToken: string;
4337
+ remoteOrigin?: string;
4338
+ }
4339
+ ```
4340
+
4341
+ ---
4342
+
4343
+ ## Support & Resources
4344
+
4345
+ - **Official Documentation**: https://docs.dedot.io
4346
+ - **API Reference**: https://docs.dedot.io/api
4347
+ - **Developer Portal**: https://developers.dedot.io
4348
+ - **GitHub**: https://github.com/dedot
4349
+ - **Community Forum**: https://community.dedot.io
4350
+ - **Support Email**: support@dedot.io
4351
+ - **Status Page**: https://status.dedot.io
4352
+
4353
+ ---
4354
+
4355
+ ## Changelog
4356
+
4357
+ ### Version 2.0.0 (Current)
4358
+ - ✨ Added Handles API with streaming support
4359
+ - ✨ Introduced comprehensive Plugin system
4360
+ - ✨ Enhanced Access client with automatic retry and token management
4361
+ - ✨ Added full TypeScript type definitions
4362
+ - ✨ Improved error handling with 12-second timeout protection
4363
+ - ✨ Added support for multi-stop route optimization
4364
+ - ✨ Enhanced nearby entity management with live controls
4365
+ - 🐛 Fixed iframe communication stability issues
4366
+ - 📚 Complete documentation overhaul
4367
+
4368
+ ### Version 1.x
4369
+ - 🎉 Initial release with Controls API
4370
+ - 🗺️ Basic map integration via iframe
4371
+ - 📍 Location services (GPS, geocoding)
4372
+ - 🚗 Simple vehicle tracking
4373
+ - 🛣️ Basic routing capabilities
4374
+
4375
+ ---
4376
+
4377
+ ## License
4378
+
4379
+ MIT License - See LICENSE file for details
4380
+
4381
+ ---
4382
+
4383
+ *Last Updated: October 2025*
4384
+ *SDK Version: 2.0.0*
4385
+ *Compatible with: MSI Gateway v3.0+, De. Platform API v1.0+*
4386
+
4387
+ ---
4388
+
4389
+ **Built by the De. Team**
4390
+
4391
+ *Making logistics as simple as flipping a switch.*