@raclettejs/core 0.1.19 → 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.
Files changed (132) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/cli.js +1 -1
  3. package/dev/README.md +4 -0
  4. package/dist/cli.js +135 -124
  5. package/dist/cli.js.map +4 -4
  6. package/dist/index.js +9 -8
  7. package/dist/index.js.map +4 -4
  8. package/package.json +9 -5
  9. package/services/backend/delay-api-debugger.sh +0 -0
  10. package/services/backend/package.json +1 -0
  11. package/services/backend/src/app.ts +4 -0
  12. package/services/backend/src/core/config/configService.ts +10 -1
  13. package/services/backend/src/core/events/eventEmitter.ts +32 -7
  14. package/services/backend/src/core/http/request-params.plugin.ts +2 -2
  15. package/services/backend/src/core/pluginSystem/pluginFastifyInstance.ts +4 -1
  16. package/services/backend/src/core/pluginSystem/pluginTypes.ts +3 -1
  17. package/services/backend/src/core/sockets/createSystemEventHandlers.ts +7 -0
  18. package/services/backend/src/core/sockets/index.ts +61 -13
  19. package/services/backend/src/core/sockets/socketManager.ts +452 -105
  20. package/services/backend/src/core/sockets/socketTypes.ts +13 -1
  21. package/services/backend/src/core/sockets/workSessionJoinPayload.ts +106 -0
  22. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.get.ts +3 -1
  23. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.getAll.ts +3 -1
  24. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.hardDelete.ts +16 -19
  25. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.patch.ts +3 -1
  26. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.post.ts +3 -1
  27. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/account/routes/route.account.remove.ts +3 -1
  28. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.service.ts +2 -3
  29. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.get.ts +3 -1
  30. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.getAll.ts +3 -1
  31. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.hardDelete.ts +16 -19
  32. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.patch.ts +3 -1
  33. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.bulk.ts +3 -1
  34. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.post.ts +3 -1
  35. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.remove.ts +3 -1
  36. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/routes/route.composition.restore.ts +12 -15
  37. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.service.ts +2 -3
  38. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.get.ts +3 -1
  39. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.getAll.ts +4 -2
  40. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.hardDelete.ts +12 -17
  41. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.patch.ts +3 -1
  42. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.bulk.ts +3 -1
  43. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.post.ts +3 -1
  44. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.remove.ts +3 -1
  45. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/route.interactionLink.restore.ts +12 -15
  46. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.service.ts +1 -0
  47. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.get.ts +3 -1
  48. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.getAll.ts +4 -2
  49. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.hardDelete.ts +3 -1
  50. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.patch.ts +3 -1
  51. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.post.ts +4 -2
  52. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/routes/route.project.remove.ts +3 -1
  53. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.get.ts +1 -0
  54. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.getAll.ts +1 -0
  55. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.hardDelete.ts +10 -15
  56. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.patch.ts +3 -1
  57. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.bulk.ts +3 -1
  58. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.post.ts +3 -1
  59. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.remove.ts +3 -1
  60. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/routes/route.tag.restore.ts +12 -15
  61. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/tag/tag.service.ts +1 -2
  62. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.get.ts +1 -0
  63. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.getAll.ts +1 -0
  64. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.hardDelete.ts +10 -15
  65. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patch.ts +4 -6
  66. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.patchLastProject.ts +4 -2
  67. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.post.ts +3 -1
  68. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.remove.ts +3 -4
  69. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/user/routes/route.user.restore.ts +12 -15
  70. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/events/index.ts +45 -0
  71. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/crud.ts +12 -0
  72. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/helpers/payload.ts +21 -0
  73. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/index.ts +55 -0
  74. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.ts +19 -0
  75. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.get.ts +53 -0
  76. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.getAll.ts +39 -0
  77. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.hardDelete.ts +53 -0
  78. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.patch.ts +58 -0
  79. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.post.ts +53 -0
  80. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/routes/route.workSession.remove.ts +52 -0
  81. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.model.ts +65 -0
  82. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.schema.ts +125 -0
  83. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/workSession/workSession.service.ts +351 -0
  84. package/services/backend/src/corePlugins/raclette__core/backend/index.ts +2 -0
  85. package/services/backend/src/corePlugins/raclette__core/frontend/generated-config.ts +159 -24
  86. package/services/backend/src/modules/authentication/index.ts +255 -4
  87. package/services/backend/src/modules/hooks/rate-limiter.ts +21 -0
  88. package/services/backend/src/types/custom-fastify.d.ts +17 -0
  89. package/services/backend/src/utils/request.utils.ts +4 -1
  90. package/services/backend/types/index.d.ts +15 -1
  91. package/services/backend/yarn.lock +13 -4
  92. package/services/frontend/index.html +1 -1
  93. package/services/frontend/provision/entrypoint.sh +0 -0
  94. package/services/frontend/src/core/lib/configService.ts +1 -1
  95. package/services/frontend/src/core/lib/dataHelper.ts +1 -1
  96. package/services/frontend/src/core/lib/eggs/snow.ts +49 -20
  97. package/services/frontend/src/core/lib/helpers/index.ts +1 -0
  98. package/services/frontend/src/core/lib/helpers/removeEmpty.ts +69 -0
  99. package/services/frontend/src/core/lib/httpClient.ts +19 -0
  100. package/services/frontend/src/core/main.ts +20 -10
  101. package/services/frontend/src/core/setup/socket.ts +4 -3
  102. package/services/frontend/src/core/setup/socketClient.ts +31 -3
  103. package/services/frontend/src/core/setup/socketEvents.ts +92 -8
  104. package/services/frontend/src/core/store/index.ts +5 -1
  105. package/services/frontend/src/core/store/reducers/compositions/index.ts +2 -2
  106. package/services/frontend/src/core/store/reducers/interactionLinks/index.ts +1 -1
  107. package/services/frontend/src/core/store/reducers/widgets/reducers.ts +1 -2
  108. package/services/frontend/src/core/store/types/index.ts +0 -1
  109. package/services/frontend/src/core/types/PluginRegistrar.ts +8 -6
  110. package/services/frontend/src/core/types/Store.ts +1 -0
  111. package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +68 -8
  112. package/services/frontend/src/orchestrator/components/menu/UserMenu.vue +17 -5
  113. package/services/frontend/src/orchestrator/components/welcomeScreen/AdminWelcomeScreen.vue +1 -1
  114. package/services/frontend/src/orchestrator/composables/index.ts +1 -0
  115. package/services/frontend/src/orchestrator/composables/usePluginApi.ts +0 -3
  116. package/services/frontend/src/orchestrator/composables/useRouteState.ts +23 -4
  117. package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +166 -0
  118. package/services/frontend/src/orchestrator/helpers/index.ts +1 -0
  119. package/services/frontend/src/orchestrator/i18n/de-DE.json +1 -1
  120. package/services/frontend/src/orchestrator/i18n/en-EU.json +1 -1
  121. package/services/frontend/src/orchestrator/i18n/sk.json +1 -1
  122. package/services/frontend/src/orchestrator/index.ts +34 -4
  123. package/services/frontend/src/orchestrator/router/routeParserHelper.ts +106 -1
  124. package/services/frontend/src/orchestrator/types/Widgets.ts +2 -0
  125. package/services/frontend/types.ts +1 -0
  126. package/src/README.md +404 -0
  127. package/src/types.ts +19 -5
  128. package/templates/README.md +0 -0
  129. package/types/index.ts +18 -4
  130. package/services/backend/.gitignore +0 -17
  131. package/services/frontend/.gitignore +0 -35
  132. package/yarn.lock +0 -2937
