Update websocket logic to ignore events trigger by API requests originating from the same session
This commit is contained in:
parent
bdc03e12cc
commit
d8d3d71523
|
@ -16,6 +16,7 @@ export enum Header {
|
|||
LICENSE_KEY = "x-budibase-license-key",
|
||||
API_VER = "x-budibase-api-version",
|
||||
APP_ID = "x-budibase-app-id",
|
||||
SESSION_ID = "x-budibase-session-id",
|
||||
TYPE = "x-budibase-type",
|
||||
PREVIEW_ROLE = "x-budibase-role",
|
||||
TENANT_ID = "x-budibase-tenant-id",
|
||||
|
|
|
@ -18,7 +18,9 @@ export const initWebsocket = () => {
|
|||
}
|
||||
|
||||
// Initialise connection
|
||||
socket = createWebsocket("/socket/client", false)
|
||||
socket = createWebsocket("/socket/client", {
|
||||
heartbeat: false,
|
||||
})
|
||||
|
||||
// Event handlers
|
||||
socket.on("plugin-update", data => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Helpers } from "@budibase/bbui"
|
||||
import { ApiVersion } from "../constants"
|
||||
import { buildAnalyticsEndpoints } from "./analytics"
|
||||
import { buildAppEndpoints } from "./app"
|
||||
|
@ -30,6 +31,14 @@ import { buildEnvironmentVariableEndpoints } from "./environmentVariables"
|
|||
import { buildEventEndpoints } from "./events"
|
||||
import { buildAuditLogsEndpoints } from "./auditLogs"
|
||||
|
||||
/**
|
||||
* Random identifier to uniquely identify a session in a tab. This is
|
||||
* used to determine the originator of calls to the API, which is in
|
||||
* turn used to determine who caused a websocket message to be sent, so
|
||||
* that we can ignore events caused by ourselves.
|
||||
*/
|
||||
export const APISessionID = Helpers.uuid()
|
||||
|
||||
const defaultAPIClientConfig = {
|
||||
/**
|
||||
* Certain definitions can't change at runtime for client apps, such as the
|
||||
|
@ -116,6 +125,7 @@ export const createAPIClient = config => {
|
|||
|
||||
// Build headers
|
||||
let headers = { Accept: "application/json" }
|
||||
headers["x-budibase-session-id"] = APISessionID
|
||||
if (!external) {
|
||||
headers["x-budibase-api-version"] = ApiVersion
|
||||
}
|
||||
|
|
|
@ -26,15 +26,15 @@ export const createGridWebsocket = context => {
|
|||
})
|
||||
|
||||
// User events
|
||||
socket.on(SocketEvent.UserUpdate, user => {
|
||||
socket.onOther(SocketEvent.UserUpdate, user => {
|
||||
users.actions.updateUser(user)
|
||||
})
|
||||
socket.on(SocketEvent.UserDisconnect, user => {
|
||||
socket.onOther(SocketEvent.UserDisconnect, user => {
|
||||
users.actions.removeUser(user)
|
||||
})
|
||||
|
||||
// Row events
|
||||
socket.on(GridSocketEvent.RowChange, async data => {
|
||||
socket.onOther(GridSocketEvent.RowChange, async data => {
|
||||
if (data.id) {
|
||||
rows.actions.replaceRow(data.id, data.row)
|
||||
} else if (data.row.id) {
|
||||
|
@ -44,7 +44,7 @@ export const createGridWebsocket = context => {
|
|||
})
|
||||
|
||||
// Table events
|
||||
socket.on(GridSocketEvent.TableChange, data => {
|
||||
socket.onOther(GridSocketEvent.TableChange, data => {
|
||||
// Only update table if one exists. If the table was deleted then we don't
|
||||
// want to know - let the builder navigate away
|
||||
if (data.table) {
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
import { io } from "socket.io-client"
|
||||
import { SocketEvent, SocketSessionTTL } from "@budibase/shared-core"
|
||||
import { APISessionID } from "../api"
|
||||
|
||||
export const createWebsocket = (path, heartbeat = true) => {
|
||||
const DefaultOptions = {
|
||||
heartbeat: true,
|
||||
}
|
||||
|
||||
export const createWebsocket = (path, options = DefaultOptions) => {
|
||||
if (!path) {
|
||||
throw "A websocket path must be provided"
|
||||
}
|
||||
const { heartbeat } = {
|
||||
...DefaultOptions,
|
||||
...options,
|
||||
}
|
||||
|
||||
// Determine connection info
|
||||
const tls = location.protocol === "https:"
|
||||
const proto = tls ? "wss:" : "ws:"
|
||||
const host = location.hostname
|
||||
const port = location.port || (tls ? 443 : 80)
|
||||
const socket = io(`${proto}//${host}:${port}`, {
|
||||
let socket = io(`${proto}//${host}:${port}`, {
|
||||
path,
|
||||
// Cap reconnection attempts to 3 (total of 15 seconds before giving up)
|
||||
reconnectionAttempts: 3,
|
||||
|
@ -37,5 +46,17 @@ export const createWebsocket = (path, heartbeat = true) => {
|
|||
clearInterval(interval)
|
||||
})
|
||||
|
||||
// Helper utility to ignore events that were triggered due to API
|
||||
// changes made by us
|
||||
socket.onOther = (event, callback) => {
|
||||
socket.on(event, data => {
|
||||
if (data?.apiSessionId !== APISessionID) {
|
||||
callback(data)
|
||||
} else {
|
||||
console.log("ignore", event, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return socket
|
||||
}
|
||||
|
|
|
@ -46,29 +46,32 @@ export default class BuilderSocket extends BaseSocket {
|
|||
}
|
||||
|
||||
emitTableUpdate(ctx: any, table: Table) {
|
||||
this.io
|
||||
.in(ctx.appId)
|
||||
.emit(BuilderSocketEvent.TableChange, { id: table._id, table })
|
||||
gridSocket?.emitTableUpdate(table)
|
||||
this.emitToRoom(ctx, ctx.appId, BuilderSocketEvent.TableChange, {
|
||||
id: table._id,
|
||||
table,
|
||||
})
|
||||
gridSocket?.emitTableUpdate(ctx, table)
|
||||
}
|
||||
|
||||
emitTableDeletion(ctx: any, id: string) {
|
||||
this.io
|
||||
.in(ctx.appId)
|
||||
.emit(BuilderSocketEvent.TableChange, { id, table: null })
|
||||
gridSocket?.emitTableDeletion(id)
|
||||
this.emitToRoom(ctx, ctx.appId, BuilderSocketEvent.TableChange, {
|
||||
id,
|
||||
table: null,
|
||||
})
|
||||
gridSocket?.emitTableDeletion(ctx, id)
|
||||
}
|
||||
|
||||
emitDatasourceUpdate(ctx: any, datasource: Datasource) {
|
||||
this.io.in(ctx.appId).emit(BuilderSocketEvent.DatasourceChange, {
|
||||
this.emitToRoom(ctx, ctx.appId, BuilderSocketEvent.DatasourceChange, {
|
||||
id: datasource._id,
|
||||
datasource,
|
||||
})
|
||||
}
|
||||
|
||||
emitDatasourceDeletion(ctx: any, id: string) {
|
||||
this.io
|
||||
.in(ctx.appId)
|
||||
.emit(BuilderSocketEvent.DatasourceChange, { id, datasource: null })
|
||||
this.emitToRoom(ctx, ctx.appId, BuilderSocketEvent.DatasourceChange, {
|
||||
id,
|
||||
datasource: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,21 +31,25 @@ export default class GridSocket extends BaseSocket {
|
|||
|
||||
emitRowUpdate(ctx: any, row: Row) {
|
||||
const tableId = getTableId(ctx)
|
||||
this.io.in(tableId).emit(GridSocketEvent.RowChange, { id: row._id, row })
|
||||
this.emitToRoom(ctx, tableId, GridSocketEvent.RowChange, {
|
||||
id: row._id,
|
||||
row,
|
||||
})
|
||||
}
|
||||
|
||||
emitRowDeletion(ctx: any, id: string) {
|
||||
const tableId = getTableId(ctx)
|
||||
this.io.in(tableId).emit(GridSocketEvent.RowChange, { id, row: null })
|
||||
this.emitToRoom(ctx, tableId, GridSocketEvent.RowChange, { id, row: null })
|
||||
}
|
||||
|
||||
emitTableUpdate(table: Table) {
|
||||
this.io
|
||||
.in(table._id!)
|
||||
.emit(GridSocketEvent.TableChange, { id: table._id, table })
|
||||
emitTableUpdate(ctx: any, table: Table) {
|
||||
this.emitToRoom(ctx, table._id!, GridSocketEvent.TableChange, {
|
||||
id: table._id,
|
||||
table,
|
||||
})
|
||||
}
|
||||
|
||||
emitTableDeletion(id: string) {
|
||||
this.io.in(id).emit(GridSocketEvent.TableChange, { id, table: null })
|
||||
emitTableDeletion(ctx: any, id: string) {
|
||||
this.emitToRoom(ctx, id, GridSocketEvent.TableChange, { id, table: null })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import http from "http"
|
|||
import Koa from "koa"
|
||||
import Cookies from "cookies"
|
||||
import { userAgent } from "koa-useragent"
|
||||
import { auth, redis } from "@budibase/backend-core"
|
||||
import { auth, Header, redis } from "@budibase/backend-core"
|
||||
import currentApp from "../middleware/currentapp"
|
||||
import { createAdapter } from "@socket.io/redis-adapter"
|
||||
import { Socket } from "socket.io"
|
||||
|
@ -271,4 +271,13 @@ export class BaseSocket {
|
|||
emit(event: string, payload: any) {
|
||||
this.io.sockets.emit(event, payload)
|
||||
}
|
||||
|
||||
// Emit an event to everyone in a room, including metadata of whom
|
||||
// the originator of the request was
|
||||
emitToRoom(ctx: any, room: string, event: string, payload: any) {
|
||||
this.io.in(room).emit(event, {
|
||||
...payload,
|
||||
apiSessionId: ctx.headers?.[Header.SESSION_ID],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue