MSI Handles API
Stream-based high-level API for common workflows and continuous operations.
Handles vs Controls
Handles provide stream-based, high-level workflows for common patterns. Use handles when you need:
- Continuous operations (location tracking, fleet monitoring)
- Event-driven workflows
- Stream-based updates
- Common patterns with minimal code
Controls provide granular promise-based operations. Learn about Controls →
Understanding Handles
Handles return stream objects with live() methods for continuous updates.
const msi = new De.MSI({
element: 'map',
accessToken: token
})
const { handles } = await msi.load()
// Handles provide stream-based operations
const stream = await handles.myLocation()
// Use .live() for continuous updates
stream.live(async (controls) => {
// This runs continuously with access to controls
console.log('Location updated!')
})
// End the stream when done
stream.end()Location Streams
myLocation()
Track the current user's location continuously.
Signature: myLocation(usertype?: 'client' | 'agent'): Stream
Track User Location
Continuous location streaming
// Create location stream
const locationStream = handles.myLocation('client')
// 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')
console.log('Accuracy:', location.accuracy, 'meters')
// Update your UI
updateLocationDisplay(location)
// Perform actions based on location
if (isNearDestination(location)) {
notifyArrival()
}
})
// Handle errors
locationStream.onerror(error => {
console.error('Location error:', error)
})
// Handle close
locationStream.onclose(() => {
console.log('Location tracking stopped')
})
// Stop tracking
locationStream.close()Parameters:
usertype(optional):'client'|'agent'- Type of user being tracked
Returns: Stream object with location updates
Advanced Location Tracking
With custom logic
let previousLocation: RTLocation | null = null
let totalDistance = 0
const stream = handles.myLocation('agent')
stream.pipe(current => {
if (previousLocation) {
// Calculate distance traveled
const distance = calculateDistance(
previousLocation.latitude,
previousLocation.longitude,
current.latitude,
current.longitude
)
// Update total distance
totalDistance += distance
updateDistanceDisplay(totalDistance)
// Check if moving
if (distance > 5) { // 5 meters threshold
console.log('User is moving')
setMovingStatus(true)
} else {
console.log('User is stationary')
setMovingStatus(false)
}
}
previousLocation = current
})
// Stop when done
stream.close()peerLocation()
Track another entity's location (driver, delivery person, vehicle).
Signature: peerLocation(position: RTLocation, caption?: Caption): Stream
Track Peer Location
Monitor external entity position
const driverStream = handles.peerLocation(
{
latitude: 40.7128,
longitude: -74.0060,
accuracy: 10,
heading: 180,
speed: 25
},
{
label: 'Your Driver',
sublabel: 'Mike Johnson',
description: 'Arriving in 5 min'
}
)
// Update driver position from WebSocket/API
socket.on('driver:location', (update) => {
driverStream.write({
position: {
latitude: update.lat,
longitude: update.lng,
accuracy: update.accuracy,
heading: update.bearing,
speed: update.speed
},
caption: {
label: 'Your Driver',
sublabel: 'Mike Johnson',
description: `${update.eta} min away`
}
})
})
// End tracking when driver arrives
socket.on('driver:arrived', () => {
driverStream.close()
})Parameters:
position: Initial location with latitude, longitude, heading, speedcaption(optional): Display label, sublabel, and description
Stream Methods:
write(data): Update position and captionclose(): End the streamonerror(fn): Handle errorsonclose(fn): Handle stream close
Multiple Peer Tracking
Track multiple entities simultaneously
const driverStreams = driverList.map(driver =>
handles.peerLocation(
{
latitude: driver.location.lat,
longitude: driver.location.lng,
heading: driver.heading,
speed: driver.speed
},
{
label: driver.name,
sublabel: driver.status,
description: driver.vehicle
}
)
)
// Update all drivers from real-time feed
socket.on('fleet:update', (updates) => {
updates.forEach((update, index) => {
const stream = driverStreams[index]
stream.write({
position: {
latitude: update.lat,
longitude: update.lng,
heading: update.bearing,
speed: update.speed
},
caption: {
label: update.name,
sublabel: update.status,
description: `${update.eta} min`
}
})
})
})
// Close all streams when done
driverStreams.forEach(stream => stream.close())onPickLocation()
Listen for user-selected locations on map.
Signature: onPickLocation(fn: (location: PickedLocation) => void): void
Location Picker
User selects location by dragging
// Enable drag-pick mode first
await controls.enableDragPickLocation({
latitude: 40.7128,
longitude: -74.0060
})
// Listen for picked location
handles.onPickLocation(location => {
console.log('Coordinates:', location.coordinates)
console.log('Point:', location.point)
// Get address if needed
controls.resolveCoordinates(location.coordinates)
.then(place => {
console.log('Address:', place?.address)
updateAddressPreview(place?.address)
})
// Save the selected location
saveDeliveryAddress(location.coordinates)
// Disable picker
controls.disableDragPickLocation()
})PickedLocation Type:
interface PickedLocation {
point: { x: number, y: number } // Screen coordinates
coordinates: Coordinates // Lat/lng coordinates
}Entity Streams
nearby()
Manage fleet or multiple entities with stream-based updates.
Signature: nearby(entities: Entity[]): NearbyStream
Fleet Management Stream
Track and manage vehicle fleet
const fleetStream = handles.nearby([
{
id: 'van-1',
type: 'car',
status: 'ACTIVE',
grade: '2H',
currentLocation: {
latitude: 40.7128,
longitude: -74.0060,
heading: 90
}
},
{
id: 'truck-1',
type: 'truck',
status: 'BUSY',
grade: '3H',
currentLocation: {
latitude: 40.7200,
longitude: -74.0100,
heading: 180
}
}
])
// Get live control interface
fleetStream.live(async (controls) => {
// Add new vehicle
await controls.add({
id: 'van-2',
type: 'car',
status: 'ACTIVE',
grade: '1H',
currentLocation: {
latitude: 40.7300,
longitude: -74.0150,
heading: 45
}
})
// Move vehicle (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()Entity Type:
interface Entity {
id: string
type: 'person' | 'moto' | 'car' | 'bus' | 'bike' | 'truck' | 'plane' | 'ship'
| 'restaurant' | 'hotel' | 'store' | 'office' | 'warehouse'
status: 'ACTIVE' | 'BUSY'
grade: '1H' | '2H' | '3H'
currentLocation: RTLocation
static?: boolean
}Live Controls:
interface ControlEntity {
add: (entity: Entity) => Promise<void>
remove: (id: string) => Promise<void>
focus: (id: string) => Promise<void>
move: (update: ActivePosition) => Promise<void>
}
interface ActivePosition {
id: string
position: RTLocation
caption?: Caption
focus?: boolean
}Real-time Fleet Updates
Sync fleet with live data
const fleetStream = await handles.nearby({
entities: initialVehicles,
clustering: true
})
// Subscribe to fleet updates
socket.on('fleet:joined', (vehicle) => {
fleetStream.live(async (controls) => {
await controls.add({
id: vehicle.id,
type: 'vehicle',
position: vehicle.location,
label: vehicle.name
})
})
})
socket.on('fleet:location', (update) => {
fleetStream.live(async (controls) => {
await controls.move(update.vehicleId, {
position: update.location,
heading: update.bearing,
animate: true,
duration: 1000
})
})
})
socket.on('fleet:status', (update) => {
fleetStream.live(async (controls) => {
const color = {
'available': '#10b981',
'busy': '#0ea5e9',
'offline': '#6b7280'
}[update.status]
await controls.update(update.vehicleId, {
color,
label: `${update.vehicleName} - ${update.status}`
})
})
})
socket.on('fleet:left', (vehicleId) => {
fleetStream.live(async (controls) => {
await controls.remove(vehicleId)
})
})Route Helpers
Simplified route point setup with visual markers.
pickupPoint()
Set pickup location with marker.
Signature: pickupPoint(location: Coordinates, caption?: Caption): Promise<void>
Quick Pickup Point
One-line pickup setup
await handles.pickupPoint(
{ lng: -74.0060, lat: 40.7128 },
{
label: 'Warehouse A',
duration: 5,
unit: 'min'
}
)dropoffPoint()
Set delivery destination with marker.
Signature: dropoffPoint(location: Coordinates, caption?: Caption): Promise<void>
Quick Dropoff Point
One-line delivery setup
await handles.dropoffPoint(
{ lng: -73.9855, lat: 40.7580 },
{
label: 'Customer Address',
duration: 15,
unit: 'min'
}
)Complete Delivery Setup
Quick route creation
// Set pickup and delivery in 2 lines
await handles.pickupPoint(
{ lng: -74.0060, lat: 40.7128 },
{ label: 'Warehouse', duration: 5, unit: 'min' }
)
await handles.dropoffPoint(
{ lng: -73.9855, lat: 40.7580 },
{ label: 'Customer' }
)Navigation Streams
navigation()
Turn-by-turn navigation with real-time position updates.
Signature: navigation(journey: Journey): Promise<Stream>
Navigation Stream
Full navigation with events
// Start navigation
const navStream = await handles.navigation({
routeId: 'delivery-route-1',
origin: {
coords: await controls.getCurrentLocation(),
caption: { label: 'Current Location' }
},
destination: {
coords: { latitude: 40.7580, longitude: -73.9855 },
caption: { label: 'Delivery Address' }
},
waypoints: [
{
coords: { latitude: 40.7489, longitude: -73.9680 },
caption: { label: 'Package Pickup' },
index: 0
}
],
options: {
mode: 'navigation',
profile: 'driving-traffic'
}
})
// 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')
})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 endedpeerDirection()
Track peer navigation (show customer where driver is navigating).
Peer Navigation Stream
Show driver's navigation to customer
// Customer view: watch driver navigate to them
const driverNavStream = await handles.peerDirection({
peerId: driverId,
showRoute: true,
showProgress: true,
liveETA: true
})
driverNavStream.live(async (controls) => {
// Receive navigation updates from driver
socket.on('driver:navigation', async (update) => {
await controls.updatePosition({
position: update.location,
heading: update.bearing,
speed: update.speed
})
await controls.updateProgress({
distanceRemaining: update.distanceRemaining,
eta: update.eta
})
// Update customer UI
updateDriverETA(update.eta)
updateDistanceRemaining(update.distanceRemaining)
})
controls.on('nearby', (distance) => {
console.log('Driver is', distance, 'meters away')
if (distance < 100) {
showDriverNearbyNotification()
}
})
controls.on('arrived', () => {
console.log('Driver has arrived!')
showArrivalNotification()
})
})Stream Lifecycle
Managing Streams
// Create stream
const stream = handles.myLocation('client')
// Listen to data
stream.pipe(location => {
console.log('Location update:', location)
})
// Handle errors
stream.onerror(error => {
console.error('Stream error:', error)
})
// Handle close
stream.onclose(() => {
console.log('Stream closed')
})
// Close stream
stream.close()Stream Methods:
pipe(fn): Listen to stream datawrite(data): Write data to stream (for peer/nav streams)close(): Close the streamonerror(fn): Handle errorsonclose(fn): Handle stream close
Stream Cleanup
Always clean up streams when component unmounts:
// React example
useEffect(() => {
const stream = handles.myLocation('client')
stream.pipe(location => {
updateLocation(location)
})
stream.onerror(error => {
console.error('Location error:', error)
})
// Cleanup
return () => {
stream.close()
}
}, [])
// Vue example
let stream: any
onMounted(() => {
stream = handles.myLocation('client')
stream.pipe(location => {
updateLocation(location)
})
})
onUnmounted(() => {
stream?.close()
})Complete Examples
Delivery Tracking App
import De from '@de./sdk'
class DeliveryTracker {
private msi: any
private handles: any
private driverStream: any
private navStream: any
async initialize() {
this.msi = new De.MSI({
element: 'map',
accessToken: token
})
const { handles } = await this.msi.load()
this.handles = handles
}
async startTracking(order: Order) {
// Setup delivery points
await this.handles.pickupPoint(order.pickup.location, {
label: 'Pickup',
sublabel: order.pickup.address
})
await this.handles.dropoffPoint(order.delivery.location, {
label: 'Delivery',
sublabel: order.delivery.address
})
// Track driver
this.driverStream = await this.handles.peerLocation(
order.driver.location,
{
label: order.driver.name,
icon: 'car',
showTrail: true
}
)
// Update driver position
socket.on('driver:location', (update) => {
this.driverStream.live(async (controls) => {
await controls.move({
position: update.location,
heading: update.bearing,
animate: true
})
})
})
// Track customer location
const customerStream = await this.handles.myLocation({
showMarker: true,
markerOptions: {
label: 'You',
color: 'blue'
}
})
customerStream.live(async (controls) => {
const myLocation = await controls.getCurrentLocation()
// Calculate distance to driver
const distance = calculateDistance(
myLocation,
order.driver.location
)
updateDistanceDisplay(distance)
})
}
cleanup() {
this.driverStream?.end()
}
}Fleet Management Dashboard
class FleetDashboard {
private handles: any
private fleetStream: any
async initialize(vehicles: Vehicle[]) {
const msi = new De.MSI({
element: 'map',
accessToken: token
})
const { handles } = await msi.load()
this.handles = handles
// Initialize fleet stream
this.fleetStream = await handles.nearby({
entities: vehicles.map(v => ({
id: v.id,
type: 'vehicle',
position: v.location,
label: v.name,
heading: v.heading,
color: this.getStatusColor(v.status)
})),
clustering: true,
autoFit: true
})
// Real-time updates
this.subscribeToUpdates()
}
subscribeToUpdates() {
socket.on('vehicle:location', (update) => {
this.fleetStream.live(async (controls) => {
await controls.move(update.vehicleId, {
position: update.location,
heading: update.bearing,
animate: true
})
})
})
socket.on('vehicle:status', (update) => {
this.fleetStream.live(async (controls) => {
await controls.update(update.vehicleId, {
color: this.getStatusColor(update.status),
label: `${update.name} - ${update.status}`
})
})
})
socket.on('vehicle:added', (vehicle) => {
this.fleetStream.live(async (controls) => {
await controls.add({
id: vehicle.id,
type: 'vehicle',
position: vehicle.location,
label: vehicle.name
})
})
})
socket.on('vehicle:removed', (vehicleId) => {
this.fleetStream.live(async (controls) => {
await controls.remove(vehicleId)
})
})
}
getStatusColor(status: string): string {
const colors = {
'available': '#10b981',
'busy': '#0ea5e9',
'maintenance': '#f59e0b',
'offline': '#6b7280'
}
return colors[status] || '#6b7280'
}
async getVehiclesInArea(bounds: Bounds): Promise<Vehicle[]> {
let vehicles: Vehicle[] = []
this.fleetStream.live(async (controls) => {
const all = await controls.getAll()
vehicles = all.filter(v =>
this.isInBounds(v.position, bounds)
)
})
return vehicles
}
isInBounds(pos: Location, bounds: Bounds): boolean {
return (
pos.lng >= bounds.southwest.lng &&
pos.lng <= bounds.northeast.lng &&
pos.lat >= bounds.southwest.lat &&
pos.lat <= bounds.northeast.lat
)
}
}Type Definitions
interface StreamHandle<T = any> {
live(callback: (controls: T) => Promise<void>): void
pause(): void
resume(): void
end(): void
isActive(): boolean
on(event: string, handler: Function): void
off(event: string, handler: Function): void
}
interface LocationStreamControls {
getCurrentLocation(): Promise<Location>
updateLabel(label: string): Promise<void>
updateSublabel(sublabel: string): Promise<void>
move(options: MoveOptions): Promise<void>
hide(): Promise<void>
show(): Promise<void>
remove(): Promise<void>
}
interface FleetStreamControls {
add(entity: NearbyEntity): Promise<void>
move(id: string, options: MoveOptions): Promise<void>
update(id: string, updates: Partial<NearbyEntity>): Promise<void>
remove(id: string): Promise<void>
get(id: string): Promise<NearbyEntity | null>
getAll(): Promise<NearbyEntity[]>
clear(): Promise<void>
fitBounds(padding?: number): Promise<void>
}Best Practices
Do
- Always clean up streams on unmount
- Use handles for continuous operations
- Combine multiple handles for rich features
- Handle stream events appropriately
- Use pause/resume for temporary stops
Avoid
- Creating duplicate streams for same entity
- Forgetting to end streams
- Mixing handles and controls unnecessarily
- Ignoring stream lifecycle events
- Creating too many concurrent streams