@@ -7,11 +7,22 @@ import {
7
7
  transformToOriginalState,
8
8
  } from "@racletteOrchestrator/router/routeParserHelper"
9
9
  import { compose, urlCompose } from "@racletteOrchestrator/router/routeParser"
10
- import { clone, mergeDeepRight, pick } from "ramda"
10
+ import {
11
+ clone,
12
+ mergeDeepRight,
13
+ pick,
14
+ evolve,
15
+ when,
16
+ is,
17
+ pickBy,
18
+ complement,
19
+ isNil,
20
+ } from "ramda"
11
21
  import type { InteractionLink } from "@shared/types"
12
22
  import {
13
23
  getCompositionById,
14
24
  getLocalizedName,
25
+ getCompositionIdByPath,
15
26
  } from "@racletteCore/store/reducers/compositions/selectors"
16
27
  import useInteractionLinks from "./useInteractionLinks"
17
28
 
@@ -48,9 +59,14 @@ export const useRouteState = () => {
48
59
  )
49
60
 
50
61
  const updateSlot = (slot: CompositionSlot) => {
51
- const newUrl = getInteractionLinkUrl(slot)
62
+ const cleanedSlot = evolve(
63
+ {
64
+ slotParams: when(is(Object), pickBy(complement(isNil))),
65
+ },
66
+ slot,
67
+ )
52
68
 
53
- // router.push({ name: "app.space.page", params: { pageId: newUrl } })
69
+ const newUrl = getInteractionLinkUrl(cleanedSlot)
54
70
  router.push({ path: "/" + newUrl })
55
71
  }
