Skip to content

Cross-App Communication

Applications in Canvas UI can communicate with each other through several mechanisms, allowing them to share data and coordinate actions. This guide explains the available communication methods and when to use each one.

Why Cross-App Communication?

Cross-app communication is essential for building complex workflows that span multiple applications. For example:

  • An ordering app might need to share product selections with a shopping cart app
  • A meeting notes app might need to communicate with a calendar app
  • A search app might need to display results in a viewer app
  • A dashboard app might need to open specific canvases when the user clicks on visualizations

Communication Methods

1. Canvas Context

The primary and recommended method for sharing persistent data between apps is through the Canvas context.

typescript
// Updating context from one app
import { useUi } from '@pitcher/canvas-ui'

// Update current canvas context - will be available to all apps
await useUi().updateCanvas({
  context: {
    pitcher: {
      completion_wizard: {
        completed: true
      }
    },
    analytics: {
      last_interaction: new Date().toISOString(),
      user_actions: ['viewed_product', 'added_to_cart']
    }
  }
})

// Reading context in another app
import { useCanvas } from '@canvas-builder/composables/useCanvas'

// Access shared data
const { activeCanvas } = useCanvas()
const isWizardCompleted = computed(() => activeCanvas.value?.context?.pitcher?.completion_wizard?.completed)
const analyticsData = computed(() => activeCanvas.value?.context?.analytics)

Canvas context is:

  • Persisted on the server
  • Available to all apps and components
  • Automatically synchronized between apps
  • Ideal for sharing data that needs to persist across sessions

2. Event Broadcasting

For real-time communication between apps (when persistence isn't needed), use the event broadcasting system:

typescript
// Broadcasting an event from one app
import { ClientApi } from '@api/client'
import { PitcherEventName } from '@pitcher/canvas-ui'

// Broadcast a canvas update event
ClientApi.broadcast({
  type: PitcherEventName.CANVAS_UPDATED,
  body: {
    id: 'canvas-id',
    context: {
      analytics: {
        last_viewed: new Date().toISOString()
      }
    }
  }
})

// Broadcast a file opened event
ClientApi.broadcast({
  type: PitcherEventName.FILE_OPENED,
  body: {
    file_id: '01HJ5RCBH631K4JDHWAQB0RPR6',
    view_id: 'content-view-123'
  }
})

// Listening for events in another app
import { usePitcherApi } from '@pitcher/canvas-ui'

// Subscribe to events
const unsubscribe = usePitcherApi().subscribe((event) => {
  if (event.type === PitcherEventName.CANVAS_UPDATED_SUCCESS) {
    // Handle the canvas update success event
    const updatedCanvas = event.body
    // Process the data...
  } else if (event.type === PitcherEventName.FILE_OPENED) {
    // Handle the file opened event
    const { file_id, view_id } = event.body
    // Process the data...
  }
})

// Don't forget to unsubscribe when the component is unmounted
onUnmounted(() => {
  unsubscribe()
})

Event broadcasting is:

  • Real-time
  • Not persisted
  • Useful for transient communication
  • Good for triggering actions in other apps

3. Browser Storage (localStorage)

Destructive localStorage Commands

Be careful with destructive localStorage operations like localStorage.clear() or clearing specific keys used by the Pitcher platform. These operations will also affect the application itself and may cause the user to be logged out or lose important session data. Always scope your localStorage operations to your specific application keys to avoid interfering with the platform's functionality.

For simple data sharing that doesn't require server persistence, localStorage can be an effective solution:

typescript
// Using VueUse for reactive localStorage in a Sharebox component
import { useLocalStorage } from '@vueuse/core'

// Define sharebox types
type ShareboxItem = {
  id: string
  fileId: string
  name: string
  type: 'file' | 'page'
  pageIndex?: number
  thumbnailUrl?: string
}

type ShareboxLocalData = {
  items: ShareboxItem[]
  instanceId?: string
  userId?: string
}

// In first app - initialize and use the store
const store = useLocalStorage<ShareboxLocalData>('sharebox', {
  items: [],
  instanceId: undefined,
  userId: undefined
})

// Add items to sharebox
const addItem = (item: ShareboxItem) => {
  if (!store.value.items.find(existingItem => existingItem.id === item.id)) {
    store.value.items.push(item)
  }
}

// Clear the sharebox
const clearSharebox = () => {
  store.value.items = []
}

// In second app - access the same data
const shareboxStore = useLocalStorage<ShareboxLocalData>('sharebox', {
  items: [],
  instanceId: undefined,
  userId: undefined
}, {
  listenToStorageChanges: true // React to changes from other apps/tabs
})

// Access items reactively
const itemCount = computed(() => shareboxStore.value.items.length)

localStorage is:

  • Immediately available (no server roundtrip)
  • Persisted in the browser only (not on the server)
  • Limited to the current browser
  • Suitable for user-specific temporary data
  • Useful for sharing state between apps in the same session

4. Apps DB

For more structured data storage that needs to be available across sessions and devices, Apps DB provides a key-value store:

typescript
import { useAppsDb } from '@lib/composables/appsDb.use'
import { APPS_DB } from '@lib/constants/appsDb.const'

// Store pitch master agenda items in Apps DB
const createAgenda = async (items) => {
  if (!myUser.value?.id) throw new Error('User ID missing')
  
  return useAppsDb({ pitcherInfo, restApiAxios }).upsertEntry({
    store_name: APPS_DB.STORES.PITCH_MASTER,
    user_id: myUser.value.id,
    entry_key: APPS_DB.ENTRIES.AGENDA,
    data: items
  })
}

// Retrieve agenda items in another app
const getAgenda = async () => {
  if (!myUser.value?.id) throw new Error('User ID missing')
  
  return useAppsDb({ pitcherInfo, restApiAxios }).getEntry({
    store_name: APPS_DB.STORES.PITCH_MASTER,
    user_id: myUser.value.id,
    entry_key: APPS_DB.ENTRIES.AGENDA
  }) as Promise<{ value: PitcherMasterItem[]; id: string }>
}

// Delete agenda when no longer needed
const deleteAgenda = async () => {
  if (!myUser.value?.id) throw new Error('User ID missing')
  
  return useAppsDb({ pitcherInfo, restApiAxios }).deleteEntry({
    store_name: APPS_DB.STORES.PITCH_MASTER,
    user_id: myUser.value.id,
    entry_key: APPS_DB.ENTRIES.AGENDA
  })
}

Apps DB is:

  • Persisted on the server
  • Available across sessions and devices
  • Scoped to instance or user-instance
  • Structured with store/key patterns
  • Good for configuration data

Choosing the Right Method

  • Canvas Context: For data that should persist with the canvas and be available to all components
  • Event Broadcasting: For real-time communication without persistence
  • localStorage: For session-specific data sharing with immediate availability
  • Apps DB: For structured data that needs to be available across sessions and devices

Best Practices

  1. Use canvas context for data that conceptually belongs to the canvas
  2. Use event broadcasting for transient communication that doesn't need persistence
  3. Document your event types and payloads for better maintainability
  4. Use localStorage for immediate cross-app state sharing within the same session
  5. Clear localStorage data when it's no longer needed to avoid accumulation
  6. Use Apps DB for more structured and persistent data storage