Skip to content

MSI Plugins & Advanced Usage

Extend the SDK with custom plugins and advanced techniques.

Plugin System Overview

Plugins provide a powerful way to:

  • Add custom functionality to the SDK
  • Integrate third-party services
  • Create reusable extensions
  • Access all SDK internals (controls, handles, utils)
  • Share functionality across projects

Plugin Architecture

Plugins are installed during MSI initialization and have full access to SDK features.

typescript
const myPlugin = {
  name: 'my-plugin',
  version: '1.0.0',
  
  async install(sdk, options) {
    // Plugin installation logic
    const { controls, handles, utils } = sdk
    
    // Your custom functionality
  }
}

const msi = new De.MSI({
  element: 'map',
  accessToken: token,
  plugins: [myPlugin]
})

Creating Plugins

Basic Plugin Structure

1

Simple Plugin

Minimal plugin implementation

typescript
const analyticsPlugin = {
  name: 'analytics',
  version: '1.0.0',
  
  async install(sdk, options) {
    const { controls } = sdk
    
    // Track all route creations
    controls.on('route:created', (route) => {
      analytics.track('Route Created', {
        distance: route.distance,
        duration: route.duration,
        waypoints: route.waypoints.length
      })
    })
    
    console.log('Analytics plugin installed')
  }
}

Plugin with Configuration

2

Configurable Plugin

Plugin with options

typescript
interface NotificationPluginOptions {
  sound?: boolean
  vibrate?: boolean
  desktop?: boolean
}

const notificationPlugin = {
  name: 'notifications',
  version: '1.0.0',
  
  async install(sdk, options: NotificationPluginOptions = {}) {
    const { controls } = sdk
    const config = {
      sound: true,
      vibrate: false,
      desktop: true,
      ...options
    }
    
    controls.on('navigation:arrived', () => {
      if (config.sound) playSound('arrival.mp3')
      if (config.vibrate) navigator.vibrate(200)
      if (config.desktop) showDesktopNotification('You have arrived!')
    })
  }
}

// Use with config
const msi = new De.MSI({
  element: 'map',
  accessToken: token,
  plugins: [
    { 
      plugin: notificationPlugin, 
      options: { sound: false, vibrate: true } 
    }
  ]
})

Plugin with Methods

3

Plugin with API

Expose plugin methods

typescript
class GeofencePlugin {
  name = 'geofence'
  version = '1.0.0'
  
  private geofences: Map<string, Geofence> = new Map()
  private controls: any
  
  async install(sdk, options) {
    this.controls = sdk.controls
    
    // Track location and check geofences
    sdk.controls.on('location:updated', (location) => {
      this.checkGeofences(location)
    })
  }
  
  // Public API
  addGeofence(id: string, center: Location, radius: number) {
    this.geofences.set(id, {
      id,
      center,
      radius,
      active: true
    })
    
    // Draw on map
    this.controls.addCircle({
      id: `geofence-${id}`,
      center,
      radius,
      color: '#0ea5e9',
      opacity: 0.2
    })
  }
  
  removeGeofence(id: string) {
    this.geofences.delete(id)
    this.controls.removeCircle(`geofence-${id}`)
  }
  
  private checkGeofences(location: Location) {
    this.geofences.forEach((fence) => {
      const distance = calculateDistance(location, fence.center)
      
      if (distance <= fence.radius && fence.active) {
        this.onEnter(fence)
      } else if (distance > fence.radius && !fence.active) {
        this.onExit(fence)
      }
    })
  }
  
  private onEnter(fence: Geofence) {
    fence.active = true
    console.log('Entered geofence:', fence.id)
    // Trigger event
  }
  
  private onExit(fence: Geofence) {
    fence.active = false
    console.log('Exited geofence:', fence.id)
    // Trigger event
  }
}

// Usage
const geofencePlugin = new GeofencePlugin()

const msi = new De.MSI({
  element: 'map',
  accessToken: token,
  plugins: [geofencePlugin]
})

// Access plugin methods
geofencePlugin.addGeofence('warehouse', warehouseLocation, 500)

Built-in Plugin Hooks

Access all SDK features within your plugin.

Controls Hook

Direct access to all Controls API methods.

typescript
const plugin = {
  name: 'route-optimizer',
  
  async install(sdk) {
    const { controls } = sdk
    
    // Use any controls method
    controls.on('route:created', async (route) => {
      // Analyze route
      const alternatives = await controls.getAlternativeRoutes()
      
      // Find best route
      const best = alternatives.reduce((best, current) => 
        current.durationInTraffic < best.durationInTraffic ? current : best
      )
      
      // Suggest if better
      if (best.durationInTraffic < route.durationInTraffic - 300) {
        await controls.suggestRoute(best)
      }
    })
  }
}

Handles Hook

Access stream-based operations.

typescript
const plugin = {
  name: 'live-tracker',
  
  async install(sdk) {
    const { handles } = sdk
    
    // Use handles for continuous operations
    const locationStream = await handles.myLocation()
    
    locationStream.live(async (controls) => {
      const location = await controls.getCurrentLocation()
      
      // Send to backend
      sendLocationUpdate(location)
    })
  }
}

Utils Hook

Access utility functions.

typescript
const plugin = {
  name: 'distance-calculator',
  
  async install(sdk) {
    const { utils } = sdk
    
    // Use utils
    const distance = utils.calculateDistance(point1, point2)
    const formatted = utils.formatDistance(distance)
    const bounds = utils.calculateBounds([point1, point2, point3])
  }
}

Example Plugins

Analytics Plugin

Track user interactions and map events.

typescript
class AnalyticsPlugin {
  name = 'analytics'
  version = '1.0.0'
  
  private analyticsId: string
  
  constructor(analyticsId: string) {
    this.analyticsId = analyticsId
  }
  
  async install(sdk) {
    const { controls } = sdk
    
    // Track route events
    controls.on('route:created', (route) => {
      this.track('Route Created', {
        distance: route.distance,
        duration: route.duration,
        waypoints: route.waypoints.length,
        profile: route.options?.profile
      })
    })
    
    // Track navigation events
    controls.on('navigation:started', () => {
      this.track('Navigation Started')
    })
    
    controls.on('navigation:complete', () => {
      this.track('Navigation Completed')
    })
    
    // Track map interactions
    controls.on('map:clicked', (location) => {
      this.track('Map Clicked', {
        lat: location.lat,
        lng: location.lng
      })
    })
    
    // Track search
    controls.on('search:query', (query) => {
      this.track('Search Performed', { query })
    })
  }
  
  private track(event: string, properties?: any) {
    // Send to analytics service
    fetch('https://analytics.example.com/track', {
      method: 'POST',
      body: JSON.stringify({
        event,
        properties,
        timestamp: Date.now(),
        userId: this.analyticsId
      })
    })
  }
}

// Usage
const analytics = new AnalyticsPlugin('user-123')
const msi = new De.MSI({
  element: 'map',
  accessToken: token,
  plugins: [analytics]
})

Performance Monitor Plugin

Monitor and optimize SDK performance.

typescript
class PerformanceMonitorPlugin {
  name = 'performance'
  version = '1.0.0'
  
  private metrics: Map<string, number[]> = new Map()
  
  async install(sdk) {
    const { controls } = sdk
    
    // Monitor route calculation time
    this.measureAsync('route:calculation', 
      controls, 
      'route:created'
    )
    
    // Monitor location updates
    this.measureAsync('location:update', 
      controls, 
      'location:updated'
    )
    
    // Report metrics every 30 seconds
    setInterval(() => this.report(), 30000)
  }
  
  private measureAsync(name: string, controls: any, event: string) {
    const startTimes = new Map()
    
    controls.on(`${event}:start`, () => {
      startTimes.set(event, performance.now())
    })
    
    controls.on(event, () => {
      const start = startTimes.get(event)
      if (start) {
        const duration = performance.now() - start
        this.recordMetric(name, duration)
        startTimes.delete(event)
      }
    })
  }
  
