@de./sdk
v1.0.4
Published
Complete SDK pack for MSI (Map Service Interface), IoTClient and DClient services
Readme
De. Platform Developer Documentation
Overview
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.
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.
Table of Contents
- Getting Started
- Architecture Overview
- Installation & Setup
- Core Components
- API Reference
- Code Examples
- Plugin Development
- Advanced Usage Patterns
- Error Handling
- Performance Optimization
- Testing
Getting Started
The De. SDK provides three main modules:
- MSI (Map System Interface): Complete map integration with iframe-based communication for visual delivery tracking
- DClient: Client-side operations for orders, events, and customer management
- Access: API authentication and HTTP request management
Who Uses De.?
Logistics Service Providers (LSP)
- Carriers, 3PLs, 4PLs, Forwarders, Brokers
- Get better coordination tools, more business opportunities
- Real-time fleet tracking and route optimization
Commerce Service Providers (CSP)
- Retailers, Manufacturers, E-commerce platforms
- Seamless delivery integration for customers
- Multi-carrier coordination without building from scratch
Developers (DEV)
- Build delivery features into applications
- Access comprehensive logistics network
- Extensible plugin system for custom functionality
End User Service (EUS)
- Customers tracking their deliveries
- Real-time visibility into package location
- Delivery preference management
Prerequisites
- Node.js 14+ or modern browser environment
- TypeScript 4.0+ (recommended)
- Valid De. workspace and access token
- Basic understanding of Promises and async/await
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Your Application │
│ (E-commerce, Logistics Dashboard, Mobile App, etc.) │
└────────────────────┬────────────────────────────────────┘
│
┌────────────┴───────────────┐
│ De. SDK │
├────────────────────────────┤
│ │
│ ┌──────────────────────┐ │
│ │ MSI (Map System) │ │
│ │ ┌────────────────┐ │ │
│ │ │ Controls │ │ │ ← Promise-based direct operations
│ │ ├────────────────┤ │ │
│ │ │ Handles │ │ │ ← Stream-based high-level API
│ │ ├────────────────┤ │ │
│ │ │ Plugins │ │ │ ← Extensibility system
│ │ └────────────────┘ │ │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ DClient │ │
│ │ - Order │ │ ← Order management
│ │ - Event │ │ ← Event tracking
│ │ - Client │ │ ← Customer operations
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ Access │ │ ← API authentication
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ │
│ │ Utils │ │ ← Utility functions
│ └──────────────────────┘ │
└────────────────────────────┘
│
▼
┌──────────────────┐
│ Iframe (MSI) │
│ Map Engine │ ← Isolated rendering environment
│ (MapBox/Google) │
└──────────────────┘
│
▼
┌──────────────────┐
│ De. Platform │
│ - Logistics API │
│ - Route Engine │
│ - Coordination │
└──────────────────┘Component Responsibilities
| Component | Purpose | Use Cases | |-----------|---------|-----------| | MSI | Map visualization & interaction | Delivery tracking, route planning, location selection | | Controls | Direct map manipulation (Promise-based) | Set routes, update markers, control navigation | | Handles | High-level workflows (Stream-based) | Real-time tracking, fleet management, live updates | | Plugins | Custom extensions | Analytics, custom markers, third-party integrations | | DClient | Order & customer management | Create orders, track status, manage clients | | Access | API communication | Authenticated requests, token management | | Utils | Helper functions | Distance calculation, formatting, validation |
Communication Flow
Your App → Access → De. API → Backend Services
↓
Your App → MSI → Iframe → Map Engine
↓ ↓
Your App ← Controls/Handles ← Map EventsInstallation & Setup
NPM Installation
npm install @de./sdkCDN Installation
<script src="https://cdn.dedot.io/sdk/v2/dedot.min.js"></script>Basic Initialization
import De from '@de./sdk';
// Initialize MSI for map visualization
const msi = new De.MSI({
element: 'map-container', // DOM element ID
accessToken: 'your-access-token',
env: 'prod', // or 'dev'
version: 1 // API version (optional)
});
// Load and get access to all components
const { controls, handles, plugins } = await msi.load();
console.log('De. SDK initialized successfully!');Full Stack Setup
import De from '@de./sdk';
// 1. Initialize Access for API operations
const access = new De.Access({
workspace: 'your-workspace-id',
accessToken: 'your-access-token',
remoteOrigin: window.location.origin,
env: 'prod', // 'dev' | 'prod'
version: 1 // API version (optional)
});
// 2. Initialize MSI for map interface
const msi = new De.MSI({
element: 'map-container',
accessToken: 'your-access-token',
env: 'prod',
version: 1
});
// 3. Load MSI components
const { controls, handles, plugins } = await msi.load();
// 4. Initialize DClient for order management
const { Client, Order, Event } = De.DClient;
// Now you have full access to:
// - controls: Direct map operations
// - handles: Stream-based high-level API
// - plugins: Custom extensions
// - access: Authenticated API calls
// - DClient: Order/customer managementConfiguration Options
interface MapOptions {
element: string; // Required: DOM element ID
accessToken: string; // Required: Authentication token
env?: 'dev' | 'prod'; // Environment (default: 'prod')
version?: number; // API version (default: 1)
}
interface AccessOptions {
workspace: string; // Required: Workspace identifier
accessToken: string; // Required: Authentication token
remoteOrigin?: string; // Optional: Origin for CORS
env?: 'dev' | 'prod'; // Environment (default: 'prod')
version?: number; // API version (default: 1)
}Quick Start Example
// Initialize
const msi = new De.MSI({
element: 'map-container',
accessToken: 'your-token',
env: 'prod'
});
const { controls, handles } = await msi.load();
// Set delivery route
await handles.pickupPoint(
{ lng: -74.0060, lat: 40.7128 },
{ label: 'Warehouse A', duration: 5, unit: 'min' }
);
await handles.dropoffPoint(
{ lng: -73.9855, lat: 40.7580 },
{ label: 'Customer' }
);
// Track delivery vehicle
const vehicleStream = handles.nearby([{
id: 'vehicle-1',
type: 'car',
status: 'ACTIVE',
grade: '2H',
currentLocation: { lng: -74.0050, lat: 40.7200 }
}]);
console.log('Delivery tracking active!');Core Components
1. MSI (Map System Interface)
The main orchestrator that embeds the map interface via iframe and provides access to all sub-components.
Constructor
const msi = new De.MSI(options: MapOptions);Key Methods
load(): Promise<MSIInterface>
Initializes the iframe, establishes communication, and returns all components.
const { controls, handles, plugins } = await msi.load();
// Returns:
interface MSIInterface {
controls: Controls; // Direct map operations
handles: Handles; // Stream-based workflows
plugins: Plugins; // Extension system
}isReady(): boolean
Check if MSI is loaded and ready for operations.
if (msi.isReady()) {
await controls.getCurrentLocation();
}plugin<T>(name: string, fn: Plugin<T>)
Register custom plugins before loading MSI.
// Register plugin
msi.plugin('deliveryAnalytics', (hooks, options) => {
return {
trackDelivery: (orderId) => {
// Implementation
}
};
});
// Use after loading
const { plugins } = await msi.load();
const analytics = plugins.use('deliveryAnalytics');
await analytics.trackDelivery('order-123');Events
// Listen to MSI lifecycle events
msi.on('ready', () => {
console.log('MSI is ready for interaction');
});
msi.on('error', (error: Error) => {
console.error('MSI error:', error);
// Handle initialization errors
});
msi.on('loaded', (channel: IOF) => {
console.log('Iframe loaded and communication established');
});Best Practices
- Always wait for load before using controls/handles
- Handle errors during initialization
- Register plugins before calling
load() - Clean up on component unmount
// React example
useEffect(() => {
const msi = new De.MSI(config);
let api: MSIInterface;
msi.on('error', handleError);
msi.load()
.then(loadedApi => {
api = loadedApi;
// Use controls, handles, plugins
})
.catch(handleError);
return () => {
// Cleanup
api?.handles.removeListeners();
};
}, []);2. Controls
Promise-based API for direct map manipulation. All operations return Promises with 12-second timeout protection.
Location Services
getCurrentLocation(): Promise<RTLocation | null>
Get user's current GPS location with accuracy, heading, and speed.
const location = await controls.getCurrentLocation();
console.log(location);
// {
// latitude: 40.7128,
// longitude: -74.0060,
// accuracy: 10, // meters
// heading: 180, // degrees (0-360)
// speed: 15 // meters/second
// }pinCurrentLocation(): Promise<Coordinates | null>
Display user's location on map with a marker.
const coords = await controls.pinCurrentLocation();
// Returns: [latitude, longitude] or nulltrackLiveLocation(): Promise<void>
Start continuous location tracking.
// Configure tracking
await controls.setLiveLocationOptions({
accuracy: 'high', // 'high' | 'medium' | 'low'
updateInterval: 5000, // milliseconds
showAccuracyCircle: true
});
// Start tracking
await controls.trackLiveLocation();
// Stop when done
await controls.untrackLiveLocation();Map Styling
setMapStyle(style: MapLayerStyle): Promise<void>
Change map appearance.
await controls.setMapStyle('dark');
// Options: 'streets' | 'outdoors' | 'light' | 'dark' | 'satellite'Search & Geocoding
searchQuery(input: string): Promise<string[]>
Search for places or addresses.
const suggestions = await controls.searchQuery('coffee shops near me');
// Returns: ['Starbucks - 123 Main St', 'Dunkin - 456 Park Ave', ...]searchSelect(index: number): Promise<SearchPlace | null>
Get details of a search result.
const place = await controls.searchSelect(0);
console.log(place);
// {
// name: 'Starbucks',
// location: { lng: -73.985428, lat: 40.748817 },
// address: '123 Main St, New York, NY'
// }resolvePlace(name: string): Promise<Coordinates | null>
Get coordinates from place name (geocoding).
const coords = await controls.resolvePlace('Empire State Building');
// Returns: { lng: -73.985428, lat: 40.748817 }resolveCoordinates(coords: Coordinates): Promise<any | null>
Get place details from coordinates (reverse geocoding).
const place = await controls.resolveCoordinates({ lng: -74.0060, lat: 40.7128 });
console.log(place);
// {
// name: 'Lower Manhattan',
// address: 'Manhattan, New York, NY',
// postalCode: '10007',
// ...
// }Drag & Pick Location
enableDragPickLocation(initialLocation?: Coordinates): Promise<void>
Enable interactive location picker.
// Enable with initial position
await controls.enableDragPickLocation({ lng: -74.0060, lat: 40.7128 });
// Listen for picked location
controls.on('pick:location', (location) => {
console.log('User picked:', location.coordinates);
console.log('Point:', location.point);
});
// Disable when done
await controls.disableDragPickLocation();setDragPickContent(type: DragPickContentType, content: DragPickContent): Promise<void>
Customize the drag picker content display.
// Show duration
await controls.setDragPickContent('duration', {
time: 15,
unit: 'min'
});
// Show distance
await controls.setDragPickContent('distance', {
distance: 5.2,
unit: 'km'
});
// Show preloader
await controls.setDragPickContent('preloader', {
preloader: true
});Route Management
setRoute(journey: Journey): Promise<void>
Create a complete route with origin, waypoints, and destination.
await controls.setRoute({
routeId: 'delivery-route-1',
origin: {
coords: { lng: -74.0060, lat: 40.7128 },
caption: {
label: 'Warehouse',
duration: 5,
unit: 'min'
}
},
destination: {
coords: { lng: -73.9855, lat: 40.7580 },
caption: {
label: 'Customer',
duration: 15,
unit: 'min'
}
},
waypoints: [
{
coords: { lng: -73.9680, lat: 40.7489 },
caption: { label: 'Stop 1: Package Pickup' },
index: 0
}
],
options: {
mode: 'navigation',
profile: 'driving-traffic',
unit: 'metric',
preference: 'TRAFFIC_AWARE',
animation: 'dot:flow'
}
});Route Options Explained:
interface RouteOptions {
id?: string | number;
mode?: 'default' | 'navigation';
// Profile determines routing algorithm
profile?: 'driving-traffic' // Real-time traffic (recommended)
| 'driving' // Standard driving
| 'cycling' // Bicycle routes
| 'biking' // Bike paths
| 'walking' // Pedestrian routes
| 'transit'; // Public transport
unit?: 'metric' | 'imperial';
// Traffic preference
preference?: 'TRAFFIC_AWARE' // Use real-time traffic
| 'TRAFFIC_UNAWARE'; // Ignore traffic
pointless?: boolean; // Hide waypoint markers
styles?: any; // Custom route styling
// Animated route options
animation?: 'dot:flow' // Flowing dots
| 'dot:fade' // Fading dots
| 'dot:pulse' // Pulsing dots
| 'dot:directional' // Directional dots
| 'solid:flow' // Flowing solid line
| 'solid:fade' // Fading solid line
| 'solid:pulse' // Pulsing solid line
| 'solid:directional' // Directional solid line
| AnimatedRouteCustomOptions;
}
// Custom animation options
interface AnimatedRouteCustomOptions {
handler?: new (engine: Engine, path: Coordinates[], rules?: AnimatedRouteRules) => AnimatedRoute;
method?: string;
rules?: {
styles?: any;
speed?: number; // pixels per frame
fadeLength?: number; // fade effect length in pixels
};
}setRouteOrigin(routeId: string, point: MapWaypoint): Promise<void>
Set or update route starting point.
await controls.setRouteOrigin('route-1', {
coords: [40.7128, -74.0060],
caption: { label: 'Warehouse A' }
});setRouteDestination(routeId: string, point: MapWaypoint): Promise<void>
Set or update route ending point.
await controls.setRouteDestination('route-1', {
coords: [40.7580, -73.9855],
caption: { label: 'Customer Delivery' }
});addRouteWaypoint(routeId: string, point: MapWaypoint): Promise<void>
Add stop to existing route.
await controls.addRouteWaypoint('route-1', {
coords: [40.7489, -73.9680],
caption: {
label: 'Additional Stop',
description: 'Package pickup at retail store'
}
});updateRouteWaypoint(routeId: string, point: MapWaypoint): Promise<void>
Update existing waypoint.
await controls.updateRouteWaypoint('route-1', {
index: 0,
coords: [40.7500, -73.9700],
caption: { label: 'Updated Stop Location' }
});removeRouteWaypoint(routeId: string, index: number): Promise<void>
Remove waypoint from route.
await controls.removeRouteWaypoint('route-1', 0);fitRouteBounds(routeId: string, margin?: number): Promise<void>
Adjust map view to show entire route.
await controls.fitRouteBounds('route-1', 100); // 100px marginfitRoutesBounds(options: RoutesFitBoundsOptions): Promise<void>
Fit multiple routes in view simultaneously.
await controls.fitRoutesBounds({
includes: ['route-1', 'route-2', 'route-3'],
margin: 80
});Nearby Entities
showNearby(entities: EntitySpecs[]): Promise<void>
Display multiple entities (vehicles, warehouses, etc.) on map.
await controls.showNearby([
{
id: 'vehicle-1',
type: 'car',
status: 'ACTIVE',
grade: '2H',
currentLocation: { lng: -74.0060, lat: 40.7128 }
},
{
id: 'warehouse-1',
type: 'warehouse',
status: 'ACTIVE',
grade: '1H',
currentLocation: { lng: -74.0100, lat: 40.7200 },
static: true
}
]);addNearbyEntity(entity: EntitySpecs): Promise<void>
Add single entity to map.
await controls.addNearbyEntity({
id: 'truck-5',
type: 'truck',
status: 'BUSY',
grade: '3H',
currentLocation: { lng: -74.0200, lat: 40.7300 }
});moveNearbyEntity(update: ActivePosition): Promise<void>
Update entity's position (for real-time tracking).
await controls.moveNearbyEntity({
id: 'vehicle-1',
position: { lng: -74.0065, lat: 40.7130, heading: 90 },
caption: { label: 'Van #101', duration: 5, unit: 'min' }
});removeNearbyEntity(id: string): Promise<void>
Remove specific entity from map.
await controls.removeNearbyEntity('vehicle-1');removeNearby(): Promise<void>
Clear all nearby entities.
await controls.removeNearby();Navigation
mountNavigation(routeId: string): Promise<void>
Prepare route for turn-by-turn navigation.
await controls.mountNavigation('delivery-route-1');loadNavigation(): Promise<void>
Load navigation instructions and UI.
await controls.loadNavigation();setInitialNavigationPosition(position: RTLocation): Promise<void>
Set starting position for navigation.
await controls.setInitialNavigationPosition({
latitude: 40.7128,
longitude: -74.0060,
accuracy: 10,
heading: 90,
speed: 0
});navigate(position: RTLocation): Promise<void>
Update navigation with current position.
// In GPS update loop
await controls.navigate({
latitude: 40.7130,
longitude: -74.0062,
accuracy: 8,
heading: 95,
speed: 15
});casting(routeId: string, direction: any, position?: RTLocation, options?: RouteOptions): Promise<void>
Upsert navigation direction data (for receiving external navigation).
await controls.casting(
'route-1',
externalNavigationData,
currentPosition,
{ mode: 'driving' }
);dismissNavigation(): Promise<void>
End navigation session.
await controls.dismissNavigation();unmountNavigation(): Promise<void>
Remove navigation from route.
await controls.unmountNavigation();Waypoint Captions
setWaypointCaption(routeId: string, id: string | number, caption: Caption): Promise<void>
Add caption to waypoint.
await controls.setWaypointCaption('route-1', 0, {
label: 'Priority Delivery',
sublabel: 'Fragile items',
description: 'Handle with care - Glass contents'
});updateWaypointCaption(routeId: string, id: string | number, caption: Caption): Promise<void>
Update existing waypoint caption.
await controls.updateWaypointCaption('route-1', 0, {
label: 'Completed',
sublabel: 'Delivered at 2:30 PM'
});removeWaypointCaption(routeId: string, id: string | number): Promise<void>
Remove waypoint caption.
await controls.removeWaypointCaption('route-1', 0);Event Listeners
on(event: string, listener: Function)
Subscribe to map events.
controls.on('route:updated', (data) => {
console.log('Route updated:', data);
});off(event: string, listener: Function)
Unsubscribe from specific event.
const handler = (data) => console.log(data);
controls.on('route:updated', handler);
controls.off('route:updated', handler);removeListeners(listener: Function)
Remove all instances of a listener.
controls.removeListeners(myHandler);3. Handles
High-level, stream-based API for real-time data workflows. Handles abstract complex operations into simple, composable streams.
Location Streams
myLocation(usertype?: 'client' | 'agent'): Stream
Create stream of user's current location with continuous updates.
const locationStream = handles.myLocation('agent');
// Listen to location updates
locationStream.pipe(location => {
console.log('Current position:', location.latitude, location.longitude);
console.log('Speed:', location.speed, 'm/s');
console.log('Heading:', location.heading, 'degrees');
});
// Handle errors
locationStream.onerror(error => {
console.error('Location error:', error);
});
// Clean up when done
locationStream.onclose(() => {
console.log('Location tracking stopped');
});
// Stop tracking
locationStream.close();peerLocation(position: RTLocation, caption?: Caption): Stream
Stream for displaying peer's location (e.g., driver tracking for customer).
const driverStream = handles.peerLocation(
{
latitude: 40.7128,
longitude: -74.0060,
accuracy: 10,
heading: 180,
speed: 25
},
{
label: 'Your Driver',
sublabel: 'Mike Johnson',
description: 'White Ford Transit - ABC-123'
}
);
// Update driver position
driverStream.write({
position: {
latitude: 40.7130,
longitude: -74.0062,
accuracy: 8,
heading: 175,
speed: 30
},
caption: {
label: 'Your Driver',
sublabel: 'Mike Johnson',
description: '5 minutes away'
}
});
// Close when delivery complete
driverStream.close();onPickLocation(fn: (location: PickedLocation) => void)
Listen for user-selected locations on map.
handles.onPickLocation(location => {
console.log('Coordinates:', location.coordinates);
console.log('Address:', location.address);
console.log('Place:', location.placeName);
// Use the picked location
saveDeliveryAddress(location);
});Entity Management
nearby(entities: Entity[]): NearbyStream
Create live stream for managing fleet/nearby entities with real-time controls.
// Initialize with current fleet
const fleetStream = handles.nearby([
{
id: 'van-1',
type: 'delivery-van',
position: [40.7128, -74.0060],
caption: { label: 'Van #101', sublabel: '5 deliveries pending' }
},
{
id: 'truck-1',
type: 'freight-truck',
position: [40.7200, -74.0100],
caption: { label: 'Truck #201', sublabel: '2 pickups remaining' }
}
]);
// Get live control interface
fleetStream.live(async controls => {
// Add new vehicle
await controls.add({
id: 'van-2',
type: 'delivery-van',
position: [40.7300, -74.0150],
caption: { label: 'Van #102', sublabel: 'Just dispatched' }
});
// Update vehicle position (real-time tracking)
setInterval(async () => {
await controls.move({
id: 'van-1',
position: getLatestVehiclePosition('van-1')
});
}, 5000);
// Remove vehicle when done
await controls.remove('truck-1');
});
// Monitor all fleet changes
fleetStream.pipe(update => {
console.log(`Fleet ${update.action}:`, update.dataset);
console.log('Total vehicles:', update.list.length);
// Update dashboard
updateFleetDashboard(update.list);
});
// Close stream
fleetStream.close();Route Helpers
pickupPoint(location: Coordinates, caption?: Caption): Promise<void>
Set pickup location with visual marker.
await handles.pickupPoint(
{ lng: -74.0060, lat: 40.7128 },
{
label: 'Warehouse A',
duration: 5,
unit: 'min'
}
);dropoffPoint(location: Coordinates, caption?: Caption): Promise<void>
Set delivery destination with marker.
await handles.dropoffPoint(
{ lng: -73.9855, lat: 40.7580 },
{
label: 'Customer Address',
duration: 15,
unit: 'min'
}
);Navigation Streams
navigation(journey: Journey): Promise<Stream>
Start turn-by-turn navigation with real-time position updates.
// Start navigation
const navStream = await handles.navigation({
routeId: 'delivery-route-1',
origin: {
coords: await controls.getCurrentLocation(),
caption: { label: 'Current Location' }
},
destination: {
coords: [40.7580, -73.9855],
caption: { label: 'Delivery Address' }
},
waypoints: [
{
coords: [40.7489, -73.9680],
caption: { label: 'Package Pickup' },
index: 0
}
],
options: {
mode: 'driving',
avoidTolls: false
}
});
// Update with GPS positions
const locationStream = handles.myLocation('agent');
locationStream.pipe(location => {
navStream.write({ position: location });
});
// Listen for navigation events
handles.on('pe:started', () => console.log('Navigation started'));
handles.on('pe:nearby', () => console.log('Approaching destination'));
handles.on('pe:arrived', () => {
console.log('Arrived at destination!');
navStream.close();
locationStream.close();
});
// Handle navigation errors
handles.on('pe:closed', () => {
console.log('Navigation ended');
});peerDirection(options?: RouteOptions): Stream
Stream for displaying peer's navigation (e.g., showing driver's route to customer).
const driverNavStream = handles.peerDirection({
mode: 'driving',
avoidTolls: false,
announceInstructions: false
});
// Receive driver navigation updates from backend
websocket.onmessage = (event) => {
const { status, direction, position } = JSON.parse(event.data);
driverNavStream.write({
status,
direction,
position
});
};
// Listen to driver proximity events
handles.on('pe:nearby', () => {
notifyCustomer('Driver is nearby!');
});
handles.on('pe:arrived', () => {
notifyCustomer('Driver has arrived!');
driverNavStream.close();
});Navigation Status Events
// All available navigation events
handles.on('pe:started', () => {}); // Navigation began
handles.on('pe:stale', () => {}); // No movement detected
handles.on('pe:long_stop', () => {}); // Stopped for extended period
handles.on('pe:low_traffic', () => {}); // Light traffic conditions
handles.on('pe:moderate_traffic', () => {}); // Moderate traffic
handles.on('pe:high_traffic', () => {}); // Heavy traffic
handles.on('pe:speed_warning', () => {}); // Speeding detected
handles.on('pe:nearby', () => {}); // Approaching destination
handles.on('pe:arrived', () => {}); // Reached destination
handles.on('pe:closed', () => {}); // Navigation ended4. Plugins
Extensible system for adding custom functionality with access to all SDK hooks.
Plugin Architecture
type Plugin<API, Options = {}> = (
hooks: PluginHook,
options?: Options
) => API;
interface PluginHook {
handles: Handles; // Stream-based operations
controls: Controls; // Direct map operations
map: MapOptions; // Map configuration
utils: typeof Utils; // Utility functions
}Creating a Plugin
// Define plugin interface
interface DeliveryAnalyticsAPI {
trackRoute: (routeId: string) => Promise<void>;
getRouteMetrics: (routeId: string) => Promise<RouteMetrics>;
exportData: () => Promise<string>;
}
interface DeliveryAnalyticsOptions {
apiKey?: string;
debug?: boolean;
}
// Implement plugin
const deliveryAnalyticsPlugin: Plugin<DeliveryAnalyticsAPI, DeliveryAnalyticsOptions> = (
hooks,
options
) => {
const { controls, handles, map, utils } = hooks;
const routeData = new Map();
return {
async trackRoute(routeId: string) {
const startTime = Date.now();
// Use handles to listen for completion
handles.on('pe:arrived', () => {
const endTime = Date.now();
const duration = endTime - startTime;
routeData.set(routeId, {
duration,
timestamp: new Date(),
distance: calculateDistance()
});
if (options?.debug) {
console.log(`Route ${routeId} completed in ${duration}ms`);
}
});
},
async getRouteMetrics(routeId: string) {
const data = routeData.get(routeId);
if (!data) throw new Error('Route not tracked');
return {
duration: data.duration,
distance: data.distance,
averageSpeed: data.distance / (data.duration / 1000)
};
},
async exportData() {
return JSON.stringify(Array.from(routeData.entries()));
}
};
};
// Register plugin
msi.plugin('deliveryAnalytics', deliveryAnalyticsPlugin);
// Use plugin after MSI loads
const { plugins } = await msi.load();
const analytics = plugins.use('deliveryAnalytics', {
apiKey: 'your-api-key',
debug: true
});
await analytics.trackRoute('route-123');Plugin Methods
plugins.mount(list: Record<string, Plugin<any>>)
Mount multiple plugins at once.
plugins.mount({
analytics: deliveryAnalyticsPlugin,
notifications: deliveryNotificationsPlugin,
customMarkers: customMarkersPlugin
});plugins.use<API, Options>(name: string, options?: Options): API
Activate and get plugin interface.
const analytics = plugins.use('analytics', { debug: true });
const notifications = plugins.use('notifications');Example: Fleet Management Plugin
const fleetManagementPlugin = (hooks, options) => {
const { controls, handles, utils } = hooks;
const vehicles = new Map();
return {
async registerVehicle(vehicle) {
vehicles.set(vehicle.id, {
...vehicle,
lastUpdate: Date.now()
});
await controls.addNearbyEntity({
id: vehicle.id,
type: 'vehicle',
position: vehicle.position,
caption: {
label: vehicle.name,
sublabel: `${vehicle.capacity} capacity`
}
});
},
async updateVehiclePosition(vehicleId, position) {
const vehicle = vehicles.get(vehicleId);
if (!vehicle) throw new Error('Vehicle not found');
vehicle.position = position;
vehicle.lastUpdate = Date.now();
await controls.moveNearbyEntity({
id: vehicleId,
position
});
},
async getVehicleStatus(vehicleId) {
const vehicle = vehicles.get(vehicleId);
return vehicle || null;
},
async optimizeRoutes(vehicleIds) {
// Use controls to calculate optimal routes
const routes = [];
for (const id of vehicleIds) {
const vehicle = vehicles.get(id);
if (vehicle && vehicle.destination) {
routes.push({
vehicleId: id,
route: await calculateOptimalRoute(
vehicle.position,
vehicle.destination
)
});
}
}
return routes;
},
getFleetSummary() {
return {
totalVehicles: vehicles.size,
activeVehicles: Array.from(vehicles.values()).filter(
v => Date.now() - v.lastUpdate < 60000
).length
};
}
};
};
// Usage
msi.plugin('fleetManagement', fleetManagementPlugin);
const { plugins } = await msi.load();
const fleet = plugins.use('fleetManagement');
await fleet.registerVehicle({
id: 'van-1',
name: 'Delivery Van #101',
position: [40.7128, -74.0060],
capacity: '1500kg'
});
const summary = fleet.getFleetSummary();
console.log('Fleet summary:', summary);5. Access
Manages authentication and HTTP requests to De. platform API.
Constructor
const access = new De.Access({
workspace: 'your-workspace-id', // Required
accessToken: 'your-access-token', // Required
remoteOrigin: window.location.origin,
env: 'prod', // 'dev' | 'prod'
version: 1 // API version
});Making Requests
request<Response>(options: HTTPRequestOptions): Promise<Response>
Execute authenticated HTTP request.
// GET request
const vehicles = await access.request<VehicleResponse[]>({
url: '/fleet/vehicles',
method: 'GET'
});
// POST request
const order = await access.request<OrderResponse>({
url: '/orders',
method: 'POST',
body: {
pickupLocation: [40.7128, -74.0060],
deliveryLocation: [40.7580, -73.9855],
items: [
{ name: 'Package A', weight: 5 }
]
}
});
// PUT request
const updated = await access.request({
url: '/orders/order-123',
method: 'PUT',
body: { status: 'in-transit' }
});
// DELETE request
await access.request({
url: '/orders/order-123',
method: 'DELETE'
});
// Custom headers
const data = await access.request({
url: '/analytics/reports',
method: 'GET',
headers: {
'De-Report-Format': 'json',
'De-Include-Metrics': 'true'
}
});Token Management
setToken(token: string): void
Update access token (e.g., after refresh).
// Refresh token flow
const newToken = await refreshAuthToken();
access.setToken(newToken);
// Also update MSI if needed
controls.refreshToken(newToken);Request Configuration
interface HTTPRequestOptions {
url: string; // API endpoint
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
body?: any; // Auto-stringified if object
headers?: Record<string, string>; // Custom headers
}Best Practices
class DeliveryService {
private access: De.Access;
constructor(config) {
this.access = new De.Access(config);
}
async createOrder(orderData) {
try {
const order = await this.access.request({
url: '/orders',
method: 'POST',
body: orderData
});
return order;
} catch (error) {
console.error('Order creation failed:', error);
throw error;
}
}
async getOrderStatus(orderId) {
return await this.access.request({
url: `/orders/${orderId}/status`,
method: 'GET'
});
}
async updateOrderLocation(orderId, location) {
return await this.access.request({
url: `/orders/${orderId}/location`,
method: 'PATCH',
body: { location }
});
}
}6. DClient
Client-side operations for orders, events, and customer management (separate from MSI).
import { DClient } from '@de./sdk';
const { Client, Order, Event } = DClient;
// Client management
const client = new Client(accessConfig);
// Order management
const order = new Order(accessConfig);
// Event tracking
const event = new Event(accessConfig);Note: DClient documentation is maintained separately. See the DClient API reference at https://docs.dedot.io/dclient
Code Examples
Example 1: Complete E-commerce Delivery Integration
import De from '@de./sdk';
class EcommerceDeliveryIntegration {
private msi: De.MSI;
private access: De.Access;
private controls?: De.Controls;
private handles?: De.Handles;
constructor(config) {
this.access = new De.Access(config);
this.msi = new De.MSI(config);
}
async initialize() {
const api = await this.msi.load();
this.controls = api.controls;
this.handles = api.handles;
}
async processCheckout(cartItems, customerAddress) {
// 1. Create order via API
const order = await this.access.request({
url: '/orders',
method: 'POST',
body: {
items: cartItems,
deliveryAddress: customerAddress,
timestamp: Date.now()
}
});
// 2. Get warehouse location
const warehouse = await this.access.request({
url: '/warehouses/nearest',
method: 'POST',
body: { address: customerAddress }
});
// 3. Display route on map
await this.handles.pickupPoint(
warehouse.location,
{
label: warehouse.name,
description: `Order #${order.id} origin`
}
);
await this.handles.dropoffPoint(
customerAddress.coordinates,
{
label: 'Your Address',
sublabel: customerAddress.street,
description: `Delivery for order #${order.id}`
}
);
// 4. Assign delivery vehicle
const vehicle = await this.assignVehicle(order.id);
// 5. Track vehicle in real-time
const trackingStream = this.trackDeliveryVehicle(vehicle);
return {
order,
warehouse,
vehicle,
trackingStream
};
}
async assignVehicle(orderId) {
return await this.access.request({
url: `/orders/${orderId}/assign-vehicle`,
method: 'POST'
});
}
trackDeliveryVehicle(vehicle) {
const vehicleStream = this.handles.peerLocation(
vehicle.currentLocation,
{
label: 'Your Delivery',
sublabel: `Driver: ${vehicle.driverName}`,
description: `${vehicle.vehicleType} - ETA: ${vehicle.eta}`
}
);
// Connect to real-time updates
const ws = new WebSocket(`wss://api.dedot.io/vehicles/${vehicle.id}`);
ws.onmessage = (event) => {
const update = JSON.parse(event.data);
vehicleStream.write({
position: update.location,
caption: {
label: 'Your Delivery',
sublabel: `Driver: ${vehicle.driverName}`,
description: `${update.distance} away - ETA: ${update.eta}`
}
});
};
// Listen for delivery events
this.handles.on('pe:nearby', () => {
this.notifyCustomer('Driver is nearby!', 'warning');
});
this.handles.on('pe:arrived', () => {
this.notifyCustomer('Driver has arrived!', 'success');
vehicleStream.close();
ws.close();
});
return vehicleStream;
}
notifyCustomer(message, type) {
// Send push notification, SMS, etc.
console.log(`[${type.toUpperCase()}] ${message}`);
}
}
// Usage
const delivery = new EcommerceDeliveryIntegration({
element: 'map-container',
accessToken: 'your-token',
workspace: 'your-workspace',
env: 'prod'
});
await delivery.initialize();
const result = await delivery.processCheckout(
[{ id: 'item-1', name: 'Product A', quantity: 2 }],
{
street: '456 Park Ave, Apt 12B',
coordinates: [40.7580, -73.9855]
}
);
console.log('Order created:', result.order.id);Example 2: Fleet Management Dashboard
class FleetManagementDashboard {
private msi: De.MSI;
private access: De.Access;
private fleetStream?: any;
private wsConnection?: WebSocket;
constructor(config) {
this.msi = new De.MSI(config);
this.access = new De.Access(config);
}
async initialize() {
const { controls, handles } = await this.msi.load();
// Get all active vehicles from API
const { vehicles } = await this.access.request({
url: '/fleet/active',
method: 'GET'
});
// Display vehicles on map
this.fleetStream = handles.nearby(
vehicles.map(v => ({
id: v.id,
type: v.vehicleType,
position: v.currentLocation,
caption: {
label: v.name,
sublabel: `Driver: ${v.driverName}`,
description: `Status: ${v.status} | Speed: ${v.speed}km/h`
}
}))
);
// Setup real-time updates
this.setupRealtimeTracking();
// Monitor fleet changes
this.fleetStream.pipe(update => {
this.updateDashboardStats(update.list);
});
return { controls, handles };
}
setupRealtimeTracking() {
this.wsConnection = new WebSocket('wss://api.dedot.io/fleet/live');
this.wsConnection.onmessage = async (event) => {
const update = JSON.parse(event.data);
this.fleetStream.live(async controls => {
switch (update.type) {
case 'position_update':
await controls.move({
id: update.vehicleId,
position: update.location
});
break;
case 'vehicle_online':
await controls.add({
id: update.vehicle.id,
type: update.vehicle.type,
position: update.vehicle.location,
caption: {
label: update.vehicle.name,
sublabel: `Driver: ${update.vehicle.driverName}`
}
});
break;
case 'vehicle_offline':
await controls.remove(update.vehicleId);
break;
}
});
};
this.wsConnection.onerror = () => {
console.error('WebSocket connection lost, reconnecting...');
setTimeout(() => this.setupRealtimeTracking(), 5000);
};
}
updateDashboardStats(vehicles) {
const stats = {
total: vehicles.length,
active: vehicles.filter(v => v.status === 'active').length,
idle: vehicles.filter(v => v.status === 'idle').length,
offline: vehicles.filter(v => v.status === 'offline').length
};
// Update UI
this.renderDashboardStats(stats);
}
renderDashboardStats(stats) {
document.getElementById('total-vehicles').textContent = stats.total;
document.getElementById('active-vehicles').textContent = stats.active;
document.getElementById('idle-vehicles').textContent = stats.idle;
document.getElementById('offline-vehicles').textContent = stats.offline;
}
async optimizeRoutes() {
const { routes } = await this.access.request({
url: '/fleet/optimize-routes',
method: 'POST'
});
console.log('Routes optimized:', routes);
return routes;
}
cleanup() {
this.wsConnection?.close();
this.fleetStream?.close();
}
}
// Usage
const dashboard = new FleetManagementDashboard({
element: 'map-container',
accessToken: 'your-token',
workspace: 'your-workspace'
});
await dashboard.initialize();Example 3: Multi-Stop Route Planning
async function planMultiStopDeliveryRoute() {
const msi = new De.MSI({
element: 'map-container',
accessToken: 'your-token',
workspace: 'your-workspace'
});
const { controls, handles } = await msi.load();
// Define delivery stops
const stops = [
{
address: 'Warehouse - 123 Industrial Blvd',
coords: [40.7128, -74.0060],
type: 'pickup',
items: ['Package A', 'Package B', 'Package C']
},
{
address: 'Customer 1 - 456 Park Ave',
coords: [40.7489, -73.9680],
type: 'delivery',
items: ['Package A']
},
{
address: 'Customer 2 - 789 Broadway',
coords: [40.7300, -73.9950],
type: 'delivery',
items: ['Package B']
},
{
address: 'Customer 3 - 321 5th Ave',
coords: [40.7580, -73.9855],
type: 'delivery',
items: ['Package C']
}
];
// Create route with all stops
await controls.setRoute({
routeId: 'multi-stop-delivery',
origin: {
coords: stops[0].coords,
caption: {
label: stops[0].address,
sublabel: 'Pickup Point',
description: stops[0].items.join(', ')
}
},
destination: {
coords: stops[stops.length - 1].coords,
caption: {
label: stops[stops.length - 1].address,
sublabel: 'Final Delivery',
description: `Items: ${stops[stops.length - 1].items.join(', ')}`
}
},
waypoints: stops.slice(1, -1).map((stop, index) => ({
coords: stop.coords,
caption: {
label: `Stop ${index + 1}`,
sublabel: stop.address,
description: `Deliver: ${stop.items.join(', ')}`
},
index
})),
options: {
mode: 'driving',
optimize: true,
avoidTolls: false
}
});
// Fit all stops in view
await controls.fitRouteBounds('multi-stop-delivery', 80);
// Start navigation
const navStream = await handles.navigation({
routeId: 'multi-stop-delivery',
origin: {
coords: stops[0].coords,
caption: { label: 'Start' }
},
destination: {
coords: stops[stops.length - 1].coords,
caption: { label: 'End' }
},
waypoints: stops.slice(1, -1).map((stop, index) => ({
coords: stop.coords,
caption: { label: `Stop ${index + 1}` },
index
}))
});
// Track progress
let currentStop = 0;
handles.on('pe:arrived', async () => {
currentStop++;
console.log(`Arrived at stop ${currentStop} of ${stops.length}`);
if (currentStop < stops.length) {
// Update waypoint caption to mark as completed
await controls.updateWaypointCaption(
'multi-stop-delivery',
currentStop - 1,
{
label: `Stop ${currentStop} - Completed ✓`,
sublabel: stops[currentStop].address
}
);
} else {
console.log('All deliveries completed!');
navStream.close();
}
});
return { navStream, stops };
}
// Usage
const { navStream, stops } = await planMultiStopDeliveryRoute();
console.log(`Route planned with ${stops.length} stops`);Example 4: Real-time Order Tracking for Customers
class CustomerOrderTracking {
private msi: De.MSI;
private access: De.Access;
constructor(config) {
this.msi = new De.MSI(config);
this.access = new De.Access(config);
}
async trackOrder(orderId: string) {
// Initialize MSI
const { controls, handles } = await this.msi.load();
// Get order details
const order = await this.access.request({
url: `/orders/${orderId}`,
method: 'GET'
});
// Show delivery route
await handles.pickupPoint(
order.pickupLocation,
{ label: 'Pickup', description: order.originName }
);
await handles.dropoffPoint(
order.deliveryLocation,
{ label: 'Your Address', description: order.deliveryAddress }
);
// Show delivery vehicle if assigned
if (order.vehicleId) {
const vehicle = await this.access.request({
url: `/vehicles/${order.vehicleId}`,
method: 'GET'
});
// Track driver's location
const driverStream = handles.peerLocation(
vehicle.currentLocation,
{
label: 'Your Driver',
sublabel: `${vehicle.driverName} - ${vehicle.vehicleModel}`,
description: `ETA: ${vehicle.eta}`
}
);
// Show driver's navigation
const driverNavStream = handles.peerDirection({
mode: 'driving'
});
// Connect to real-time updates
const ws = new WebSocket(`wss://api.dedot.io/orders/${orderId}/live`);
ws.onmessage = (event) => {
const update = JSON.parse(event.data);
// Update driver position
driverStream.write({
position: update.vehicleLocation,
caption: {
label: 'Your Driver',
sublabel: `${vehicle.driverName} - ${vehicle.vehicleModel}`,
description: `${update.distanceToDelivery} away - ETA: ${update.eta}`
}
});
// Update driver's navigation
if (update.navigationData) {
driverNavStream.write({
status: update.status,
direction: update.navigationData,
position: update.vehicleLocation
});
}
};
// Listen for proximity alerts
handles.on('pe:nearby', () => {
this.showNotification('Driver is nearby! Prepare to receive your delivery.');
});
handles.on('pe:arrived', () => {
this.showNotification('Driver has arrived at your location!');
ws.close();
driverStream.close();
driverNavStream.close();
});
return {
order,
vehicle,
streams: { driverStream, driverNavStream },
websocket: ws
};
}
return { order };
}
showNotification(message: string) {
// Show browser notification
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('De. Delivery', {
body: message,
icon: '/delivery-icon.png'
});
}
// Also show in-app notification
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 5000);
}
}
// Usage
const tracking = new CustomerOrderTracking({
element: 'map-container',
accessToken: 'customer-token',
workspace: 'your-workspace'
});
const result = await tracking.trackOrder('order-12345');
console.log('Tracking order:', result.order.id);Example 5: Warehouse Management Plugin
const warehouseManagementPlugin = (hooks, options) => {
const { controls, handles, utils } = hooks;
const warehouses = new Map();
const inventory = new Map();
return {
async registerWarehouse(warehouse) {
warehouses.set(warehouse.id, warehouse);
await controls.addNearbyEntity({
id: warehouse.id,
type: 'warehouse',
position: warehouse.location,
caption: {
label: warehouse.name,
sublabel: `Capacity: ${warehouse.capacity}`,
description: `Items: ${warehouse.currentStock}`
}
});
return warehouse.id;
},
async updateInventory(warehouseId, items) {
inventory.set(warehouseId, items);
const warehouse = warehouses.get(warehouseId);
if (warehouse) {
await controls.moveNearbyEntity({
id: warehouseId,
position: warehouse.location
});
}
},
async findNearestWarehouse(location, itemsNeeded) {
let nearest = null;
let minDistance = Infinity;
for (const [id, warehouse] of warehouses) {
const items = inventory.get(id) || [];
const hasAllItems = itemsNeeded.every(item =>
items.some(i => i.id === item.id && i.quantity >= item.quantity)
);
if (hasAllItems) {
const distance = utils.calculateDistance(
location,
warehouse.location
);
if (distance < minDistance) {
minDistance = distance;
nearest = { warehouse, distance };
}
}
}
return nearest;
},
async optimizeStockDistribution() {
const distributionPlan = [];
for (const [id, warehouse] of warehouses) {
const items = inventory.get(id) || [];
const lowStock = items.filter(i => i.quantity < i.minQuantity);
if (lowStock.length > 0) {
distributionPlan.push({
warehouseId: id,
warehouse: warehouse.name,
needsRestocking: lowStock
});
}
}
return distributionPlan;
},
getWarehouseStats() {
return {
total: warehouses.size,
averageCapacity: Array.from(warehouses.values())
.reduce((sum, w) => sum + w.capacity, 0) / warehouses.size,
totalItems: Array.from(inventory.values())
.reduce((sum, items) => sum + items.length, 0)
};
}
};
};
// Usage
msi.plugin('warehouseManagement', warehouseManagementPlugin);
const { plugins } = await msi.load();
const wms = plugins.use('warehouseManagement');
// Register warehouses
await wms.registerWarehouse({
id: 'wh-001',
name: 'Brooklyn Distribution Center',
location: [40.7128, -74.0060],
capacity: 10000,
currentStock: 7500
});
// Find nearest warehouse with items
const nearest = await wms.findNearestWarehouse(
[40.7580, -73.9855],
[{ id: 'item-a', quantity: 5 }]
);
console.log('Nearest warehouse:', nearest.warehouse.name);
console.log('Distance:', nearest.distance, 'km');Advanced Usage Patterns
Pattern 1: Dynamic Route Optimization
class DynamicRouteOptimizer {
private controls: De.Controls;
private access: De.Access;
constructor(controls: De.Controls, access: De.Access) {
this.controls = controls;
this.access = access;
}
async optimizeDeliveryRoute(deliveries: Delivery[]) {
// Get traffic data from API
const trafficData = await this.access.request({
url: '/traffic/current',
method: 'POST',
body: {
locations: deliveries.map(d => d.location)
}
});
// Sort deliveries by priority and traffic conditions
const optimized = this.sortByOptimalSequence(
deliveries,
trafficData
);
// Create route with optimized stops
await this.controls.setRoute({
routeId: 'optimized-route',
origin: {
coords: optimized[0].location,
caption: { label: 'Start' }
},
destination: {
coords: optimized[optimized.length - 1].location,
caption: { label: 'End' }
},
waypoints: optimized.slice(1, -1).map((delivery, index) => ({
coords: delivery.location,
caption: {
label: `Stop ${index + 1}`,
description: `Priority: ${delivery.priority}`
},
index
})),
options: {
mode: 'driving',
optimize: true,
avoidHighTraffic: true
}
});
return optimized;
}
private sortByOptimalSequence(
deliveries: Delivery[],
trafficData: any
): Delivery[] {
// Complex optimization algorithm
// Consider: priority, traffic, distance, time windows
return deliveries.sort((a, b) => {
const scoreA = this.calculateDeliveryScore(a, trafficData);
const scoreB = this.calculateDeliveryScore(b, trafficData);
return scoreB - scoreA; // Higher score = higher priority
});
}
private calculateDeliveryScore(delivery: Delivery, trafficData: any): number {
let score = 0;
// Priority weight (0-100)
score += delivery.priority * 10;
// Traffic conditions (lower traffic = higher score)
const traffic = trafficData.find(t =>
t.location[0] === delivery.location[0] &&
t.location[1] === delivery.location[1]
);
if (traffic) {
score += (100 - traffic.congestionLevel);
}
// Time window urgency
const timeToDeadline = delivery.deadline - Date.now();
if (timeToDeadline < 3600000) { // Less than 1 hour
score += 50;
}
return score;
}
}
// Usage
const optimizer = new DynamicRouteOptimizer(controls, access);
const deliveries = [
{ location: [40.7128, -74.0060], priority: 8, deadline: Date.now() + 7200000 },
{ location: [40.7580, -73.9855], priority: 10, deadline: Date.now() + 3600000 },
{ location: [40.7489, -73.9680], priority: 5, deadline: Date.now() + 14400000 }
];
const optimized = await optimizer.optimizeDeliveryRoute(deliveries);
console.log('Optimized delivery sequence:', optimized);Pattern 2: Real-time Fleet Coordination
class FleetCoordinator {
private handles: De.Handles;
private access: De.Access;
private activeVehicles: Map<string, VehicleState> = new Map();
private orders: Map<string, Order> = new Map();
constructor(handles: De.Handles, access: De.Access) {
this.handles = handles;
this.access = access;
}
async initialize() {
// Get all active vehicles
const { vehicles } = await this.access.request({
url: '/fleet/active',
method: 'GET'
});
// Display vehicles on map
const fleetStream = this.handles.nearby(
vehicles.map(v => this.formatVehicleEntity(v))
);
// Setup real-time coordination
fleetStream.live(async controls => {
this.setupWebSocketConnection(controls);
this.monitorVehicleStatus();
});
return fleetStream;
}
private formatVehicleEntity(vehicle: any) {
return {
id: vehicle.id,
type: vehicle.type,
position: vehicle.currentLocation,
caption: {
label: vehicle.name,
sublabel: `Status: ${vehicle.status}`,
description: vehicle.currentOrder
? `Delivering order #${vehicle.currentOrder}`
: 'Available'
}
};
}
private setupWebSocketConnection(controls: any) {
const ws = new WebSocket('wss://api.dedot.io/fleet/coordination');
ws.onmessage = async (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case 'vehicle_update':
await this.handleVehicleUpdate(controls, message.data);
break;
case 'new_order':
await this.handleNewOrder(controls, message.data);
break;
case 'order_completed':
await this.handleOrderCompletion(controls, message.data);
break;
}
};
}
private async handleVehicleUpdate(controls: any, data: any) {
this.activeVehicles.set(data.vehicleId, {
location: data.location,
status: data.status,
lastUpdate: Date.now()
});
await controls.move({
id: data.vehicleId,
position: data.location
});
}
private async handleNewOrder(controls: any, order: Order) {
this.orders.set(order.id, order);
// Find best vehicle for this order
const vehicle = await this.assignBestVehicle(order);
if (vehicle) {
console.log(`Assigned order ${order.id} to vehicle ${vehicle.id}`);
// Notify vehicle via API
await this.access.request({
url: `/vehicles/${vehicle.id}/assign-order`,
method: 'POST',
body: { orderId: order.id }
});
}
}
private async handleOrderCompletion(controls: any, data: any) {
this.orders.delete(data.orderId);
// Update vehicle status
const vehicle = this.activeVehicles.get(data.vehicleId);
if (vehicle) {
vehicle.status = 'available';
this.activeVehicles.set(data.vehicleId, vehicle);
}
}
private async assignBestVehicle(order: Order): Promise<any | null> {
let bestVehicle = null;
let minDistance = Infinity;
for (const [id, state] of this.activeVehicles) {
if (state.status === 'available') {
const distance = this.calculateDistance(
state.location,
order.pickupLocation
);
if (distance < minDistance) {
minDistance = distance;
bestVehicle = { id, ...state };
}
}
}
return bestVehicle;
}
private monitorVehicleStatus() {
setInterval(() => {
const now = Date.now();
for (const [id, state] of this.activeVehicles) {
const timeSinceUpdate = now - state.lastUpdate;
// Alert if vehicle hasn't updated in 5 minutes
if (timeSinceUpdate > 300000) {
console.warn(`Vehicle ${id} connection lost`);
this.alertDispatcher(`Vehicle ${id} offline`);
}
}
}, 60000); // Check every minute
}
private calculateDistance(from: number[], to: number[]): number {
const R = 6371; // Earth radius in km
const dLat = this.toRad(to[0] - from[0]);
const dLon = this.toRad(to[1] - from[1]);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.toRad(from[0])) * Math.cos(this.toRad(to[0])) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
private toRad(deg: number): number {
return deg * (Math.PI / 180);
}
private alertDispatcher(message: string) {
console.log('[ALERT]', message);
// Send notification to dispatcher
}
getFleetMetrics() {
const available = Array.from(this.activeVehicles.values())
.filter(v => v.status === 'available').length;
return {
totalVehicles: this.activeVehicles.size,
available,
busy: this.activeVehicles.size - available,
pendingOrders: this.orders.size
};
}
}
// Usage
const coordinator = new FleetCoordinator(handles, access);
const fleetStream = await coordinator.initialize();
setInterval(() => {
const metrics = coordinator.getFleetMetrics();
console.log('Fleet metrics:', metrics);
}, 30000); // Every 30 secondsPattern 3: Geofencing and Zone Management
class GeofenceManager {
private controls: De.Controls;
private handles: De.Handles;
private zones: Map<string, Zone> = new Map();
constructor(controls: De.Controls, handles: De.Handles) {
this.controls = controls;
this.handles = handles;
}
async createDeliveryZone(zone: Zone) {
this.zones.set(zone.id, zone);
// Display zone on map
await this.controls.addNearbyEntity({
id: zone.id,
type: 'zone',
position: zone.center,
radius: zone.radius,
caption: {
label: zone.name,
description: `Delivery zone - ${zone.restrictions || 'No restrictions'}`
}
});
}
async monitorVehicleInZones(vehicleId: string) {
const locationStream = this.handles.myLocation('agent');
locationStream.pipe(location => {
for (const [zoneId, zone] of this.zones) {
const isInside = this.isLocationInZone(location, zone);
if (isInside && !zone.activeVehicles?.includes(vehicleId)) {
this.handleVehicleEnterZone(vehicleId, zone);
} else if (!isInside && zone.activeVehicles?.includes(vehicleId)) {
this.handleVehicleExitZone(vehicleId, zone);
}
}
});
return locationStream;
}
private isLocationInZone(location: RTLocation, zone: Zone): boolean {
const distance = this.calculateDistance(
[location.latitude, location.longitude],
zone.center
);
return distance <= zone.radius;
}
private handleVehicleEnterZone(vehicleId: string, zone: Zone) {
console.log(`Vehicle ${vehicleId} entered zone ${zone.name}`);
if (!zone.activeVehicles) {
zone.activeVehicles = [];
}
zone.activeVehicles.push(vehicleId);
// Apply zone restrictions
if (zone.speedLimit) {
this.notifyDriver(vehicleId, `Speed limit: ${zone.speedLimit}km/h`);
}
if (zone.restrictions) {
this.notifyDriver(vehicleId, `Zone restrictions: ${zone.restrictions}`);
}
}
private handleVehicleExitZone(vehicleId: string, zone: Zone) {
console.log(`Vehicle ${vehicleId} exited zone ${zone.name}`);
if (zone.activeVehicles) {
zone.activeVehicles = zone.activeVehicles.filter(id => id !== vehicleId);
}
}
private calculateDistance(from: number[], to: number[]): number {
const R = 6371000; // Earth radius in meters
const dLat = this.toRad(to[0] - from[0]);
const dLon = this.toRad(to[1] - from[1]);
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.toRad(from[0])) * Math.cos(this.toRad(to[0])) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
private toRad(deg: number): number {
return deg * (Math.PI / 180);
}
private notifyDriver(vehicleId: string, message: string) {
console.log(`[Notification to ${vehicleId}