user and websocket stores converted to typescript
This commit is contained in:
parent
5ecc145228
commit
9c7beeeeaf
|
@ -1,62 +0,0 @@
|
|||
import { writable, get, derived } from "svelte/store"
|
||||
|
||||
export const createUserStore = () => {
|
||||
const store = writable([])
|
||||
|
||||
const init = users => {
|
||||
store.set(users)
|
||||
}
|
||||
|
||||
const updateUser = user => {
|
||||
const $users = get(store)
|
||||
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
||||
store.set([...$users, user])
|
||||
} else {
|
||||
store.update(state => {
|
||||
const index = state.findIndex(x => x.sessionId === user.sessionId)
|
||||
state[index] = user
|
||||
return state.slice()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const removeUser = sessionId => {
|
||||
store.update(state => {
|
||||
return state.filter(x => x.sessionId !== sessionId)
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
store.set([])
|
||||
}
|
||||
|
||||
return {
|
||||
...store,
|
||||
actions: {
|
||||
init,
|
||||
updateUser,
|
||||
removeUser,
|
||||
reset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const userStore = createUserStore()
|
||||
|
||||
export const userSelectedResourceMap = derived(userStore, $userStore => {
|
||||
let map = {}
|
||||
$userStore.forEach(user => {
|
||||
const resource = user.builderMetadata?.selectedResourceId
|
||||
if (resource) {
|
||||
if (!map[resource]) {
|
||||
map[resource] = []
|
||||
}
|
||||
map[resource].push(user)
|
||||
}
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
export const isOnlyUser = derived(userStore, $userStore => {
|
||||
return $userStore.length < 2
|
||||
})
|
|
@ -0,0 +1,72 @@
|
|||
import { get, derived } from "svelte/store"
|
||||
import { BudiStore } from "../BudiStore"
|
||||
import { UIUser } from "@budibase/types"
|
||||
|
||||
export class UserStore extends BudiStore<UIUser[]> {
|
||||
actions: {
|
||||
init: (users: UIUser[]) => void
|
||||
updateUser: (user: UIUser) => void
|
||||
removeUser: (sessionId: string) => void
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super([])
|
||||
this.actions = {
|
||||
init: this.init.bind(this),
|
||||
updateUser: this.updateUser.bind(this),
|
||||
removeUser: this.removeUser.bind(this),
|
||||
reset: this.reset.bind(this),
|
||||
}
|
||||
}
|
||||
|
||||
init(users: UIUser[]) {
|
||||
this.store.set(users)
|
||||
}
|
||||
|
||||
updateUser(user: UIUser) {
|
||||
const $users = get(this.store)
|
||||
if (!$users.some(x => x.sessionId === user.sessionId)) {
|
||||
this.store.set([...$users, user])
|
||||
} else {
|
||||
this.update(state => {
|
||||
const index = state.findIndex(x => x.sessionId === user.sessionId)
|
||||
state[index] = user
|
||||
return state.slice()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
removeUser(sessionId: string) {
|
||||
this.update(state => {
|
||||
return state.filter(x => x.sessionId !== sessionId)
|
||||
})
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.store.set([])
|
||||
}
|
||||
}
|
||||
|
||||
export const userStore = new UserStore()
|
||||
|
||||
export const userSelectedResourceMap = derived(
|
||||
userStore,
|
||||
($userStore): Record<string, UIUser[]> => {
|
||||
let map: Record<string, UIUser[]> = {}
|
||||
$userStore.forEach(user => {
|
||||
const resource = user.builderMetadata?.selectedResourceId
|
||||
if (resource) {
|
||||
if (!map[resource]) {
|
||||
map[resource] = []
|
||||
}
|
||||
map[resource].push(user)
|
||||
}
|
||||
})
|
||||
return map
|
||||
}
|
||||
)
|
||||
|
||||
export const isOnlyUser = derived(userStore, $userStore => {
|
||||
return $userStore.length < 2
|
||||
})
|
|
@ -1,95 +0,0 @@
|
|||
import { createWebsocket } from "@budibase/frontend-core"
|
||||
import {
|
||||
automationStore,
|
||||
userStore,
|
||||
appStore,
|
||||
themeStore,
|
||||
navigationStore,
|
||||
deploymentStore,
|
||||
snippets,
|
||||
datasources,
|
||||
tables,
|
||||
roles,
|
||||
} from "@/stores/builder"
|
||||
import { get } from "svelte/store"
|
||||
import { auth, appsStore } from "@/stores/portal"
|
||||
import { screenStore } from "./screens"
|
||||
import { SocketEvent, BuilderSocketEvent, helpers } from "@budibase/shared-core"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export const createBuilderWebsocket = appId => {
|
||||
const socket = createWebsocket("/socket/builder")
|
||||
|
||||
// Built-in events
|
||||
socket.on("connect", () => {
|
||||
socket.emit(BuilderSocketEvent.SelectApp, { appId }, ({ users }) => {
|
||||
userStore.actions.init(users)
|
||||
})
|
||||
})
|
||||
socket.on("connect_error", err => {
|
||||
console.error("Failed to connect to builder websocket:", err.message)
|
||||
})
|
||||
socket.on("disconnect", () => {
|
||||
userStore.actions.reset()
|
||||
})
|
||||
|
||||
// User events
|
||||
socket.onOther(SocketEvent.UserUpdate, ({ user }) => {
|
||||
userStore.actions.updateUser(user)
|
||||
})
|
||||
socket.onOther(SocketEvent.UserDisconnect, ({ sessionId }) => {
|
||||
userStore.actions.removeUser(sessionId)
|
||||
})
|
||||
socket.onOther(BuilderSocketEvent.LockTransfer, ({ userId }) => {
|
||||
if (userId === get(auth)?.user?._id) {
|
||||
appStore.update(state => ({
|
||||
...state,
|
||||
hasLock: true,
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
// Data section events
|
||||
socket.onOther(BuilderSocketEvent.TableChange, ({ id, table }) => {
|
||||
tables.replaceTable(id, table)
|
||||
})
|
||||
socket.onOther(BuilderSocketEvent.DatasourceChange, ({ id, datasource }) => {
|
||||
datasources.replaceDatasource(id, datasource)
|
||||
})
|
||||
|
||||
// Role events
|
||||
socket.onOther(BuilderSocketEvent.RoleChange, ({ id, role }) => {
|
||||
roles.replace(id, role)
|
||||
})
|
||||
|
||||
// Design section events
|
||||
socket.onOther(BuilderSocketEvent.ScreenChange, ({ id, screen }) => {
|
||||
screenStore.replace(id, screen)
|
||||
})
|
||||
|
||||
// App events
|
||||
socket.onOther(BuilderSocketEvent.AppMetadataChange, ({ metadata }) => {
|
||||
appStore.syncMetadata(metadata)
|
||||
themeStore.syncMetadata(metadata)
|
||||
navigationStore.syncMetadata(metadata)
|
||||
snippets.syncMetadata(metadata)
|
||||
})
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AppPublishChange,
|
||||
async ({ user, published }) => {
|
||||
await appsStore.load()
|
||||
if (published) {
|
||||
await deploymentStore.load()
|
||||
}
|
||||
const verb = published ? "published" : "unpublished"
|
||||
notifications.success(`${helpers.getUserLabel(user)} ${verb} this app`)
|
||||
}
|
||||
)
|
||||
|
||||
// Automation events
|
||||
socket.onOther(BuilderSocketEvent.AutomationChange, ({ id, automation }) => {
|
||||
automationStore.actions.replace(id, automation)
|
||||
})
|
||||
|
||||
return socket
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
import { createWebsocket } from "@budibase/frontend-core"
|
||||
import {
|
||||
automationStore,
|
||||
userStore,
|
||||
appStore,
|
||||
themeStore,
|
||||
navigationStore,
|
||||
deploymentStore,
|
||||
snippets,
|
||||
datasources,
|
||||
tables,
|
||||
roles,
|
||||
} from "@/stores/builder"
|
||||
import { get } from "svelte/store"
|
||||
import { auth, appsStore } from "@/stores/portal"
|
||||
import { screenStore } from "./screens"
|
||||
import { SocketEvent, BuilderSocketEvent, helpers } from "@budibase/shared-core"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Automation, Datasource, Role, Table, UIUser } from "@budibase/types"
|
||||
|
||||
export const createBuilderWebsocket = (appId: string) => {
|
||||
const socket = createWebsocket("/socket/builder")
|
||||
|
||||
// Built-in events
|
||||
socket.on("connect", () => {
|
||||
socket.emit(
|
||||
BuilderSocketEvent.SelectApp,
|
||||
{ appId },
|
||||
({ users }: { users: UIUser[] }) => {
|
||||
userStore.actions.init(users)
|
||||
}
|
||||
)
|
||||
})
|
||||
socket.on("connect_error", err => {
|
||||
console.error("Failed to connect to builder websocket:", err.message)
|
||||
})
|
||||
socket.on("disconnect", () => {
|
||||
userStore.actions.reset()
|
||||
})
|
||||
|
||||
// User events
|
||||
socket.onOther(SocketEvent.UserUpdate, ({ user }: { user: UIUser }) => {
|
||||
userStore.actions.updateUser(user)
|
||||
})
|
||||
socket.onOther(
|
||||
SocketEvent.UserDisconnect,
|
||||
({ sessionId }: { sessionId: string }) => {
|
||||
userStore.actions.removeUser(sessionId)
|
||||
}
|
||||
)
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.LockTransfer,
|
||||
({ userId }: { userId: string }) => {
|
||||
if (userId === get(auth)?.user?._id) {
|
||||
appStore.update(state => ({
|
||||
...state,
|
||||
hasLock: true,
|
||||
}))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Data section events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.TableChange,
|
||||
({ id, table }: { id: string; table: Table }) => {
|
||||
tables.replaceTable(id, table)
|
||||
}
|
||||
)
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.DatasourceChange,
|
||||
({ id, datasource }: { id: string; datasource: Datasource }) => {
|
||||
datasources.replaceDatasource(id, datasource)
|
||||
}
|
||||
)
|
||||
|
||||
// Role events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.RoleChange,
|
||||
({ id, role }: { id: string; role: Role }) => {
|
||||
roles.replace(id, role)
|
||||
}
|
||||
)
|
||||
|
||||
// Design section events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.ScreenChange,
|
||||
({ id, screen }: { id: string; screen: Screen }) => {
|
||||
screenStore.replace(id, screen)
|
||||
}
|
||||
)
|
||||
|
||||
// App events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AppMetadataChange,
|
||||
({ metadata }: { metadata: any }) => {
|
||||
appStore.syncMetadata(metadata)
|
||||
themeStore.syncMetadata(metadata)
|
||||
navigationStore.syncMetadata(metadata)
|
||||
snippets.syncMetadata(metadata)
|
||||
}
|
||||
)
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AppPublishChange,
|
||||
async ({ user, published }: { user: UIUser; published: boolean }) => {
|
||||
await appsStore.load()
|
||||
if (published) {
|
||||
await deploymentStore.load()
|
||||
}
|
||||
const verb = published ? "published" : "unpublished"
|
||||
notifications.success(`${helpers.getUserLabel(user)} ${verb} this app`)
|
||||
}
|
||||
)
|
||||
|
||||
// Automation events
|
||||
socket.onOther(
|
||||
BuilderSocketEvent.AutomationChange,
|
||||
({ id, automation }: { id: string; automation: Automation }) => {
|
||||
automationStore.actions.replace(id, automation)
|
||||
}
|
||||
)
|
||||
|
||||
return socket
|
||||
}
|
|
@ -1,12 +1,18 @@
|
|||
import { io } from "socket.io-client"
|
||||
import { io, Socket } from "socket.io-client"
|
||||
import { SocketEvent, SocketSessionTTL } from "@budibase/shared-core"
|
||||
import { APISessionID } from "../api"
|
||||
|
||||
const DefaultOptions = {
|
||||
heartbeat: true,
|
||||
}
|
||||
export interface ExtendedSocket extends Socket {
|
||||
onOther: (event: string, callback: (data: any) => void) => void
|
||||
}
|
||||
|
||||
export const createWebsocket = (path, options = DefaultOptions) => {
|
||||
export const createWebsocket = (
|
||||
path: string,
|
||||
options = DefaultOptions
|
||||
): ExtendedSocket => {
|
||||
if (!path) {
|
||||
throw "A websocket path must be provided"
|
||||
}
|
||||
|
@ -32,10 +38,10 @@ export const createWebsocket = (path, options = DefaultOptions) => {
|
|||
// Disable polling and rely on websocket only, as HTTP transport
|
||||
// will only work with sticky sessions which we don't have
|
||||
transports: ["websocket"],
|
||||
})
|
||||
}) as ExtendedSocket
|
||||
|
||||
// Set up a heartbeat that's half of the session TTL
|
||||
let interval
|
||||
let interval: NodeJS.Timeout | undefined
|
||||
if (heartbeat) {
|
||||
interval = setInterval(() => {
|
||||
socket.emit(SocketEvent.Heartbeat)
|
||||
|
@ -48,7 +54,7 @@ export const createWebsocket = (path, options = DefaultOptions) => {
|
|||
|
||||
// Helper utility to ignore events that were triggered due to API
|
||||
// changes made by us
|
||||
socket.onOther = (event, callback) => {
|
||||
socket.onOther = (event: string, callback: (data: any) => void) => {
|
||||
socket.on(event, data => {
|
||||
if (data?.apiSessionId !== APISessionID) {
|
||||
callback(data)
|
|
@ -3,4 +3,5 @@ import { User } from "@budibase/types"
|
|||
export interface UIUser extends User {
|
||||
sessionId: string
|
||||
gridMetadata?: { focusedCellId?: string }
|
||||
builderMetadata?: { selectedResourceId?: string }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue