Skip to content

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.

typescript
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

1

Track User Location

Continuous location streaming

typescript
// 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

2

Advanced Location Tracking

With custom logic

typescript
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

1

Track Peer Location

Monitor external entity position

typescript
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, speed
  • caption (optional): Display label, sublabel, and description

Stream Methods:

  • write(data): Update position and caption
  • close(): End the stream
  • onerror(fn): Handle errors
  • onclose(fn): Handle stream close
2

Multiple Peer Tracking

Track multiple entities simultaneously

typescript
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

1

Location Picker

User selects location by dragging

typescript
// 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:

typescript
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

1

Fleet Management Stream

Track and manage vehicle fleet

typescript
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:

typescript
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:

typescript
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
}
2

Real-time Fleet Updates

Sync fleet with live data

typescript
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>

1

Quick Pickup Point

One-line pickup setup

typescript
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>

2

Quick Dropoff Point

One-line delivery setup

typescript
await handles.dropoffPoint(
  { lng: -73.9855, lat: 40.7580 },
  {
    label: 'Customer Address',
    duration: 15,
    unit: 'min'
  }
)
3

Complete Delivery Setup

Quick route creation

typescript
// 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' }
)

Turn-by-turn navigation with real-time position updates.

Signature: navigation(journey: Journey): Promise<Stream>

1

Navigation Stream

Full navigation with events

typescript
// 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:

typescript
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 ended

peerDirection()

Track peer navigation (show customer where driver is navigating).

1

Peer Navigation Stream

Show driver's navigation to customer

typescript
// 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

typescript
// 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 data
  • write(data): Write data to stream (for peer/nav streams)
  • close(): Close the stream
  • onerror(fn): Handle errors
  • onclose(fn): Handle stream close

Stream Cleanup

Always clean up streams when component unmounts:

typescript
// 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

typescript
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

typescript
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

typescript
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

Next Steps