56
72
 
@@ -112,7 +128,9 @@ export const useRouteState = () => {
112
128
  console.error(`InteractionLink ${id} not found!`)
113
129
  }
114
130
  }
115
-
131
+ const getCurrentCompositionId = (compositionPath: string): string => {
132
+ return getCompositionIdByPath(compositionPath)
133
+ }
116
134
  return {
117
135
  composed,
118
136
  compositionSlots,
@@ -122,6 +140,7 @@ export const useRouteState = () => {
122
140
  removeSlot,
123
141
  triggerInteractionLink,
124
142
  triggerInteractionLinkById,
143
+ getCurrentCompositionId,
125
144
  }
126
145
  }
127
146
 
@@ -0,0 +1,166 @@
1
+ import { ref, type Ref } from "vue"
2
+ import getEnvVariable from "@racletteCore/lib/getEnvVariable"
3
+ import log from "@racletteCore/lib/logger"
4
+
5
+ export type OnAllWidgetsReady = (callback: () => void) => void
6
+
7
+ interface WidgetLifecycleHooks {
8
+ onAllWidgetsReady: OnAllWidgetsReady
9
+ onWidgetRendered: (element: HTMLElement) => void
10
+ onWidgetUnmounted: () => void
11
+ }
12
+
13
+ interface WidgetLifecycleManager {
14
+ createLifecycleHooks: (widgetId: string) => WidgetLifecycleHooks
15
+ allWidgetsReady: Ref<boolean>
16
+ reset: () => void
17
+ }
18
+
19
+ export function createWidgetLifecycleManager(
20
+ totalWidgets: number,
21
+ ): WidgetLifecycleManager {
22
+ const renderedWidgets = ref(new Set<string>())
23
+ const allWidgetsReady = ref(false)
24
+ const readyCallbacks = new Set<() => void>()
25
+ const observers = new Map<string, MutationObserver>()
26
+
27
+ const DEV_MODE =
28
+ getEnvVariable("RACLETTE_DEBUG_MODE") === "true" ||
29
+ !getEnvVariable("RACLETTE_DEBUG_MODE")
30
+
31
+ const checkAllReady = () => {
32
+ if (renderedWidgets.value.size === totalWidgets && !allWidgetsReady.value) {
33
+ allWidgetsReady.value = true
34
+
35
+ if (DEV_MODE) {
36
+ log.widgets(
37
+ "useWidgetLifecycle",
38
+ `✅ All ${totalWidgets} widgets are fully rendered`,
39
+ )
40
+ }
41
+
42
+ readyCallbacks.forEach((callback) => callback())
43
+ readyCallbacks.clear()
44
+ }
45
+ }
46
+
47
+ const hasVisibleContent = (element: HTMLElement): boolean => {
48
+ // Check if element has any child nodes
49
+ if (element.childNodes.length === 0) {
50
+ return false
51
+ }
52
+
53
+ // Check if element has visible dimensions
54
+ const rect = element.getBoundingClientRect()
55
+ if (rect.width === 0 && rect.height === 0) {
56
+ return false
57
+ }
58
+
59
+ // Check for text content or child elements
60
+ const hasTextContent = element.textContent?.trim().length > 0
61
+ const hasChildElements = element.children.length > 0
62
+
63
+ return hasTextContent || hasChildElements
64
+ }
65
+
66
+ const registerWidgetAsRendered = (id: string) => {
67
+ if (!renderedWidgets.value.has(id)) {
68
+ renderedWidgets.value.add(id)
69
+
70
+ if (DEV_MODE) {
71
+ log.widgets(
72
+ "useWidgetLifecycle",
73
+ `📦 Widget rendered: ${id} (${renderedWidgets.value.size}/${totalWidgets})`,
74
+ )
75
+ }
76
+
77
+ checkAllReady()
78
+ }
79
+ }
80
+
81
+ const unregisterWidget = (id: string) => {
82
+ if (DEV_MODE) {
83
+ log.widgets("useWidgetLifecycle", `📤 Widget unregistered: ${id}`)
84
+ }
85
+
86
+ // Clean up observer
87
+ const observer = observers.get(id)
88
+ if (observer) {
89
+ observer.disconnect()
90
+ observers.delete(id)
91
+ }
92
+
93
+ renderedWidgets.value.delete(id)
94
+
95
+ if (renderedWidgets.value.size < totalWidgets) {
96
+ allWidgetsReady.value = false
97
+ }
98
+ }
99
+
100
+ const observeWidgetRendering = (id: string, element: HTMLElement) => {
101
+ // Check immediately if content is already rendered
102
+ if (hasVisibleContent(element)) {
103
+ registerWidgetAsRendered(id)
104
+ return
105
+ }
106
+
107
+ // Otherwise, observe for changes
108
+ const observer = new MutationObserver(() => {
109
+ if (hasVisibleContent(element)) {
110
+ registerWidgetAsRendered(id)
111
+ observer.disconnect()
112
+ observers.delete(id)
113
+ }
114
+ })
115
+
116
+ observer.observe(element, {
117
+ childList: true,
118
+ subtree: true,
119
+ characterData: true,
120
+ attributes: true,
121
+ attributeFilter: ["style", "class"],
122
+ })
123
+
124
+ observers.set(id, observer)
125
+
126
+ if (DEV_MODE) {
127
+ log.widgets(
128
+ "useWidgetLifecycle",
129
+ `👀 Observing widget for content: ${id}`,
130
+ )
131
+ }
132
+ }
133
+
134
+ const createLifecycleHooks = (widgetId: string): WidgetLifecycleHooks => ({
135
+ onAllWidgetsReady: (callback: () => void) => {
136
+ if (allWidgetsReady.value) {
137
+ callback()
138
+ } else {
139
+ readyCallbacks.add(callback)
140
+ }
141
+ },
142
+ onWidgetRendered: (element: HTMLElement) => {
143
+ observeWidgetRendering(widgetId, element)
144
+ },
145
+ onWidgetUnmounted: () => unregisterWidget(widgetId),
146
+ })
147
+
148
+ const reset = () => {
149
+ // Disconnect all observers
150
+ observers.forEach((observer) => observer.disconnect())
151
+ observers.clear()
152
+ renderedWidgets.value.clear()
153
+ allWidgetsReady.value = false
154
+ readyCallbacks.clear()
155
+
156
+ if (DEV_MODE) {
157
+ log.widgets("useWidgetLifecycle", "🔄 Widget lifecycle manager reset")
158
+ }
159
+ }
160
+
161
+ return {
162
+ createLifecycleHooks,
163
+ allWidgetsReady,
164
+ reset,
165
+ }
166
+ }
@@ -1 +1,2 @@
1
1
  export * from "./loginUserNotifications"
2
+ export * from "./widgetLoader"
@@ -5,7 +5,7 @@
5
5
  "subtitle": "Konfiguriere eine Landingpage in der Workbench, damit diese Seite verschwindet! Sieh dir den Dialog zur Erstellung/Bearbeitung von InteractionLinks für den Landingpage-Umschalter an.",
6
6
  "subtitleUser": "Deine Schnittstelle zur käsigen Seite der Dinge",
7
7
  "noLandingPage": "Keine Landingpage konfiguriert",
8
- "visitWorkbench": "Besuche die Raclette-Workbench, um dein Portal zu konfigurieren und deine bevorzugte Landingpage einzurichten.",
8
+ "visitWorkbench": "Besuche die racletteJS-Workbench, um dein Portal zu konfigurieren und deine bevorzugte Landingpage einzurichten.",
9
9
  "goToWorkbench": "Workbench öffnen",
10
10
  "configurationsTitle": "Was du in der Workbench konfigurieren kannst",
11
11
  "compositionInfoTitle": "Kompositionen",
@@ -5,7 +5,7 @@
5
5
  "subtitle": "Configure a Landingpage in the Workbench to make this Page disapear! See the InteractionLink Creation/Edit Dialogue for the landingpage toggle",
6
6
  "subtitleUser": "Your interface to the cheesy side of things",
