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
}
