@raclettejs/core 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/bin/cli.js +1 -1
- package/dev/README.md +4 -0
- package/dist/cli.js +135 -124
- package/dist/cli.js.map +4 -4
- package/dist/index.js +9 -8
- package/dist/index.js.map +4 -4
- package/package.json +9 -5
- package/services/backend/delay-api-debugger.sh +0 -0
- package/services/backend/package.json +1 -0
- package/services/backend/src/app.ts +4 -0
- package/services/backend/src/core/config/configService.ts +10 -1
- package/services/backend/src/core/events/eventEmitter.ts +32 -7
- package/services/backend/src/core/http/request-params.plugin.ts +2 -2
- package/services/backend/src/core/pluginSystem/pluginFastifyInstance.ts +4 -1
- package/services/backend/src/core/pluginSystem/pluginTypes.ts +3 -1
- package/services/backend/src/core/sockets/createSystemEventHandlers.ts +7 -0
- package/services/backend/src/core/sockets/index.ts +61 -13
- package/services/backend/src/core/sockets/socketManager.ts +452 -105
- package/services/backend/src/core/sockets/socketTypes.ts +13 -1
- package/services/backend/src/core/sockets/workSessionJoinPayload.ts +106 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.getAll.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.hardDelete.ts +16 -19
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +86 -3
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.getAll.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.hardDelete.ts +16 -19
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.bulk.ts +60 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.service.ts +96 -12
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.getAll.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.hardDelete.ts +12 -17
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.bulk.ts +62 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.service.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.get.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.getAll.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.hardDelete.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.post.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.get.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.getAll.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.hardDelete.ts +10 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.patch.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.bulk.ts +60 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.remove.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +73 -3
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.get.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.getAll.ts +1 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.hardDelete.ts +10 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patch.ts +4 -6
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchLastProject.ts +4 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.post.ts +3 -1
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.remove.ts +3 -4
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.restore.ts +12 -15
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/events/index.ts +45 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/crud.ts +12 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/payload.ts +21 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/index.ts +55 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.ts +19 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.get.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.getAll.ts +39 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.hardDelete.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.patch.ts +58 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.post.ts +53 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.remove.ts +52 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.model.ts +65 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema.ts +125 -0
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.service.ts +351 -0
- package/services/backend/src/corePlugins/raclette__core/backend/index.ts +2 -0
- package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +159 -24
- package/services/backend/src/modules/authentication/index.ts +255 -4
- package/services/backend/src/modules/hooks/rate-limiter.ts +21 -0
- package/services/backend/src/types/custom-fastify.d.ts +17 -0
- package/services/backend/src/utils/request.utils.ts +4 -1
- package/services/backend/types/index.d.ts +15 -1
- package/services/backend/yarn.lock +13 -4
- package/services/frontend/index.html +1 -1
- package/services/frontend/provision/entrypoint.sh +0 -0
- package/services/frontend/src/core/lib/configService.ts +1 -1
- package/services/frontend/src/core/lib/data/dataApi.ts +10 -1
- package/services/frontend/src/core/lib/dataHelper.ts +1 -1
- package/services/frontend/src/core/lib/eggs/snow.ts +66 -48
- package/services/frontend/src/core/lib/helpers/index.ts +1 -0
- package/services/frontend/src/core/lib/helpers/removeEmpty.ts +69 -0
- package/services/frontend/src/core/lib/httpClient.ts +19 -0
- package/services/frontend/src/core/main.ts +20 -10
- package/services/frontend/src/core/setup/socket.ts +4 -3
- package/services/frontend/src/core/setup/socketClient.ts +31 -3
- package/services/frontend/src/core/setup/socketEvents.ts +92 -8
- package/services/frontend/src/core/store/index.ts +5 -1
- package/services/frontend/src/core/store/reducers/compositions/index.ts +2 -2
- package/services/frontend/src/core/store/reducers/interactionLinks/index.ts +1 -1
- package/services/frontend/src/core/store/reducers/widgets/reducers.ts +1 -2
- package/services/frontend/src/core/store/types/index.ts +0 -1
- package/services/frontend/src/core/types/PluginRegistrar.ts +8 -6
- package/services/frontend/src/core/types/Store.ts +1 -0
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +68 -8
- package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +17 -5
- package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/usePluginApi.ts +0 -3
- package/services/frontend/src/orchestrator/composables/useRouteState.ts +23 -4
- package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +166 -0
- package/services/frontend/src/orchestrator/helpers/index.ts +1 -0
- package/services/frontend/src/orchestrator/i18n/de-DE.json +15 -1
- package/services/frontend/src/orchestrator/i18n/en-EU.json +15 -1
- package/services/frontend/src/orchestrator/i18n/sk.json +15 -1
- package/services/frontend/src/orchestrator/index.ts +34 -4
- package/services/frontend/src/orchestrator/router/routeParserHelper.ts +106 -1
- package/services/frontend/src/orchestrator/types/Widgets.ts +2 -0
- package/services/frontend/types.ts +1 -0
- package/src/README.md +404 -0
- package/src/types.ts +19 -5
- package/templates/README.md +0 -0
- package/types/index.ts +18 -4
- package/services/backend/.gitignore +0 -17
- package/services/frontend/.gitignore +0 -35
- package/yarn.lock +0 -2937
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
type EmptyCheckConfig = {
|
|
2
|
+
removeNull?: boolean
|
|
3
|
+
removeUndefined?: boolean
|
|
4
|
+
removeFalse?: boolean
|
|
5
|
+
removeEmptyString?: boolean
|
|
6
|
+
removeZero?: boolean
|
|
7
|
+
removeEmptyArray?: boolean
|
|
8
|
+
removeEmptyObject?: boolean
|
|
9
|
+
customCheck?: (value: unknown) => boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DEFAULT_CONFIG: Required<Omit<EmptyCheckConfig, "customCheck">> = {
|
|
13
|
+
removeNull: true,
|
|
14
|
+
removeUndefined: true,
|
|
15
|
+
removeFalse: false,
|
|
16
|
+
removeEmptyString: false,
|
|
17
|
+
removeZero: false,
|
|
18
|
+
removeEmptyArray: false,
|
|
19
|
+
removeEmptyObject: false,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isEmpty = (value: unknown, config: EmptyCheckConfig): boolean => {
|
|
23
|
+
const checks: Array<() => boolean> = [
|
|
24
|
+
() => config.removeNull === true && value === null,
|
|
25
|
+
() => config.removeUndefined === true && value === undefined,
|
|
26
|
+
() => config.removeFalse === true && value === false,
|
|
27
|
+
() => config.removeEmptyString === true && value === "",
|
|
28
|
+
() => config.removeZero === true && value === 0,
|
|
29
|
+
() =>
|
|
30
|
+
config.removeEmptyArray === true &&
|
|
31
|
+
Array.isArray(value) &&
|
|
32
|
+
value.length === 0,
|
|
33
|
+
() =>
|
|
34
|
+
config.removeEmptyObject === true &&
|
|
35
|
+
typeof value === "object" &&
|
|
36
|
+
value !== null &&
|
|
37
|
+
!Array.isArray(value) &&
|
|
38
|
+
Object.keys(value).length === 0,
|
|
39
|
+
() => config.customCheck?.(value) ?? false,
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
return checks.some((check) => check())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const recurse = (value: unknown, config: EmptyCheckConfig): unknown => {
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
return value
|
|
48
|
+
.map((item) => recurse(item, config))
|
|
49
|
+
.filter((item) => !isEmpty(item, config))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof value === "object" && value !== null) {
|
|
53
|
+
return Object.fromEntries(
|
|
54
|
+
Object.entries(value)
|
|
55
|
+
.map(([key, val]) => [key, recurse(val, config)] as const)
|
|
56
|
+
.filter(([, val]) => !isEmpty(val, config)),
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const removeEmptyValues = <T extends object>(
|
|
64
|
+
obj: T,
|
|
65
|
+
config: EmptyCheckConfig = {},
|
|
66
|
+
): Partial<T> => {
|
|
67
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config }
|
|
68
|
+
return recurse(obj, mergedConfig) as Partial<T>
|
|
69
|
+
}
|
|
@@ -13,6 +13,7 @@ const axiosClient = axios.create({
|
|
|
13
13
|
"Accept-Version": "*",
|
|
14
14
|
},
|
|
15
15
|
validateStatus: (status) => status >= 200 && status < 300,
|
|
16
|
+
withCredentials: true, // Required for CORS with credentials
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
// Fetch client configuration
|
|
@@ -370,6 +371,23 @@ const unsetHttpToken = () => {
|
|
|
370
371
|
delete fetchClient.defaults.headers.common.Authorization
|
|
371
372
|
}
|
|
372
373
|
|
|
374
|
+
const WORK_SESSION_HEADER = "x-work-session-id"
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Set work session ID for unauthenticated shared sessions.
|
|
378
|
+
* When set, all API requests will include this header so backend can allow access via authenticatedOrWorksession.
|
|
379
|
+
* @param sessionId - Work session UUID or null to clear
|
|
380
|
+
*/
|
|
381
|
+
const setWorkSessionId = (sessionId: string | null) => {
|
|
382
|
+
if (sessionId) {
|
|
383
|
+
axiosClient.defaults.headers.common[WORK_SESSION_HEADER] = sessionId
|
|
384
|
+
fetchClient.defaults.headers.common[WORK_SESSION_HEADER] = sessionId
|
|
385
|
+
} else {
|
|
386
|
+
delete axiosClient.defaults.headers.common[WORK_SESSION_HEADER]
|
|
387
|
+
delete fetchClient.defaults.headers.common[WORK_SESSION_HEADER]
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
373
391
|
// Main export object with both clients
|
|
374
392
|
const httpClient: {
|
|
375
393
|
axios: typeof axiosClient
|
|
@@ -386,6 +404,7 @@ export {
|
|
|
386
404
|
apiHealthcheck,
|
|
387
405
|
setHttpToken,
|
|
388
406
|
unsetHttpToken,
|
|
407
|
+
setWorkSessionId,
|
|
389
408
|
configureHttpInterceptors,
|
|
390
409
|
DEFAULT_TIMEOUT,
|
|
391
410
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
configureHttpInterceptors,
|
|
7
7
|
apiHealthcheck,
|
|
8
8
|
setHttpToken,
|
|
9
|
+
setWorkSessionId,
|
|
9
10
|
} from "@racletteCore/lib/httpClient"
|
|
10
11
|
|
|
11
12
|
import { chadsayFE } from "@racletteCore"
|
|
@@ -66,13 +67,22 @@ const initPluginRegistration = async () => {
|
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
const createRacletteApp = async (
|
|
69
|
-
options
|
|
70
|
+
options: { requireAuth?: boolean; sessionId?: string } = {
|
|
71
|
+
requireAuth: true,
|
|
72
|
+
},
|
|
70
73
|
): Promise<CreateAppResponse> => {
|
|
71
74
|
setupHttpClient()
|
|
75
|
+
if (options.sessionId) {
|
|
76
|
+
setWorkSessionId(options.sessionId)
|
|
77
|
+
} else {
|
|
78
|
+
setWorkSessionId(null)
|
|
79
|
+
}
|
|
72
80
|
|
|
73
81
|
let user: User | null = null
|
|
74
82
|
await configService.init()
|
|
75
|
-
|
|
83
|
+
|
|
84
|
+
// If sessionId is provided, skip authentication
|
|
85
|
+
if (options.requireAuth && !options.sessionId) {
|
|
76
86
|
const { authenticated, error, user: _user, code } = await checkAuthStatus()
|
|
77
87
|
|
|
78
88
|
if (!authenticated) {
|
|
@@ -88,7 +98,7 @@ const createRacletteApp = async (
|
|
|
88
98
|
user = _user!
|
|
89
99
|
}
|
|
90
100
|
|
|
91
|
-
await setupDependencies()
|
|
101
|
+
await setupDependencies(options.sessionId)
|
|
92
102
|
|
|
93
103
|
store.dispatch({ type: "user/set", payload: user })
|
|
94
104
|
|
|
@@ -98,11 +108,11 @@ const createRacletteApp = async (
|
|
|
98
108
|
return new Promise<CreateAppResponse>(async (resolve, reject) => {
|
|
99
109
|
eventbus.on("ui/setupProject", (spaceId: string) => {
|
|
100
110
|
resolve({
|
|
101
|
-
authenticated:
|
|
111
|
+
authenticated: !!user || !!options.sessionId,
|
|
102
112
|
setupComplete: true,
|
|
103
113
|
spaceId,
|
|
104
114
|
})
|
|
105
|
-
console.log(chadsayFE("
|
|
115
|
+
console.log(chadsayFE("racletteJS is up and running!"))
|
|
106
116
|
})
|
|
107
117
|
await awaitTime(2000)
|
|
108
118
|
reject("timeouted")
|
|
@@ -182,7 +192,7 @@ const generateLoginCallback =
|
|
|
182
192
|
return {
|
|
183
193
|
success: false,
|
|
184
194
|
// TODO: Constants would be nice here
|
|
185
|
-
error:
|
|
195
|
+
error: "Invalid response: Token not found",
|
|
186
196
|
message: "Authentication failed: Invalid server response",
|
|
187
197
|
}
|
|
188
198
|
}
|
|
@@ -201,7 +211,7 @@ const generateLoginCallback =
|
|
|
201
211
|
|
|
202
212
|
return {
|
|
203
213
|
success: false,
|
|
204
|
-
error,
|
|
214
|
+
error: errorMessage,
|
|
205
215
|
message: errorMessage,
|
|
206
216
|
}
|
|
207
217
|
}
|
|
@@ -210,7 +220,7 @@ const generateLoginCallback =
|
|
|
210
220
|
const getCookie = (name: string): string | null => {
|
|
211
221
|
const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"))
|
|
212
222
|
|
|
213
|
-
return match ? match[2] : null
|
|
223
|
+
return match && match[2] ? match[2] : null
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
const checkAuthStatus = async (): Promise<{
|
|
@@ -246,7 +256,7 @@ const checkAuthStatus = async (): Promise<{
|
|
|
246
256
|
}
|
|
247
257
|
}
|
|
248
258
|
|
|
249
|
-
const setupDependencies = async () => {
|
|
259
|
+
const setupDependencies = async (sessionId?: string) => {
|
|
250
260
|
registerStoreEffects(store)
|
|
251
261
|
|
|
252
262
|
// api client - remaining config
|
|
@@ -254,7 +264,7 @@ const setupDependencies = async () => {
|
|
|
254
264
|
apiHealthcheck(store)
|
|
255
265
|
|
|
256
266
|
// socket
|
|
257
|
-
socket = createSocket(SOCKET_URL, appSessionId)
|
|
267
|
+
socket = createSocket(SOCKET_URL, appSessionId, { sessionId })
|
|
258
268
|
|
|
259
269
|
registerSocketEvents(socket, store, eventbus)
|
|
260
270
|
// listen to eventbus and send messages to the socket server
|
|
@@ -9,10 +9,10 @@ import log from "@racletteCore/lib/logger"
|
|
|
9
9
|
* createSocket returns an instance of a custom SocketClient
|
|
10
10
|
* @param {string} host - our socket endpoint
|
|
11
11
|
* @param {string} appSessionId - The id for the current application - server session
|
|
12
|
-
* @param {object} opts - socket.io options
|
|
12
|
+
* @param {object} opts - socket.io options (can include sessionId for work sessions)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const createSocket = (host, appSessionId, opts = {}) => {
|
|
15
|
+
const createSocket = (host, appSessionId, opts: { sessionId?: string } = {}) => {
|
|
16
16
|
log.socket("createSocket", { host, appSessionId, opts })
|
|
17
17
|
const token = getUserToken()
|
|
18
18
|
const defaults = {
|
|
@@ -22,7 +22,8 @@ const createSocket = (host, appSessionId, opts = {}) => {
|
|
|
22
22
|
transports: ["polling", "websocket"],
|
|
23
23
|
withCredentials: true,
|
|
24
24
|
auth: {
|
|
25
|
-
token:
|
|
25
|
+
...(token ? { token } : {}),
|
|
26
|
+
...(opts.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
26
27
|
},
|
|
27
28
|
extraHeaders: {
|
|
28
29
|
"app-session-id": appSessionId,
|
|
@@ -5,6 +5,28 @@
|
|
|
5
5
|
import log from "@racletteCore/lib/logger"
|
|
6
6
|
import { getUserToken } from "./sessionHandler"
|
|
7
7
|
import configService from "@racletteCore/lib/configService"
|
|
8
|
+
import { validate } from "uuid"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract sessionId from URL if present
|
|
12
|
+
* Supports racletteJS route format: /:workSession/id={uuid}
|
|
13
|
+
*/
|
|
14
|
+
const extractSessionIdFromUrl = (): string | null => {
|
|
15
|
+
const path = window.location.pathname + (window.location.hash || "")
|
|
16
|
+
// Match /:workSession/id={uuid} pattern
|
|
17
|
+
const workSessionMatch = path.match(/\/:workSession\/id=([^/&#]+)/)
|
|
18
|
+
|
|
19
|
+
if (workSessionMatch && workSessionMatch[1]) {
|
|
20
|
+
const sessionId = workSessionMatch[1]
|
|
21
|
+
// Validate UUID format
|
|
22
|
+
if (validate(sessionId)) {
|
|
23
|
+
return sessionId
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
8
30
|
const registerSocketClient = ($socket, $eventbus) => {
|
|
9
31
|
$eventbus.on("socket/join/room", (roomId?: string) => {
|
|
10
32
|
const { requireAuthentication } = configService.getConfig()?.global ?? {
|
|
@@ -12,16 +34,22 @@ const registerSocketClient = ($socket, $eventbus) => {
|
|
|
12
34
|
}
|
|
13
35
|
|
|
14
36
|
let token: string | null = null
|
|
37
|
+
const sessionId = extractSessionIdFromUrl()
|
|
38
|
+
|
|
39
|
+
// Check if we're in a work session context (sessionId in URL indicates work session)
|
|
40
|
+
const isWorkSession = !!sessionId
|
|
15
41
|
|
|
16
42
|
if (requireAuthentication) {
|
|
17
43
|
token = getUserToken()
|
|
18
44
|
|
|
19
|
-
|
|
20
|
-
|
|
45
|
+
// For work sessions, we don't need a token - the sessionId is used for authentication
|
|
46
|
+
// The sessionId was already sent during socket handshake in the auth object
|
|
47
|
+
if (!token && !isWorkSession) {
|
|
48
|
+
return log.socket("emitJoin", "no token and not a work session")
|
|
21
49
|
}
|
|
22
50
|
}
|
|
23
51
|
|
|
24
|
-
log.socket("emitJoin", { roomId, token })
|
|
52
|
+
log.socket("emitJoin", { roomId, token, sessionId, isWorkSession })
|
|
25
53
|
$socket.emit("join", { roomId, token })
|
|
26
54
|
})
|
|
27
55
|
|
|
@@ -12,6 +12,29 @@ import { setupSpace } from "@racletteCore/lib/projectDataHelper"
|
|
|
12
12
|
import { handleServerItem } from "@racletteCore/lib/data/dataApi"
|
|
13
13
|
import * as userNotifier from "@racletteCore/lib/userNotifier"
|
|
14
14
|
import type { ProjectConfiguration } from "../store/types"
|
|
15
|
+
import { validate } from "uuid"
|
|
16
|
+
import { setHttpToken, setWorkSessionId } from "@racletteCore/lib/httpClient"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extract sessionId from URL if present
|
|
20
|
+
* Supports racletteJS route format: /:workSession/id={uuid}
|
|
21
|
+
*/
|
|
22
|
+
const extractSessionIdFromUrl = (): string | null => {
|
|
23
|
+
const path = window.location.pathname + (window.location.hash || "")
|
|
24
|
+
// Match /:workSession/id={uuid} pattern
|
|
25
|
+
const workSessionMatch = path.match(/\/:workSession\/id=([^/&#]+)/)
|
|
26
|
+
|
|
27
|
+
if (workSessionMatch && workSessionMatch[1]) {
|
|
28
|
+
const sessionId = workSessionMatch[1]
|
|
29
|
+
// Validate UUID format
|
|
30
|
+
if (validate(sessionId)) {
|
|
31
|
+
return sessionId
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
const registerSocketEvents = ($socket, $store, $eventbus) => {
|
|
16
39
|
/**
|
|
17
40
|
* connect
|
|
@@ -171,24 +194,85 @@ const registerSocketEvents = ($socket, $store, $eventbus) => {
|
|
|
171
194
|
|
|
172
195
|
/**
|
|
173
196
|
* on joinConfirmation this means that we entered a new project with a new scope!
|
|
197
|
+
* For work sessions, the payload structure is different: { session, composition }
|
|
174
198
|
*/
|
|
175
199
|
$socket.on(
|
|
176
200
|
"joinConfirmation",
|
|
177
201
|
(payload: {
|
|
178
|
-
project
|
|
179
|
-
projectConfig
|
|
180
|
-
compositions
|
|
181
|
-
interactionLinks
|
|
202
|
+
project?: { _id: string; account: string }
|
|
203
|
+
projectConfig?: ProjectConfiguration
|
|
204
|
+
compositions?: Composition[]
|
|
205
|
+
interactionLinks?: InteractionLink[]
|
|
206
|
+
token?: string // Work session JWT token
|
|
182
207
|
}) => {
|
|
183
|
-
const { project, projectConfig, compositions, interactionLinks } = payload
|
|
208
|
+
const { project, projectConfig, compositions, interactionLinks, token } = payload
|
|
209
|
+
|
|
210
|
+
// Check if this is a work session (no project but has compositions/interactionLinks)
|
|
211
|
+
const isWorkSession = !project && compositions && compositions.length > 0
|
|
212
|
+
|
|
213
|
+
if (isWorkSession) {
|
|
214
|
+
// Work session: use session ID as room identifier
|
|
215
|
+
const sessionId = extractSessionIdFromUrl() || "work_session"
|
|
216
|
+
const resolveId = "join_session_" + sessionId
|
|
217
|
+
|
|
218
|
+
log.socket("onJoinConfirmation", "Work session join", payload)
|
|
219
|
+
|
|
220
|
+
// If token is provided, use it for authentication (replaces x-work-session-id header)
|
|
221
|
+
if (token) {
|
|
222
|
+
setHttpToken(token)
|
|
223
|
+
// Clear the x-work-session-id header since we're using token now
|
|
224
|
+
setWorkSessionId(null)
|
|
225
|
+
log.socket("onJoinConfirmation", "Work session token set", { hasToken: !!token })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
$eventbus.once("store_actionResolved_" + resolveId, async (action) => {
|
|
229
|
+
$store.dispatch({
|
|
230
|
+
type: "ui/update",
|
|
231
|
+
payload: { loading: true },
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// Use the same setupSpace function as logged-in users
|
|
235
|
+
// This ensures all normal processing happens (landing page detection, etc.)
|
|
236
|
+
await setupSpace(compositions || [], interactionLinks || [], projectConfig || {})
|
|
184
237
|
|
|
185
|
-
|
|
238
|
+
// Don't navigate - let the default composition loading logic handle it
|
|
239
|
+
// The URL stays as /:workSession/id={uuid} and the router will match
|
|
240
|
+
// the composition from the store based on the default logic
|
|
241
|
+
|
|
242
|
+
// Enable fullscreen mode (hide nav bars) for work sessions
|
|
243
|
+
$store.dispatch({
|
|
244
|
+
type: "ui/update",
|
|
245
|
+
payload: {
|
|
246
|
+
availableInterfaces: [], // Hide all UI interfaces for fullscreen
|
|
247
|
+
navigationOpen: false,
|
|
248
|
+
},
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
$store.dispatch({
|
|
252
|
+
type: "ui/update",
|
|
253
|
+
payload: { loading: false },
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
$eventbus.emit("ui/setupProject", sessionId)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// Reset store states for work session
|
|
260
|
+
$store.dispatch({
|
|
261
|
+
type: "projectInit",
|
|
262
|
+
resolveId,
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Regular project join confirmation (existing flow)
|
|
269
|
+
const roomId = project?._id
|
|
186
270
|
|
|
187
271
|
if (!project) {
|
|
188
272
|
log.socket("onJoinConfirmation", "no Room")
|
|
189
|
-
|
|
190
273
|
return
|
|
191
274
|
}
|
|
275
|
+
|
|
192
276
|
log.socket("onJoinConfirmation", payload)
|
|
193
277
|
|
|
194
278
|
const resolveId = "join_" + roomId
|
|
@@ -204,7 +288,7 @@ const registerSocketEvents = ($socket, $store, $eventbus) => {
|
|
|
204
288
|
type: "ui/update",
|
|
205
289
|
payload: { loading: true },
|
|
206
290
|
})
|
|
207
|
-
await setupSpace(compositions, interactionLinks, projectConfig)
|
|
291
|
+
await setupSpace(compositions || [], interactionLinks || [], projectConfig || {})
|
|
208
292
|
|
|
209
293
|
$eventbus.emit("ui/setupProject", roomId)
|
|
210
294
|
})
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
import {
|
|
38
38
|
compositionsReducers,
|
|
39
39
|
compositionsEffects,
|
|
40
|
+
compositionsSelectors,
|
|
40
41
|
} from "./reducers/compositions"
|
|
41
42
|
import backendReducers from "./reducers/backend"
|
|
42
43
|
import { queriesEffects, queriesReducers } from "./reducers/queries"
|
|
@@ -51,7 +52,7 @@ const extensions =
|
|
|
51
52
|
? [
|
|
52
53
|
new LoggerExtension(),
|
|
53
54
|
new ReduxDevtoolsExtension({
|
|
54
|
-
name: "
|
|
55
|
+
name: "racletteJS",
|
|
55
56
|
trace: true,
|
|
56
57
|
traceLimit: 25,
|
|
57
58
|
}),
|
|
@@ -129,6 +130,9 @@ const createStore = (eventbus, appSessionId: string): RacletteStore => {
|
|
|
129
130
|
appSessionId,
|
|
130
131
|
hasChanges: (oldVal: string, newVal: string): boolean =>
|
|
131
132
|
JSON.stringify(oldVal) !== JSON.stringify(newVal),
|
|
133
|
+
selectors: {
|
|
134
|
+
compositions: compositionsSelectors,
|
|
135
|
+
},
|
|
132
136
|
getState: (params: string | any[] | StoreHelperGetState = {}): object => {
|
|
133
137
|
// Default values
|
|
134
138
|
let stateKey = ""
|
|
@@ -11,7 +11,7 @@ import type { WidgetBase } from "../../types"
|
|
|
11
11
|
import { v4 as uuidv4 } from "uuid"
|
|
12
12
|
import { $store } from "@racletteCore"
|
|
13
13
|
import log from "@racletteCore/lib/logger"
|
|
14
|
-
|
|
14
|
+
import * as compositionsSelectors from "./selectors"
|
|
15
15
|
const reducerMap = {
|
|
16
16
|
init: () => applicationState.pages,
|
|
17
17
|
projectInit: reducer.projectInit,
|
|
@@ -82,4 +82,4 @@ const compositionsEffects = (store) => {
|
|
|
82
82
|
}
|
|
83
83
|
const compositionsReducers = reducerFromMap(reducerMap)
|
|
84
84
|
|
|
85
|
-
export { compositionsEffects, compositionsReducers }
|
|
85
|
+
export { compositionsEffects, compositionsReducers, compositionsSelectors }
|
|
@@ -6,7 +6,7 @@ import reducerFromMap from "@racletteCore/lib/reducerFromMap"
|
|
|
6
6
|
import { applicationState } from "@racletteCore/store/state"
|
|
7
7
|
import interactionLinksEffects from "./effects"
|
|
8
8
|
const reducerMap = {
|
|
9
|
-
init: () => applicationState.
|
|
9
|
+
init: () => applicationState.interactionLinks,
|
|
10
10
|
projectInit: reducer.projectInit,
|
|
11
11
|
"interactionLinks/add": reducer.interactionLinksAdd,
|
|
12
12
|
"interactionLinks/set": reducer.interactionLinksSet,
|
|
@@ -3,9 +3,8 @@ import { applicationState, storePatterns } from "@racletteCore/store/state"
|
|
|
3
3
|
|
|
4
4
|
const cleanWidgetState = (widget) => {
|
|
5
5
|
const cleanWidget = clone(widget)
|
|
6
|
-
|
|
7
6
|
delete cleanWidget.type
|
|
8
|
-
|
|
7
|
+
// TODO implement empty state attribute deletion here, using removeEmpty
|
|
9
8
|
return cleanWidget
|
|
10
9
|
}
|
|
11
10
|
|
|
@@ -12,6 +12,8 @@ import type {
|
|
|
12
12
|
PluginApi,
|
|
13
13
|
} from "@racletteOrchestrator"
|
|
14
14
|
import type { DefineComponent } from "vue"
|
|
15
|
+
import type { UIState, RouteState, WidgetBase } from "@racletteCore/store/types"
|
|
16
|
+
import type { Query } from "@racletteCore/store/reducers/queries/reducers"
|
|
15
17
|
export type BroadcastChannelDefinition = {
|
|
16
18
|
channel: string
|
|
17
19
|
channelKey: string
|
|
@@ -77,12 +79,12 @@ export type PluginFrontendDefinition = {
|
|
|
77
79
|
type StorePayloadObject = Record<string, any>
|
|
78
80
|
|
|
79
81
|
export type PluginStoreApi = {
|
|
80
|
-
getUiState
|
|
81
|
-
setUiState: (payload:
|
|
82
|
-
getWidgetState
|
|
83
|
-
updateWidgetState: (payload:
|
|
84
|
-
setWidgetState: (payload:
|
|
85
|
-
getQueryState
|
|
82
|
+
getUiState(): UIState
|
|
83
|
+
setUiState: (payload: Partial<UIState>) => void
|
|
84
|
+
getWidgetState(widgetId: string): WidgetBase | Record<string, never>
|
|
85
|
+
updateWidgetState: (payload: Partial<WidgetBase>, widgetId: string) => void
|
|
86
|
+
setWidgetState: (payload: Partial<WidgetBase>, widgetId: string) => void
|
|
87
|
+
getQueryState(actionId: actionId): Query | Record<string, never>
|
|
86
88
|
getQueryData<T = any>(actionId: actionId): T
|
|
87
89
|
get<T = any>(pathArray: string[], filter: Record<string, any>): T
|
|
88
90
|
}
|
|
@@ -13,5 +13,6 @@ export type StoreHelperGetState = {
|
|
|
13
13
|
export type RacletteStore = ReduxStore & {
|
|
14
14
|
hasChanges: (oldVal: string, newVal: string) => boolean
|
|
15
15
|
getState: (params: string | any[] | StoreHelperGetState) => any
|
|
16
|
+
selectors: Record<string, any>
|
|
16
17
|
appSessionId?: String
|
|
17
18
|
}
|
|
@@ -87,11 +87,20 @@
|
|
|
87
87
|
<script setup lang="ts">
|
|
88
88
|
import type { Composition } from "@shared/types/core/Composition.types"
|
|
89
89
|
import type { WidgetBase } from "@racletteCore/store/types"
|
|
90
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
watch,
|
|
92
|
+
ref,
|
|
93
|
+
h,
|
|
94
|
+
computed,
|
|
95
|
+
onUnmounted,
|
|
96
|
+
nextTick,
|
|
97
|
+
type Component,
|
|
98
|
+
} from "vue"
|
|
91
99
|
import { widgetLoader } from "@racletteOrchestrator/helpers/widgetLoader"
|
|
92
100
|
import { useDisplay } from "vuetify"
|
|
93
101
|
import useStateSubscriber from "@racletteOrchestrator/composables/useStateSubscriber"
|
|
94
102
|
import useRouteState from "@racletteOrchestrator/composables/useRouteState"
|
|
103
|
+
import { createWidgetLifecycleManager } from "@racletteOrchestrator/composables/useWidgetLifecycle"
|
|
95
104
|
|
|
96
105
|
const props = withDefaults(
|
|
97
106
|
defineProps<{
|
|
@@ -104,6 +113,7 @@ const props = withDefaults(
|
|
|
104
113
|
maxHeight: 0,
|
|
105
114
|
},
|
|
106
115
|
)
|
|
116
|
+
|
|
107
117
|
const { compositionSlots } = useRouteState()
|
|
108
118
|
|
|
109
119
|
const currentRouteConfig = computed(() =>
|
|
@@ -156,12 +166,18 @@ const gridKey = ref(0)
|
|
|
156
166
|
|
|
157
167
|
// Calculate max height per widget based on grid layout
|
|
158
168
|
const maxWidgetHeight = computed(() => {
|
|
159
|
-
// Always set max height to the calculated available screen height (with buffer)
|
|
160
|
-
// Later we should determine a max value based on other widgets in each row, so this value will be different across rows and columns
|
|
161
|
-
|
|
162
169
|
return Math.min(props.maxHeight, 2400)
|
|
163
170
|
})
|
|
164
171
|
|
|
172
|
+
// Calculate total number of widgets
|
|
173
|
+
const totalWidgets = computed(() => {
|
|
174
|
+
return props.widgetsLayout.reduce((sum, column) => sum + column.length, 0)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// Widget lifecycle manager
|
|
178
|
+
const lifecycleManager = ref(createWidgetLifecycleManager(totalWidgets.value))
|
|
179
|
+
|
|
180
|
+
// Widget component cache
|
|
165
181
|
const widgetComponentCache = new Map<string, Component>()
|
|
166
182
|
|
|
167
183
|
const getSlotWidget = (widget: WidgetBase) => {
|
|
@@ -169,11 +185,45 @@ const getSlotWidget = (widget: WidgetBase) => {
|
|
|
169
185
|
|
|
170
186
|
if (!widgetComponentCache.has(cacheKey)) {
|
|
171
187
|
try {
|
|
172
|
-
const
|
|
173
|
-
|
|
188
|
+
const originalComponent = widgetLoader(widget)
|
|
189
|
+
|
|
190
|
+
// Wrap the component to inject lifecycle hooks
|
|
191
|
+
const wrappedComponent = {
|
|
192
|
+
name: `${originalComponent.name || "Widget"}Wrapper`,
|
|
193
|
+
setup(props, context) {
|
|
194
|
+
const { onAllWidgetsReady, onWidgetRendered, onWidgetUnmounted } =
|
|
195
|
+
lifecycleManager.value.createLifecycleHooks(widget.uuid)
|
|
196
|
+
|
|
197
|
+
onUnmounted(() => {
|
|
198
|
+
onWidgetUnmounted()
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Pass lifecycle hook to the component as a prop
|
|
202
|
+
const enhancedProps = {
|
|
203
|
+
...props,
|
|
204
|
+
onAllWidgetsReady,
|
|
205
|
+
ref: (el: any) => {
|
|
206
|
+
if (el) {
|
|
207
|
+
// Element is mounted, observe it
|
|
208
|
+
nextTick(() => {
|
|
209
|
+
// Get the actual DOM element
|
|
210
|
+
const element = el.$el || el
|
|
211
|
+
if (element instanceof HTMLElement) {
|
|
212
|
+
onWidgetRendered(element)
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return () => h(originalComponent, enhancedProps, context.slots)
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
widgetComponentCache.set(cacheKey, wrappedComponent)
|
|
174
224
|
} catch (error) {
|
|
175
225
|
console.error("🚨 Error creating widget component:", error)
|
|
176
|
-
//
|
|
226
|
+
// Cache error component too
|
|
177
227
|
widgetComponentCache.set(cacheKey, {
|
|
178
228
|
name: "ComponentCreationError",
|
|
179
229
|
setup() {
|
|
@@ -195,10 +245,20 @@ const getGridCols = (rowLength) => (mdAndDown.value ? 12 : 12 / rowLength)
|
|
|
195
245
|
|
|
196
246
|
const getSlotCols = (slot) => (mdAndDown.value ? 12 : slot.column)
|
|
197
247
|
|
|
198
|
-
// Watch for changes in widgetsLayout to trigger transition
|
|
248
|
+
// Watch for changes in widgetsLayout to trigger transition and reset lifecycle
|
|
199
249
|
watch(
|
|
200
250
|
() => props.widgetsLayout,
|
|
201
251
|
() => {
|
|
252
|
+
// Reset lifecycle manager
|
|
253
|
+
lifecycleManager.value.reset()
|
|
254
|
+
|
|
255
|
+
// Clear component cache to force re-wrapping with new lifecycle manager
|
|
256
|
+
widgetComponentCache.clear()
|
|
257
|
+
|
|
258
|
+
// Create new lifecycle manager with updated widget count
|
|
259
|
+
lifecycleManager.value = createWidgetLifecycleManager(totalWidgets.value)
|
|
260
|
+
|
|
261
|
+
// Trigger grid transition
|
|
202
262
|
gridKey.value++
|
|
203
263
|
},
|
|
204
264
|
{ deep: true },
|