7
7
  "noLandingPage": "No landing page configured",
8
- "visitWorkbench": "Visit the Raclette Workbench to configure your portal and set up your preferred landing page.",
8
+ "visitWorkbench": "Visit the racletteJS Workbench to configure your portal and set up your preferred landing page.",
9
9
  "goToWorkbench": "Open Workbench",
10
10
  "configurationsTitle": "What you can configure in the Workbench",
11
11
  "compositionInfoTitle": "Compositions",
@@ -5,7 +5,7 @@
5
5
  "subtitle": "Nakonfigurujte vstupnú stránku vo Workbench, aby táto stránka zmizla! Pozrite si dialóg na vytváranie/upravenie InteractionLink pre prepínač vstupnej stránky.",
6
6
  "subtitleUser": "Vaše rozhranie k syrovej strane vecí",
7
7
  "noLandingPage": "Vstupná stránka nie je nakonfigurovaná",
8
- "visitWorkbench": "Navštívte Raclette Workbench na konfiguráciu vášho portálu a nastavenie preferovanej vstupnej stránky.",
8
+ "visitWorkbench": "Navštívte racletteJS Workbench na konfiguráciu vášho portálu a nastavenie preferovanej vstupnej stránky.",
9
9
  "goToWorkbench": "Otvoriť Workbench",
10
10
  "configurationsTitle": "Čo môžete nakonfigurovať vo Workbench",
11
11
  "compositionInfoTitle": "Kompozície",
@@ -2,25 +2,55 @@ import type { App as VueApp } from "vue"
2
2
  import createRacletteApp, { corePluginApi } from "@racletteCore/main"
3
3
  import { createVueApp, createLoginVueApp } from "./main"
4
4
  import { initApp } from "./setup/application"
5
+ import { validate } from "uuid"
5
6
  export * from "./exports"
6
7
 
7
8
  const VUE_MOUNTING_ELEMENT = "#app"
8
9
  let currentApp: VueApp | undefined
9
10
 
