Add WIP initial multi-user websocket implementation for sheets
This commit is contained in:
parent
01867f5736
commit
36c953443f
|
@ -15,12 +15,12 @@
|
|||
}
|
||||
|
||||
const goToBuilder = () => {
|
||||
if (app.lockedOther) {
|
||||
notifications.error(
|
||||
`App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
||||
)
|
||||
return
|
||||
}
|
||||
// if (app.lockedOther) {
|
||||
// notifications.error(
|
||||
// `App locked by ${app.lockedBy.email}. Please allow lock to expire or have them unlock this app.`
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
$goto(`../../app/${app.devId}`)
|
||||
}
|
||||
|
||||
|
@ -59,9 +59,7 @@
|
|||
<div class="app-row-actions">
|
||||
<AppLockModal {app} buttonSize="M" />
|
||||
<Button size="S" secondary on:click={goToOverview}>Manage</Button>
|
||||
<Button size="S" primary disabled={app.lockedOther} on:click={goToBuilder}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button size="S" primary on:click={goToBuilder}>Edit</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
}
|
||||
|
||||
onMount(async () => {
|
||||
return
|
||||
if (!hasSynced && application) {
|
||||
try {
|
||||
await API.syncApp(application)
|
||||
|
|
|
@ -1467,6 +1467,11 @@
|
|||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@socket.io/component-emitter@~3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
|
||||
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
|
||||
|
||||
"@spectrum-css/accordion@^3.0.24":
|
||||
version "3.0.30"
|
||||
resolved "https://registry.yarnpkg.com/@spectrum-css/accordion/-/accordion-3.0.30.tgz#0893a6db28bab984bf5adaf7e1ba194e741db615"
|
||||
|
@ -2581,7 +2586,7 @@ dayjs@^1.10.4, dayjs@^1.11.2:
|
|||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
|
||||
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
|
||||
|
||||
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
|
@ -2774,6 +2779,22 @@ end-of-stream@^1.1.0:
|
|||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
engine.io-client@~6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.4.0.tgz#88cd3082609ca86d7d3c12f0e746d12db4f47c91"
|
||||
integrity sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
engine.io-parser "~5.0.3"
|
||||
ws "~8.11.0"
|
||||
xmlhttprequest-ssl "~2.0.0"
|
||||
|
||||
engine.io-parser@~5.0.3:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45"
|
||||
integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==
|
||||
|
||||
enquirer@^2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
|
||||
|
@ -5896,6 +5917,24 @@ snapdragon@^0.8.1:
|
|||
source-map-resolve "^0.5.0"
|
||||
use "^3.1.0"
|
||||
|
||||
socket.io-client@^4.6.1:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.6.1.tgz#80d97d5eb0feca448a0fb6d69a7b222d3d547eab"
|
||||
integrity sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.2"
|
||||
engine.io-client "~6.4.0"
|
||||
socket.io-parser "~4.2.1"
|
||||
|
||||
socket.io-parser@~4.2.1:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206"
|
||||
integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
|
@ -6713,6 +6752,11 @@ ws@^7.4.6:
|
|||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
|
||||
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
||||
|
||||
ws@~8.11.0:
|
||||
version "8.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
|
||||
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
|
@ -6723,6 +6767,11 @@ xmlchars@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xmlhttprequest-ssl@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
|
||||
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"dependencies": {
|
||||
"@budibase/bbui": "2.3.18-alpha.15",
|
||||
"lodash": "^4.17.21",
|
||||
"socket.io-client": "^4.6.1",
|
||||
"svelte": "^3.46.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { setContext, createEventDispatcher } from "svelte"
|
||||
import { setContext, createEventDispatcher, onMount } from "svelte"
|
||||
import { writable } from "svelte/store"
|
||||
import { createReorderStores } from "./stores/reorder"
|
||||
import { createViewportStores } from "./stores/viewport"
|
||||
|
@ -15,6 +15,7 @@
|
|||
import { createAPIClient } from "../../api"
|
||||
import ScrollOverlay from "./ScrollOverlay.svelte"
|
||||
import StickyColumn from "./StickyColumn.svelte"
|
||||
import { createWebsocket } from "./websocket"
|
||||
|
||||
export let API
|
||||
export let tableId
|
||||
|
@ -62,6 +63,11 @@
|
|||
|
||||
// Set context for children to consume
|
||||
setContext("spreadsheet", context)
|
||||
|
||||
// Initialise websocket for multi-user
|
||||
onMount(() => {
|
||||
return createWebsocket(context)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="sheet" style="--cell-height:{cellHeight}px;" id="sheet-{rand}">
|
||||
|
|
|
@ -72,31 +72,6 @@ export const createRowsStore = context => {
|
|||
})
|
||||
})
|
||||
|
||||
// Local handler to process new rows inside the fetch, and append any new
|
||||
// rows to state that we haven't encountered before
|
||||
const handleNewRows = newRows => {
|
||||
let rowsToAppend = []
|
||||
let newRow
|
||||
for (let i = 0; i < newRows.length; i++) {
|
||||
newRow = newRows[i]
|
||||
if (!rowCacheMap[newRow._id]) {
|
||||
rowCacheMap[newRow._id] = true
|
||||
rowsToAppend.push(newRow)
|
||||
}
|
||||
}
|
||||
if (rowsToAppend.length) {
|
||||
rows.update($rows => {
|
||||
return [
|
||||
...$rows,
|
||||
...rowsToAppend.map((row, idx) => ({
|
||||
...row,
|
||||
__idx: $rows.length + idx,
|
||||
})),
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a new empty row
|
||||
const addRow = async () => {
|
||||
try {
|
||||
|
@ -127,6 +102,43 @@ export const createRowsStore = context => {
|
|||
}
|
||||
}
|
||||
|
||||
// Refreshes a specific row, handling updates, addition or deletion
|
||||
const refreshRow = async id => {
|
||||
// Get index of row to check if it exists
|
||||
const $rows = get(rows)
|
||||
const index = $rows.findIndex(row => row._id === id)
|
||||
|
||||
// Fetch row from the server again
|
||||
const res = await API.searchTable({
|
||||
tableId: get(tableId),
|
||||
limit: 1,
|
||||
query: {
|
||||
equal: {
|
||||
_id: id,
|
||||
},
|
||||
},
|
||||
paginate: false,
|
||||
})
|
||||
let newRow = res?.rows?.[0]
|
||||
|
||||
// Process as either an update, addition or deletion
|
||||
if (newRow) {
|
||||
if (index !== -1) {
|
||||
// An existing row was updated
|
||||
rows.update(state => {
|
||||
state[index] = { ...newRow, __idx: index }
|
||||
return state
|
||||
})
|
||||
} else {
|
||||
// A new row was created
|
||||
handleNewRows([newRow])
|
||||
}
|
||||
} else if (index !== -1) {
|
||||
// A row was removed
|
||||
handleRemoveRows([$rows[index]])
|
||||
}
|
||||
}
|
||||
|
||||
// Updates a value of a row
|
||||
const updateRow = async (rowId, column, value) => {
|
||||
const $rows = get(rows)
|
||||
|
@ -151,35 +163,11 @@ export const createRowsStore = context => {
|
|||
notifications.error(`Error saving row: ${error?.message}`)
|
||||
}
|
||||
|
||||
// Fetch row from the server again
|
||||
const res = await API.searchTable({
|
||||
tableId: get(tableId),
|
||||
limit: 1,
|
||||
query: {
|
||||
equal: {
|
||||
_id: row._id,
|
||||
},
|
||||
},
|
||||
paginate: false,
|
||||
})
|
||||
if (res?.rows?.[0]) {
|
||||
newRow = res.rows[0]
|
||||
}
|
||||
|
||||
// Update state again with this row
|
||||
newRow = { ...newRow, __idx: row.__idx }
|
||||
rows.update(state => {
|
||||
state[index] = newRow
|
||||
return state
|
||||
})
|
||||
|
||||
return newRow
|
||||
return await refreshRow(row._id)
|
||||
}
|
||||
|
||||
// Deletes an array of rows
|
||||
const deleteRows = async rowsToDelete => {
|
||||
const deletedIds = rowsToDelete.map(row => row._id)
|
||||
|
||||
// Actually delete rows
|
||||
rowsToDelete.forEach(row => {
|
||||
delete row.__idx
|
||||
|
@ -190,6 +178,38 @@ export const createRowsStore = context => {
|
|||
})
|
||||
|
||||
// Update state
|
||||
handleRemoveRows(rowsToDelete)
|
||||
}
|
||||
|
||||
// Local handler to process new rows inside the fetch, and append any new
|
||||
// rows to state that we haven't encountered before
|
||||
const handleNewRows = newRows => {
|
||||
let rowsToAppend = []
|
||||
let newRow
|
||||
for (let i = 0; i < newRows.length; i++) {
|
||||
newRow = newRows[i]
|
||||
if (!rowCacheMap[newRow._id]) {
|
||||
rowCacheMap[newRow._id] = true
|
||||
rowsToAppend.push(newRow)
|
||||
}
|
||||
}
|
||||
if (rowsToAppend.length) {
|
||||
rows.update($rows => {
|
||||
return [
|
||||
...$rows,
|
||||
...rowsToAppend.map((row, idx) => ({
|
||||
...row,
|
||||
__idx: $rows.length + idx,
|
||||
})),
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Local handler to remove rows from state
|
||||
const handleRemoveRows = rowsToRemove => {
|
||||
const deletedIds = rowsToRemove.map(row => row._id)
|
||||
|
||||
// We deliberately do not remove IDs from the cache map as the data may
|
||||
// still exist inside the fetch, but we don't want to add it again
|
||||
rows.update(state => {
|
||||
|
@ -217,6 +237,7 @@ export const createRowsStore = context => {
|
|||
updateRow,
|
||||
deleteRows,
|
||||
loadNextPage,
|
||||
refreshRow,
|
||||
},
|
||||
},
|
||||
schema,
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { derived, get } from "svelte/store"
|
||||
import { io } from "socket.io-client"
|
||||
|
||||
export const createWebsocket = context => {
|
||||
const { rows, config } = context
|
||||
const tableId = derived(config, $config => $config.tableId)
|
||||
|
||||
// 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}`, {
|
||||
path: "/socket/dataspace",
|
||||
// Cap reconnection attempts to 3 (total of 15 seconds before giving up)
|
||||
reconnectionAttempts: 3,
|
||||
// Delay reconnection attempt by 5 seconds
|
||||
reconnectionDelay: 5000,
|
||||
reconnectionDelayMax: 5000,
|
||||
// Timeout after 4 seconds so we never stack requests
|
||||
timeout: 4000,
|
||||
})
|
||||
|
||||
const connectToDataspace = tableId => {
|
||||
if (!socket.connected) {
|
||||
return
|
||||
}
|
||||
console.log("Idenifying dataspace", tableId)
|
||||
|
||||
// Identify which dataspace we are editing
|
||||
socket.emit("identify", tableId, response => {
|
||||
// handle initial connection info
|
||||
console.log("response", response)
|
||||
})
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
socket.on("connect", () => {
|
||||
connectToDataspace(get(tableId))
|
||||
})
|
||||
|
||||
socket.on("row-update", data => {
|
||||
if (data.id) {
|
||||
rows.actions.refreshRow(data.id)
|
||||
}
|
||||
console.log(data)
|
||||
})
|
||||
|
||||
socket.on("connect_error", err => {
|
||||
console.log("Failed to connect to websocket:", err.message)
|
||||
})
|
||||
|
||||
// Change websocket connection when dataspace changes
|
||||
tableId.subscribe(connectToDataspace)
|
||||
|
||||
return () => socket?.disconnect()
|
||||
}
|
|
@ -2,12 +2,73 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@socket.io/component-emitter@~3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
|
||||
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
|
||||
|
||||
debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
engine.io-client@~6.4.0:
|
||||
version "6.4.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.4.0.tgz#88cd3082609ca86d7d3c12f0e746d12db4f47c91"
|
||||
integrity sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
engine.io-parser "~5.0.3"
|
||||
ws "~8.11.0"
|
||||
xmlhttprequest-ssl "~2.0.0"
|
||||
|
||||
engine.io-parser@~5.0.3:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45"
|
||||
integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==
|
||||
|
||||
lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
socket.io-client@^4.6.1:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.6.1.tgz#80d97d5eb0feca448a0fb6d69a7b222d3d547eab"
|
||||
integrity sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.2"
|
||||
engine.io-client "~6.4.0"
|
||||
socket.io-parser "~4.2.1"
|
||||
|
||||
socket.io-parser@~4.2.1:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206"
|
||||
integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
svelte@^3.46.2:
|
||||
version "3.49.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.49.0.tgz#5baee3c672306de1070c3b7888fc2204e36a4029"
|
||||
integrity sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA==
|
||||
|
||||
ws@~8.11.0:
|
||||
version "8.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
|
||||
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
|
||||
|
||||
xmlhttprequest-ssl@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
|
||||
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
"bull": "4.10.1",
|
||||
"chmodr": "1.2.0",
|
||||
"chokidar": "3.5.3",
|
||||
"cookies": "0.8.0",
|
||||
"csvtojson": "2.0.10",
|
||||
"curlconverter": "3.21.0",
|
||||
"dd-trace": "3.13.2",
|
||||
|
@ -107,7 +108,7 @@
|
|||
"redis": "4",
|
||||
"server-destroy": "1.0.1",
|
||||
"snowflake-promise": "^4.5.0",
|
||||
"socket.io": "^4.5.1",
|
||||
"socket.io": "4.6.1",
|
||||
"svelte": "3.49.0",
|
||||
"swagger-parser": "10.0.3",
|
||||
"tar": "6.1.11",
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from "@budibase/backend-core"
|
||||
import { PluginType, FileType, PluginSource, Plugin } from "@budibase/types"
|
||||
import env from "../../../environment"
|
||||
import { ClientAppSocket } from "../../../websocket"
|
||||
import { clientAppSocket } from "../../../websockets"
|
||||
import { sdk as pro } from "@budibase/pro"
|
||||
|
||||
export async function getPlugins(type?: PluginType) {
|
||||
|
@ -91,7 +91,7 @@ export async function create(ctx: any) {
|
|||
|
||||
const doc = await pro.plugins.storePlugin(metadata, directory, source)
|
||||
|
||||
ClientAppSocket.emit("plugins-update", { name, hash: doc.hash })
|
||||
clientAppSocket.emit("plugins-update", { name, hash: doc.hash })
|
||||
ctx.body = {
|
||||
message: "Plugin uploaded successfully",
|
||||
plugins: [doc],
|
||||
|
@ -133,6 +133,6 @@ export async function processUploadedPlugin(
|
|||
}
|
||||
|
||||
const doc = await pro.plugins.storePlugin(metadata, directory, source)
|
||||
ClientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash })
|
||||
clientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash })
|
||||
return doc
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { quotas } from "@budibase/pro"
|
|||
import * as internal from "./internal"
|
||||
import * as external from "./external"
|
||||
import { isExternalTable } from "../../../integrations/utils"
|
||||
import { dataspaceSocket } from "../../../websockets"
|
||||
|
||||
function pickApi(tableId: any) {
|
||||
if (isExternalTable(tableId)) {
|
||||
|
@ -45,6 +46,9 @@ export async function patch(ctx: any): Promise<any> {
|
|||
ctx.eventEmitter.emitRow(`row:update`, appId, row, table)
|
||||
ctx.message = `${table.name} updated successfully.`
|
||||
ctx.body = row
|
||||
|
||||
// Notify websocket change
|
||||
dataspaceSocket.emit("row-update", { id: row._id })
|
||||
} catch (err) {
|
||||
ctx.throw(400, err)
|
||||
}
|
||||
|
@ -67,6 +71,9 @@ export const save = async (ctx: any) => {
|
|||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
|
||||
ctx.message = `${table.name} saved successfully`
|
||||
ctx.body = row
|
||||
|
||||
// Notify websocket change
|
||||
dataspaceSocket.emit("row-update", { id: row._id })
|
||||
}
|
||||
export async function fetchView(ctx: any) {
|
||||
const tableId = getTableId(ctx)
|
||||
|
@ -105,6 +112,8 @@ export async function destroy(ctx: any) {
|
|||
response = rows
|
||||
for (let row of rows) {
|
||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
||||
// Notify websocket change
|
||||
dataspaceSocket.emit("row-update", { id: row._id })
|
||||
}
|
||||
} else {
|
||||
let resp = await quotas.addQuery(() => pickApi(tableId).destroy(ctx), {
|
||||
|
@ -114,6 +123,8 @@ export async function destroy(ctx: any) {
|
|||
response = resp.response
|
||||
row = resp.row
|
||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
||||
// Notify websocket change
|
||||
dataspaceSocket.emit("row-update", { id: row._id })
|
||||
}
|
||||
ctx.status = 200
|
||||
// for automations include the row that was deleted
|
||||
|
|
|
@ -28,7 +28,7 @@ import * as automations from "./automations"
|
|||
import { Thread } from "./threads"
|
||||
import * as redis from "./utilities/redis"
|
||||
import { events, logging, middleware } from "@budibase/backend-core"
|
||||
import { initialise as initialiseWebsockets } from "./websocket"
|
||||
import { initialise as initialiseWebsockets } from "./websockets"
|
||||
import { startup } from "./startup"
|
||||
const Sentry = require("@sentry/node")
|
||||
const destroyable = require("server-destroy")
|
||||
|
@ -72,7 +72,7 @@ if (env.isProd()) {
|
|||
|
||||
const server = http.createServer(app.callback())
|
||||
destroyable(server)
|
||||
initialiseWebsockets(server)
|
||||
initialiseWebsockets(app, server)
|
||||
|
||||
let shuttingDown = false,
|
||||
errCode = 0
|
||||
|
|
|
@ -35,12 +35,12 @@ async function checkDevAppLocks(ctx: BBContext) {
|
|||
if (!appId || !appId.startsWith(APP_DEV_PREFIX)) {
|
||||
return
|
||||
}
|
||||
if (!(await doesUserHaveLock(appId, ctx.user))) {
|
||||
ctx.throw(400, "User does not hold app lock.")
|
||||
}
|
||||
// if (!(await doesUserHaveLock(appId, ctx.user))) {
|
||||
// ctx.throw(400, "User does not hold app lock.")
|
||||
// }
|
||||
|
||||
// they do have lock, update it
|
||||
await updateLock(appId, ctx.user)
|
||||
// await updateLock(appId, ctx.user)
|
||||
}
|
||||
|
||||
async function updateAppUpdatedAt(ctx: BBContext) {
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { Server } from "socket.io"
|
||||
import http from "http"
|
||||
|
||||
class Websocket {
|
||||
socketServer: Server
|
||||
|
||||
constructor(server: http.Server, path: string) {
|
||||
this.socketServer = new Server(server, {
|
||||
path,
|
||||
})
|
||||
}
|
||||
|
||||
// Emit an event to all sockets
|
||||
emit(event: string, payload: any) {
|
||||
this.socketServer.sockets.emit(event, payload)
|
||||
}
|
||||
}
|
||||
|
||||
// Likely to be more socket instances in future
|
||||
let ClientAppSocket: Websocket
|
||||
|
||||
export const initialise = (server: http.Server) => {
|
||||
ClientAppSocket = new Websocket(server, "/socket/client")
|
||||
}
|
||||
|
||||
export { ClientAppSocket }
|
|
@ -0,0 +1,11 @@
|
|||
import Socket from "./websocket"
|
||||
import authorized from "../middleware/authorized"
|
||||
import http from "http"
|
||||
import Koa from "koa"
|
||||
import { permissions } from "@budibase/backend-core"
|
||||
|
||||
export default class ClientAppWebsocket extends Socket {
|
||||
constructor(app: Koa, server: http.Server) {
|
||||
super(app, server, "/socket/client", [authorized(permissions.BUILDER)])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import authorized from "../middleware/authorized"
|
||||
import Socket from "./websocket"
|
||||
import { permissions } from "@budibase/backend-core"
|
||||
import http from "http"
|
||||
import Koa from "koa"
|
||||
|
||||
export default class DataspaceSocket extends Socket {
|
||||
constructor(app: Koa, server: http.Server) {
|
||||
super(app, server, "/socket/dataspace", [authorized(permissions.BUILDER)])
|
||||
|
||||
this.io.on("connection", socket => {
|
||||
const user = socket.data.user
|
||||
console.log(`Dataspace user connected: ${user?._id}`)
|
||||
|
||||
// Initial identification of conneted dataspace
|
||||
socket.on("identify", async (tableId, callback) => {
|
||||
socket.join(tableId)
|
||||
|
||||
const sockets = await this.io.in(tableId).fetchSockets()
|
||||
callback(sockets.map(socket => socket.data.user))
|
||||
})
|
||||
|
||||
// Disconnection cleanup
|
||||
socket.on("disconnect", reason => {
|
||||
console.log(`Disconnecting ${user.email} because of ${reason}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import http from "http"
|
||||
import Koa from "koa"
|
||||
import DataspaceSocket from "./dataspace"
|
||||
import ClientAppSocket from "./client"
|
||||
|
||||
let clientAppSocket: ClientAppSocket
|
||||
let dataspaceSocket: DataspaceSocket
|
||||
|
||||
export const initialise = (app: Koa, server: http.Server) => {
|
||||
clientAppSocket = new ClientAppSocket(app, server)
|
||||
dataspaceSocket = new DataspaceSocket(app, server)
|
||||
}
|
||||
|
||||
export { clientAppSocket, dataspaceSocket }
|
|
@ -0,0 +1,73 @@
|
|||
import { Server } from "socket.io"
|
||||
import http from "http"
|
||||
import Koa from "koa"
|
||||
import Cookies from "cookies"
|
||||
import { userAgent } from "koa-useragent"
|
||||
import { auth } from "@budibase/backend-core"
|
||||
|
||||
export default class Socket {
|
||||
io: Server
|
||||
|
||||
constructor(
|
||||
app: Koa,
|
||||
server: http.Server,
|
||||
path: string,
|
||||
additionalMiddlewares?: any[]
|
||||
) {
|
||||
this.io = new Server(server, {
|
||||
path,
|
||||
})
|
||||
|
||||
// Attach default middlewares
|
||||
const authenticate = auth.buildAuthMiddleware([], {
|
||||
publicAllowed: true,
|
||||
})
|
||||
const middlewares = [
|
||||
userAgent,
|
||||
authenticate,
|
||||
...(additionalMiddlewares || []),
|
||||
]
|
||||
|
||||
// Apply middlewares
|
||||
this.io.use(async (socket, next) => {
|
||||
// Build fake koa context
|
||||
const res = new http.ServerResponse(socket.request)
|
||||
const ctx: any = {
|
||||
...app.createContext(socket.request, res),
|
||||
|
||||
// Additional overrides needed to make our middlewares work with this
|
||||
// fake koa context
|
||||
cookies: new Cookies(socket.request, res),
|
||||
get: (field: string) => socket.request.headers[field],
|
||||
throw: (code: number, message: string) => {
|
||||
throw new Error(message)
|
||||
},
|
||||
|
||||
// Needed for koa-useragent middleware
|
||||
header: socket.request.headers,
|
||||
}
|
||||
|
||||
// Run all koa middlewares
|
||||
try {
|
||||
for (let [idx, middleware] of middlewares.entries()) {
|
||||
await middleware(ctx, () => {
|
||||
if (idx === middlewares.length - 1) {
|
||||
// Middlewares are finished.
|
||||
// Extract some data from our enriched koa context to persist
|
||||
// as metadata for the socket
|
||||
socket.data.user = ctx.user
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Emit an event to all sockets
|
||||
emit(event: string, payload: any) {
|
||||
this.io.sockets.emit(event, payload)
|
||||
}
|
||||
}
|
|
@ -5647,7 +5647,7 @@ cookiejar@^2.1.3:
|
|||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
|
||||
integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
|
||||
|
||||
cookies@~0.8.0:
|
||||
cookies@0.8.0, cookies@~0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
|
||||
integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==
|
||||
|
@ -6462,10 +6462,10 @@ engine.io-parser@~5.0.3:
|
|||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.4.tgz#0b13f704fa9271b3ec4f33112410d8f3f41d0fc0"
|
||||
integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
|
||||
|
||||
engine.io@~6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0"
|
||||
integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==
|
||||
engine.io@~6.4.1:
|
||||
version "6.4.1"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.4.1.tgz#8056b4526a88e779f9c280d820422d4e3eeaaae5"
|
||||
integrity sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==
|
||||
dependencies:
|
||||
"@types/cookie" "^0.4.1"
|
||||
"@types/cors" "^2.8.12"
|
||||
|
@ -6476,7 +6476,7 @@ engine.io@~6.2.0:
|
|||
cors "~2.8.5"
|
||||
debug "~4.3.1"
|
||||
engine.io-parser "~5.0.3"
|
||||
ws "~8.2.3"
|
||||
ws "~8.11.0"
|
||||
|
||||
enhanced-resolve@^5.9.3:
|
||||
version "5.9.3"
|
||||
|
@ -13943,30 +13943,32 @@ snowflake-sdk@^1.6.0:
|
|||
uuid "^3.3.2"
|
||||
winston "^3.1.0"
|
||||
|
||||
socket.io-adapter@~2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6"
|
||||
integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==
|
||||
socket.io-adapter@~2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz#5de9477c9182fdc171cd8c8364b9a8894ec75d12"
|
||||
integrity sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==
|
||||
dependencies:
|
||||
ws "~8.11.0"
|
||||
|
||||
socket.io-parser@~4.2.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5"
|
||||
integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==
|
||||
socket.io-parser@~4.2.1:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206"
|
||||
integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
|
||||
dependencies:
|
||||
"@socket.io/component-emitter" "~3.1.0"
|
||||
debug "~4.3.1"
|
||||
|
||||
socket.io@^4.5.1:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.2.tgz#1eb25fd380ab3d63470aa8279f8e48d922d443ac"
|
||||
integrity sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ==
|
||||
socket.io@4.6.1:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.6.1.tgz#62ec117e5fce0692fa50498da9347cfb52c3bc70"
|
||||
integrity sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==
|
||||
dependencies:
|
||||
accepts "~1.3.4"
|
||||
base64id "~2.0.0"
|
||||
debug "~4.3.2"
|
||||
engine.io "~6.2.0"
|
||||
socket.io-adapter "~2.4.0"
|
||||
socket.io-parser "~4.2.0"
|
||||
engine.io "~6.4.1"
|
||||
socket.io-adapter "~2.5.2"
|
||||
socket.io-parser "~4.2.1"
|
||||
|
||||
socks@^2.7.0:
|
||||
version "2.7.0"
|
||||
|
@ -15936,10 +15938,10 @@ ws@^5.2.0:
|
|||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@~8.2.3:
|
||||
version "8.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
|
||||
integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
|
||||
ws@~8.11.0:
|
||||
version "8.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
|
||||
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
|
||||
|
||||
x3-linkedlist@1.2.0:
|
||||
version "1.2.0"
|
||||
|
|
Loading…
Reference in New Issue