  private recordMetric(name: string, value: number) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, [])
    }
    this.metrics.get(name)!.push(value)
  }
  
  private report() {
    const report: any = {}
    
    this.metrics.forEach((values, name) => {
      report[name] = {
        count: values.length,
        avg: values.reduce((a, b) => a + b, 0) / values.length,
        min: Math.min(...values),
        max: Math.max(...values)
      }
    })
    
    console.log('Performance Report:', report)
    
    // Send to monitoring service
    sendToMonitoring(report)
    
    // Clear metrics
    this.metrics.clear()
  }
}

Custom Marker Plugin

Add custom marker types and behaviors.

typescript
class CustomMarkerPlugin {
  name = 'custom-markers'
  version = '1.0.0'
  
  private controls: any
  
  async install(sdk) {
    this.controls = sdk.controls
  }
  
  // Animated delivery marker
  async addDeliveryMarker(location: Location, status: string) {
    const icon = this.getDeliveryIcon(status)
    const color = this.getStatusColor(status)
    
    await this.controls.addMarker({
      id: `delivery-${Date.now()}`,
      position: location,
      icon,
      color,
      animation: status === 'in_transit' ? 'bounce' : 'none'
    })
  }
  
  // Pulsing alert marker
  async addAlertMarker(location: Location, severity: string) {
    await this.controls.addMarker({
      id: `alert-${Date.now()}`,
      position: location,
      icon: 'warning',
      color: severity === 'high' ? '#ef4444' : '#f59e0b',
      pulse: true,
      onClick: () => {
        this.showAlertDetails(location)
      }
    })
  }
  
  // Cluster marker with count
  async addClusterMarker(center: Location, count: number) {
    await this.controls.addMarker({
      id: `cluster-${Date.now()}`,
      position: center,
      label: count.toString(),
      style: 'cluster',
      onClick: () => {
        this.expandCluster(center)
      }
    })
  }
  
  private getDeliveryIcon(status: string): string {
    const icons = {
      'pending': 'package',
      'in_transit': 'truck',
      'delivered': 'check-circle',
      'failed': 'x-circle'
    }
    return icons[status] || 'package'
  }
  
  private getStatusColor(status: string): string {
    const colors = {
      'pending': '#6b7280',
      'in_transit': '#0ea5e9',
      'delivered': '#10b981',
      'failed': '#ef4444'
    }
    return colors[status] || '#6b7280'
  }
}

Offline Support Plugin

Cache data for offline usage.

typescript
class OfflinePlugin {
  name = 'offline'
  version = '1.0.0'
  
  private cache: Map<string, any> = new Map()
  private db: IDBDatabase | null = null
  
  async install(sdk) {
    const { controls } = sdk
    
    // Initialize IndexedDB
    await this.initDB()
    
    // Cache routes
    controls.on('route:created', async (route) => {
      await this.cacheRoute(route)
    })
    
    // Cache search results
    controls.on('search:results', async (results) => {
      await this.cacheSearch(results)
    })
    
    // Intercept network requests
    this.interceptRequests(controls)
  }
  
  private async initDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('de-sdk-cache', 1)
      
      request.onsuccess = () => {
        this.db = request.result
        resolve(this.db)
      }
      
      request.onerror = () => reject(request.error)
      
      request.onupgradeneeded = (event: any) => {
        const db = event.target.result
        db.createObjectStore('routes', { keyPath: 'id' })
        db.createObjectStore('searches', { keyPath: 'query' })
      }
    })
  }
  
  private async cacheRoute(route: any) {
    if (!this.db) return
    
    const transaction = this.db.transaction(['routes'], 'readwrite')
    const store = transaction.objectStore('routes')
    
    store.put({
      id: route.id,
      data: route,
      timestamp: Date.now()
    })
  }
  
  private interceptRequests(controls: any) {
    const originalRequest = controls.request
    
    controls.request = async (...args: any[]) => {
      if (!navigator.onLine) {
        // Return cached data when offline
        return this.getCachedData(args)
      }
      
      return originalRequest.apply(controls, args)
    }
  }
}

Advanced Patterns

Multi-Instance Management

Manage multiple map instances with plugins.

typescript
class MultiMapManager {
  private instances: Map<string, any> = new Map()
  
  async createMap(id: string, element: string, plugins: any[] = []) {
    const msi = new De.MSI({
      element,
      accessToken: token,
      plugins: [
        ...plugins,
        this.createSyncPlugin(id)
      ]
    })
    
    await msi.load()
    this.instances.set(id, msi)
    
    return msi
  }
  
  private createSyncPlugin(mapId: string) {
    return {
      name: `sync-${mapId}`,
      
      install: (sdk) => {
        // Sync events across maps
        sdk.controls.on('route:created', (route) => {
          this.broadcastToOthers(mapId, 'route:created', route)
        })
      }
    }
  }
  
  private broadcastToOthers(sourceId: string, event: string, data: any) {
    this.instances.forEach((instance, id) => {
      if (id !== sourceId) {
        instance.trigger(event, data)
      }
    })
  }
}

State Management Integration

Integrate with Redux/Vuex.

typescript
// Redux plugin
class ReduxPlugin {
  name = 'redux'
  
  constructor(private store: any) {}
  
  async install(sdk) {
    const { controls } = sdk
    
    // Dispatch actions for SDK events
    controls.on('route:created', (route) => {
      this.store.dispatch({
        type: 'MAP_ROUTE_CREATED',
        payload: route
      })
    })
    
    controls.on('location:updated', (location) => {
      this.store.dispatch({
        type: 'MAP_LOCATION_UPDATED',
        payload: location
      })
    })
    
    // Listen to store changes
    this.store.subscribe(() => {
      const state = this.store.getState()
      
      if (state.map.updateRequired) {
        this.syncWithStore(controls, state)
      }
    })
  }
  
  private async syncWithStore(controls: any, state: any) {
    if (state.map.selectedRoute) {
      await controls.selectRoute(state.map.selectedRoute)
    }
  }
}

Memory Management

Optimize resource usage.

typescript
class MemoryManagerPlugin {
  name = 'memory-manager'
  
  private markerPool: any[] = []
  private maxMarkers = 100
  
  async install(sdk) {
    const { controls } = sdk
    
    // Monitor marker count
    controls.on('marker:added', () => {
      this.checkMarkerLimit(controls)
    })
    
    // Cleanup old entities
    setInterval(() => {
      this.cleanup(controls)
    }, 60000)
  }
  
  private async checkMarkerLimit(controls: any) {
    const markers = await controls.getAllMarkers()
    
    if (markers.length > this.maxMarkers) {
      // Remove oldest markers
      const toRemove = markers
        .sort((a, b) => a.timestamp - b.timestamp)
        .slice(0, markers.length - this.maxMarkers)
      
      for (const marker of toRemove) {
        await controls.removeMarker(marker.id)
      }
    }
  }
  
  private async cleanup(controls: any) {
    // Remove markers not visible for 5 minutes
    const markers = await controls.getAllMarkers()
    const now = Date.now()
    
    for (const marker of markers) {
      if (now - marker.lastVisible > 5 * 60 * 1000) {
        await controls.removeMarker(marker.id)
      }
    }
  }
}

Best Practices

Do

  • Keep plugins focused on single responsibility
  • Properly clean up resources in uninstall
  • Use TypeScript for better plugin development
  • Document your plugin API
  • Version your plugins
  • Handle errors gracefully

Avoid

  • Modifying SDK internals directly
  • Creating memory leaks
  • Blocking the main thread
  • Storing sensitive data
  • Conflicting with other plugins
  • Over-engineering simple features

Type Definitions

typescript
interface Plugin {
  name: string
  version?: string
  install(sdk: SDKContext, options?: any): Promise<void> | void
  uninstall?(): Promise<void> | void
}

interface SDKContext {
  controls: MSIControls
  handles: MSIHandles
  utils: SDKUtils
  config: SDKConfig
}

interface PluginConfig {
  plugin: Plugin
  options?: any
}

Next Steps