11
+ /**
12
+ * Check if URL matches :workSession/id={uuid} pattern and extract sessionId
13
+ * Supports racletteJS route format: /:workSession/id={uuid}
14
+ */
15
+ const extractSessionIdFromUrl = (): string | null => {
16
+ const path = window.location.pathname + (window.location.hash || "")
17
+ // Match /:workSession/id={uuid} pattern
18
+ // The route parser will handle this, but we need to extract it here for bootstrapping
19
+ const workSessionMatch = path.match(/\/:workSession\/id=([^/&#]+)/)
20
+
21
+ if (workSessionMatch && workSessionMatch[1]) {
22
+ const sessionId = workSessionMatch[1]
23
+ // Validate UUID format
24
+ if (validate(sessionId)) {
25
+ return sessionId
26
+ }
27
+ }
28
+
29
+ return null
30
+ }
31
+
10
32
  const startApp = async () => {
11
33
  try {
12
- const res = await createRacletteApp()
34
+ // Check if URL indicates a work session
35
+ const sessionId = extractSessionIdFromUrl()
36
+
37
+ const res = await createRacletteApp({
38
+ requireAuth: !sessionId, // Skip auth requirement if sessionId present
39
+ sessionId: sessionId || undefined,
40
+ })
13
41
 
14
42
  if (currentApp) {
15
43
  currentApp.unmount()
16
44
  currentApp = undefined
17
45
  }
18
46
 
19
- if (!res.authenticated) {
47
+ if (!res.authenticated && !sessionId) {
20
48
  const noUsers = res.code === "RCLT_DB_NO_USERS"
21
- currentApp = createLoginVueApp(VUE_MOUNTING_ELEMENT, res.login, noUsers) // No await needed
49
+ if (res.login) {
50
+ currentApp = await createLoginVueApp(VUE_MOUNTING_ELEMENT, res.login, noUsers)
51
+ }
22
52
  } else {
23
- currentApp = createVueApp(VUE_MOUNTING_ELEMENT) // No await needed
53
+ currentApp = await createVueApp(VUE_MOUNTING_ELEMENT)
24
54
  await initApp(corePluginApi)
25
55
  }
26
56
  } catch (err) {
@@ -1,5 +1,8 @@
1
1
  import { compose, type ComposedRouteState } from "./routeParser"
2
2
  import { $store } from "@racletteCore"
3
+ import { validate } from "uuid"
4
+ import type { Composition } from "@shared/types/core/Composition.types"
5
+ import type { InteractionLink } from "@shared/types/core/InteractionLink.types"
3
6
 
4
7
  export type WidgetLayoutType = "default" | "spaced" | "card" | "border" | "full"
5
8
 
@@ -15,11 +18,50 @@ export const DEFAULT_SLOT_NAME = "page"
15
18
  export const DEFAULT_MODAL_SLOT_NAME = "modal"
16
19
  export const DEFAULT_INLINE_SLOT_NAME = "inline"
17
20
 
21
+ /**
22
+ * Reserved composition name for work sessions
23
+ * When detected, triggers special handling for shared sessions
24
+ */
25
+ export const WORK_SESSION_COMPOSITION_NAME = "workSession"
26
+
27
+ /**
28
+ * Extract sessionId from workSession composition route
29
+ * Handles URLs like /workSession/id={uuid}
30
+ * Returns the UUID if found, null otherwise
31
+ */
32
+ const extractWorkSessionId = (route: string): string | null => {
33
+ // Parse the route to check if it contains workSession composition
34
+ const composed = compose(route)
35
+
36
+ // Check if any composition in the route is "workSession"
37
+ for (const [composition, paramsArray] of composed.map) {
38
+ if (composition === WORK_SESSION_COMPOSITION_NAME) {
39
+ // Extract id parameter from the first params object
40
+ const params =
41
+ Array.isArray(paramsArray) && paramsArray.length > 0
42
+ ? paramsArray[0]
43
+ : {}
44
+
45
+ if (typeof params === "object" && params !== null && "id" in params) {
46
+ const sessionId = params.id as string
47
+ // Validate UUID format
48
+ if (validate(sessionId)) {
49
+ return sessionId
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ return null
56
+ }
57
+
18
58
  export const parseRouteToCompositionSlots = (
19
59
  route: string,
20
60
  defaultSlotName = DEFAULT_SLOT_NAME,
21
61
  addDefaultSlotIfMissing = false,
22
62
  ) => {
63
+ // If it's a workSession route, we'll handle it specially
64
+ // For now, parse normally - the composition will be updated after socket connection
23
65
  const composed = compose(route)
24
66
  return parseComposedToCompositionSlots(
25
67
  composed,
@@ -27,6 +69,25 @@ export const parseRouteToCompositionSlots = (
27
69
  addDefaultSlotIfMissing,
28
70
  )
29
71
  }
72
+ // Fix number and boolean params
73
+ const coerceParams = (
74
+ params: Record<string, string>,
75
+ ): Record<string, string | number | boolean> =>
76
+ Object.fromEntries(
77
+ Object.entries(params).map(([key, value]) => {
78
+ if (value === "true") return [key, true]
79
+ if (value === "false") return [key, false]
80
+ if (value !== "" && !isNaN(Number(value))) return [key, Number(value)]
81
+ return [key, value]
82
+ }),
83
+ )
84
+ /**
85
+ * Check if a route contains a workSession composition
86
+ * Returns the sessionId if found, null otherwise
87
+ */
88
+ export const getWorkSessionIdFromRoute = (route: string): string | null => {
89
+ return extractWorkSessionId(route)
90
+ }
30
91
 
31
92
  export const parseComposedToCompositionSlots = (
32
93
  composed: ComposedRouteState,
@@ -37,7 +98,9 @@ export const parseComposedToCompositionSlots = (
37
98
  ([slotValue, paramsArray, ...slots]) => ({
38
99
  slotName: (slots[0] as string) || defaultSlotName,
39
100
  slotValue: slotValue as string,
40
- slotParams: (paramsArray as Record<string, string>[])[0] || {},
101
+ slotParams: coerceParams(
102
+ (paramsArray as Record<string, string>[])[0] || {},
103
+ ),
41
104
  }),
42
105
  )
43
106
 
@@ -75,3 +138,45 @@ export const transformToOriginalState = (
75
138
  // TODO: care about anchor
76
139
  return { map, anchor: null }
77
140
  }
141
+ export const findInteractionLinkByPathname = (
142
+ pathname: string,
143
+ ): InteractionLink | null => {
144
+ const interactionLinks = $store.getState("interactionLinks")
145
+ const composition =
146
+ $store.selectors.compositions.getCompositionByPath(pathname)
147
+
148
+ if (!composition) {
149
+ return null
150
+ }
151
+
152
+ // Find interactionLink that references this composition
153
+ const found = Object.values(interactionLinks).find(
154
+ (link: InteractionLink) => link.composition === composition._id,
155
+ )
156
+
157
+ return found || null
158
+ }
159
+ /**
160
+ * Get active compositions from current route
161
+ */
162
+ export const getActiveCompositionsFromRoute = (
163
+ fullPath: string,
164
+ ): Composition[] => {
165
+ const compositionSlots = parseRouteToCompositionSlots(
166
+ fullPath,
167
+ undefined,
168
+ true,
169
+ )
170
+ const compositions: Composition[] = []
171
+
172
+ compositionSlots.forEach((slot) => {
173
+ const composition = $store.selectors.compositions.getCompositionByPath(
174
+ slot.slotValue,
175
+ )
176
+ if (composition) {
177
+ compositions.push(composition)
178
+ }
179
+ })
180
+
181
+ return compositions
182
+ }
@@ -31,6 +31,8 @@ export type WidgetConf = {
31
31
  }
32
32
  }
33
33
 
34
+ export type OnAllWidgetsReady = (callback: () => void) => void
35
+
34
36
  export type ConfigurableParameters = Record<
35
37
  string,
36
38
  EditorSettings & { defaultValue: any }
@@ -1,2 +1,3 @@
1
1
  export type * from "./src/core/types"
2
2
  export type * from "./src/orchestrator/types"
3
+ export type * from "./src/core/store/types"