The app initializes a Socket.IO connection that connects to the Frappe realtime server:
export function initSocket() {
let host = window.location.hostname
let siteName = window.site_name
let port = window.location.port ? `:${socketio_port}` : ''
let protocol = port ? 'http' : 'https'
let url = `${protocol}://${host}${port}/${siteName}`
let socket = io(url, {
withCredentials: true,
reconnectionAttempts: 5,
})
socket.on('refetch_resource', (data) => {
if (data.cache_key) {
let resource =
getCachedResource(data.cache_key) ||
getCachedListResource(data.cache_key)
if (resource) {
resource.reload()
}
}
})
return socket
}Key features:
- Reads
socketio_portfrom Frappe config - Multi-tenant support via site namespace (
/${siteName}) - Automatic resource refetching via
refetch_resourceevent - Integrates with frappe-ui's resource caching system
The socket is made globally available throughout the Vue app:
let socket
if (import.meta.env.DEV) {
frappeRequest({ url: '/api/method/crm.www.crm.get_context_for_dev' }).then(
(values) => {
for (let key in values) {
window[key] = values[key]
}
socket = initSocket()
app.config.globalProperties.$socket = socket
app.mount('#app')
},
)
} else {
socket = initSocket()
app.config.globalProperties.$socket = socket
app.mount('#app')
}Stores provide centralized access to the socket:
import { defineStore } from 'pinia'
import { getCurrentInstance, ref } from 'vue'
export const globalStore = defineStore('crm-global', () => {
const app = getCurrentInstance()
const { $dialog, $socket } = app.appContext.config.globalProperties
// ... other store logic
return {
$dialog,
$socket,
// ... other exports
}
})onMounted(() => {
$socket.on('crm_notification', () => {
notificationsStore().notifications.reload()
})
})
onBeforeUnmount(() => {
$socket.off('crm_notification')
})onMounted(() => {
$socket.on('whatsapp_message', (data) => {
if (
data.reference_doctype === props.doctype &&
data.reference_name === doc.value.data.name
) {
whatsappMessages.reload()
}
})
})
onBeforeUnmount(() => {
$socket.off('whatsapp_message')
})class CRMNotification(Document):
def on_update(self):
frappe.publish_realtime("crm_notification")def on_update(doc, method):
frappe.publish_realtime(
"whatsapp_message",
{
"reference_doctype": doc.reference_doctype,
"reference_name": doc.reference_name,
},
)Resources created with a cache key can be automatically refetched from the backend:
const lead = createResource({
url: 'crm.fcrm.doctype.crm_lead.api.get_lead',
params: { name: props.leadId },
cache: ['lead', props.leadId], // Cache key enables auto-refetch
auto: true,
})When the backend calls:
frappe.publish_realtime('refetch_resource', {'cache_key': ['lead', 'LEAD-001']})The socket handler automatically finds and reloads the matching resource.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Vue Frontend β
β β
β ββββββββββββββββ ββββββββββββββββ β
β β Component βββββΆβ $socket.on β β
β β (Mounted) β β (listener) β β
β ββββββββββββββββ ββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββ β
β β socket.io-clientβ β
β βββββββββββββββββββ β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β WebSocket
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frappe SocketIO Server (Node.js) β
β /realtime β
β β
β β’ Multi-tenant (namespaced by site) β
β β’ Redis pub/sub integration β
β β’ Cookie/Token authentication β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β Redis Pub/Sub
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frappe Backend (Python) β
β β
β frappe.publish_realtime('event_name', data) β
β frappe.publish_progress(...) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Global Socket Instance: Socket is initialized once and made globally available
- Lifecycle Management: Always clean up listeners in
onBeforeUnmount - Filtered Events: Components filter events based on context (e.g., checking doctype/name)
- Resource Reloading: Trigger
resource.reload()on realtime events - Cache-based Auto-refetch: Resources with cache keys can be auto-refetched
- Backend Hooks: Use DocType hooks (
on_update, etc.) to publish realtime events
This implementation follows Frappe's realtime documentation and integrates seamlessly with frappe-ui's resource management system!