@jskit-ai/users-web 0.1.4

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 (97) hide show
  1. package/package.descriptor.mjs +507 -0
  2. package/package.json +31 -0
  3. package/src/client/components/ConsoleSettingsClientElement.vue +24 -0
  4. package/src/client/components/MembersAdminClientElement.vue +404 -0
  5. package/src/client/components/ProfileClientElement.vue +242 -0
  6. package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
  7. package/src/client/components/UsersShellMenuLinkItem.vue +140 -0
  8. package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +87 -0
  9. package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
  10. package/src/client/components/UsersWorkspacePermissionMenuItem.vue +90 -0
  11. package/src/client/components/UsersWorkspaceSelector.vue +237 -0
  12. package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
  13. package/src/client/components/UsersWorkspaceToolsWidget.vue +23 -0
  14. package/src/client/components/WorkspaceMembersClientElement.vue +663 -0
  15. package/src/client/components/WorkspaceSettingsClientElement.vue +230 -0
  16. package/src/client/components/WorkspacesClientElement.vue +514 -0
  17. package/src/client/composables/accountSettingsAvatarUploadRuntime.js +241 -0
  18. package/src/client/composables/accountSettingsInvitesRuntime.js +88 -0
  19. package/src/client/composables/accountSettingsRuntimeConstants.js +77 -0
  20. package/src/client/composables/accountSettingsRuntimeHelpers.js +75 -0
  21. package/src/client/composables/errorMessageHelpers.js +66 -0
  22. package/src/client/composables/internal/useOperationScope.js +144 -0
  23. package/src/client/composables/modelStateHelpers.js +49 -0
  24. package/src/client/composables/operationUiHelpers.js +121 -0
  25. package/src/client/composables/operationValidationHelpers.js +52 -0
  26. package/src/client/composables/refValueHelpers.js +19 -0
  27. package/src/client/composables/scopeHelpers.js +145 -0
  28. package/src/client/composables/useAccess.js +109 -0
  29. package/src/client/composables/useAccountSettingsRuntime.js +533 -0
  30. package/src/client/composables/useAddEdit.js +135 -0
  31. package/src/client/composables/useAddEditCore.js +137 -0
  32. package/src/client/composables/useBootstrapQuery.js +52 -0
  33. package/src/client/composables/useCommand.js +112 -0
  34. package/src/client/composables/useCommandCore.js +130 -0
  35. package/src/client/composables/useEndpointResource.js +104 -0
  36. package/src/client/composables/useFieldErrorBag.js +61 -0
  37. package/src/client/composables/useList.js +85 -0
  38. package/src/client/composables/useListCore.js +65 -0
  39. package/src/client/composables/usePagedCollection.js +125 -0
  40. package/src/client/composables/usePaths.js +108 -0
  41. package/src/client/composables/useRealtimeQueryInvalidation.js +105 -0
  42. package/src/client/composables/useScopeRuntime.js +107 -0
  43. package/src/client/composables/useSurfaceRouteContext.js +31 -0
  44. package/src/client/composables/useUiFeedback.js +96 -0
  45. package/src/client/composables/useView.js +89 -0
  46. package/src/client/composables/useViewCore.js +104 -0
  47. package/src/client/composables/useWorkspaceRouteContext.js +28 -0
  48. package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
  49. package/src/client/index.js +7 -0
  50. package/src/client/lib/bootstrap.js +95 -0
  51. package/src/client/lib/httpClient.js +67 -0
  52. package/src/client/lib/menuIcons.js +192 -0
  53. package/src/client/lib/permissions.js +34 -0
  54. package/src/client/lib/profileSurfaceMenuLinks.js +142 -0
  55. package/src/client/lib/surfaceAccessPolicy.js +350 -0
  56. package/src/client/lib/theme.js +99 -0
  57. package/src/client/lib/workspaceLinkResolver.js +207 -0
  58. package/src/client/lib/workspaceSurfaceContext.js +82 -0
  59. package/src/client/lib/workspaceSurfacePaths.js +163 -0
  60. package/src/client/providers/UsersWebClientProvider.js +85 -0
  61. package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
  62. package/src/client/runtime/bootstrapPlacementRuntime.js +413 -0
  63. package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +32 -0
  64. package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +157 -0
  65. package/src/client/support/contractGuards.js +34 -0
  66. package/src/client/support/realtimeWorkspace.js +12 -0
  67. package/src/client/support/runtimeNormalization.js +27 -0
  68. package/src/client/support/workspaceQueryKeys.js +15 -0
  69. package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +162 -0
  70. package/templates/src/components/WorkspaceNotFoundCard.vue +33 -0
  71. package/templates/src/components/account/settings/AccountSettingsClientElement.vue +153 -0
  72. package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +77 -0
  73. package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +55 -0
  74. package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +125 -0
  75. package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +94 -0
  76. package/templates/src/composables/useWorkspaceNotFoundState.js +48 -0
  77. package/templates/src/pages/account/index.vue +17 -0
  78. package/templates/src/pages/admin/members/index.vue +7 -0
  79. package/templates/src/pages/admin/workspace/settings/index.vue +16 -0
  80. package/templates/src/pages/console/settings/index.vue +16 -0
  81. package/templates/src/surfaces/admin/index.vue +29 -0
  82. package/templates/src/surfaces/admin/root.vue +20 -0
  83. package/templates/src/surfaces/app/index.vue +27 -0
  84. package/templates/src/surfaces/app/root.vue +20 -0
  85. package/test/bootstrap.test.js +38 -0
  86. package/test/bootstrapPlacementRuntime.test.js +991 -0
  87. package/test/errorMessageHelpers.test.js +28 -0
  88. package/test/exportsContract.test.js +39 -0
  89. package/test/menuIcons.test.js +33 -0
  90. package/test/permissions.test.js +35 -0
  91. package/test/profileSurfaceMenuLinks.test.js +207 -0
  92. package/test/refValueHelpers.test.js +14 -0
  93. package/test/scopeHelpers.test.js +57 -0
  94. package/test/surfaceAccessPolicy.test.js +129 -0
  95. package/test/theme.test.js +95 -0
  96. package/test/workspaceLinkResolver.test.js +61 -0
  97. package/test/workspaceSurfacePaths.test.js +39 -0
@@ -0,0 +1,413 @@
1
+ import {
2
+ CLIENT_MODULE_ROUTER_TOKEN,
3
+ CLIENT_MODULE_VUE_APP_TOKEN
4
+ } from "@jskit-ai/kernel/client/moduleBootstrap";
5
+ import { WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN } from "@jskit-ai/shell-web/client/placement";
6
+ import { REALTIME_SOCKET_CLIENT_TOKEN } from "@jskit-ai/realtime/client/tokens";
7
+ import { USERS_BOOTSTRAP_CHANGED_EVENT } from "@jskit-ai/users-core/shared/events/usersEvents";
8
+ import {
9
+ findWorkspaceBySlug,
10
+ normalizeWorkspaceList,
11
+ resolvePlacementUserFromBootstrapPayload
12
+ } from "../lib/bootstrap.js";
13
+ import { normalizePermissionList } from "../lib/permissions.js";
14
+ import {
15
+ resolveBootstrapThemeName,
16
+ resolveVuetifyThemeController,
17
+ setVuetifyThemeName
18
+ } from "../lib/theme.js";
19
+ import { createBootstrapPlacementRouteGuards } from "./bootstrapPlacementRouteGuards.js";
20
+ import {
21
+ BOOTSTRAP_PLACEMENT_SOURCE,
22
+ USERS_WEB_BOOTSTRAP_PLACEMENT_RUNTIME_TOKEN,
23
+ WORKSPACE_BOOTSTRAP_STATUS_ERROR,
24
+ WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN,
25
+ WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND,
26
+ WORKSPACE_BOOTSTRAP_STATUS_RESOLVED,
27
+ WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED
28
+ } from "./bootstrapPlacementRuntimeConstants.js";
29
+ import {
30
+ countPendingInvites,
31
+ createProviderLogger,
32
+ fetchBootstrapPayload,
33
+ normalizeWorkspaceBootstrapStatus,
34
+ normalizeWorkspaceSlugKey,
35
+ resolveAuthSignature,
36
+ resolveRequestedWorkspaceBootstrapStatus,
37
+ resolveRouteState
38
+ } from "./bootstrapPlacementRuntimeHelpers.js";
39
+ import { resolveErrorStatusCode } from "../support/runtimeNormalization.js";
40
+
41
+ function createBootstrapPlacementRuntime({ app, logger = null, fetchBootstrap = fetchBootstrapPayload } = {}) {
42
+ if (!app || typeof app.has !== "function" || typeof app.make !== "function") {
43
+ throw new Error("createBootstrapPlacementRuntime requires application has()/make().");
44
+ }
45
+ if (!app.has(WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN)) {
46
+ throw new Error("createBootstrapPlacementRuntime requires shell-web placement runtime.");
47
+ }
48
+ if (typeof fetchBootstrap !== "function") {
49
+ throw new TypeError("createBootstrapPlacementRuntime requires fetchBootstrap(workspaceSlug).");
50
+ }
51
+
52
+ const runtimeLogger = logger || createProviderLogger(app);
53
+ const placementRuntime = app.make(WEB_PLACEMENT_RUNTIME_CLIENT_TOKEN);
54
+ const router = app.has(CLIENT_MODULE_ROUTER_TOKEN) ? app.make(CLIENT_MODULE_ROUTER_TOKEN) : null;
55
+ let vuetifyThemeController = resolveVuetifyThemeController(
56
+ app.has(CLIENT_MODULE_VUE_APP_TOKEN) ? app.make(CLIENT_MODULE_VUE_APP_TOKEN) : null
57
+ );
58
+ const socket = app.has(REALTIME_SOCKET_CLIENT_TOKEN) ? app.make(REALTIME_SOCKET_CLIENT_TOKEN) : null;
59
+ const cleanup = [];
60
+ let refreshQueue = Promise.resolve();
61
+ let shutdownRequested = false;
62
+ let authSignature = resolveAuthSignature(placementRuntime.getContext());
63
+ let lastRouteWorkspaceSlug = resolveRouteState(placementRuntime, router).workspaceSlug;
64
+ const workspaceBootstrapStatusBySlug = new Map();
65
+ const workspaceBootstrapStatusListeners = new Set();
66
+ const root = typeof globalThis === "object" && globalThis ? globalThis : null;
67
+
68
+ const routeGuards = createBootstrapPlacementRouteGuards({
69
+ placementRuntime,
70
+ router,
71
+ root,
72
+ getWorkspaceBootstrapStatus: (workspaceSlug) => getWorkspaceBootstrapStatus(workspaceSlug)
73
+ });
74
+
75
+ cleanup.push(() => {
76
+ routeGuards.shutdown();
77
+ });
78
+
79
+ function setWorkspaceBootstrapStatus(workspaceSlug = "", status = "", source = BOOTSTRAP_PLACEMENT_SOURCE) {
80
+ const workspaceSlugKey = normalizeWorkspaceSlugKey(workspaceSlug);
81
+ const normalizedStatus = normalizeWorkspaceBootstrapStatus(status);
82
+ if (!workspaceSlugKey || !normalizedStatus) {
83
+ return;
84
+ }
85
+
86
+ const previousStatus = workspaceBootstrapStatusBySlug.get(workspaceSlugKey) || "";
87
+ workspaceBootstrapStatusBySlug.set(workspaceSlugKey, normalizedStatus);
88
+ if (previousStatus === normalizedStatus) {
89
+ return;
90
+ }
91
+
92
+ placementRuntime.setContext(
93
+ {
94
+ workspaceBootstrapStatuses: Object.freeze(Object.fromEntries(workspaceBootstrapStatusBySlug))
95
+ },
96
+ {
97
+ source
98
+ }
99
+ );
100
+
101
+ const payload = Object.freeze({
102
+ workspaceSlug: workspaceSlugKey,
103
+ status: normalizedStatus,
104
+ source: String(source || BOOTSTRAP_PLACEMENT_SOURCE).trim() || BOOTSTRAP_PLACEMENT_SOURCE
105
+ });
106
+ for (const listener of workspaceBootstrapStatusListeners) {
107
+ try {
108
+ listener(payload);
109
+ } catch {}
110
+ }
111
+ routeGuards.enforceWorkspaceRouteForStatusUpdate(payload);
112
+ }
113
+
114
+ function getWorkspaceBootstrapStatus(workspaceSlug = "") {
115
+ const workspaceSlugKey = normalizeWorkspaceSlugKey(workspaceSlug);
116
+ if (!workspaceSlugKey) {
117
+ return "";
118
+ }
119
+ return String(workspaceBootstrapStatusBySlug.get(workspaceSlugKey) || "");
120
+ }
121
+
122
+ function subscribeWorkspaceBootstrapStatus(listener) {
123
+ if (typeof listener !== "function") {
124
+ return () => {};
125
+ }
126
+ workspaceBootstrapStatusListeners.add(listener);
127
+ return () => {
128
+ workspaceBootstrapStatusListeners.delete(listener);
129
+ };
130
+ }
131
+
132
+ function writePlacementContext(payload = {}, state = {}, source = BOOTSTRAP_PLACEMENT_SOURCE) {
133
+ const availableWorkspaces = normalizeWorkspaceList(payload?.workspaces);
134
+ const currentWorkspace = findWorkspaceBySlug(availableWorkspaces, state.workspaceSlug);
135
+ const permissions = normalizePermissionList(payload?.permissions);
136
+ const user = resolvePlacementUserFromBootstrapPayload(payload, state.context?.user);
137
+ const workspaceInvitesEnabled = payload?.app?.features?.workspaceInvites === true;
138
+ const pendingInvitesCount = workspaceInvitesEnabled ? countPendingInvites(payload?.pendingInvites) : 0;
139
+
140
+ placementRuntime.setContext(
141
+ {
142
+ workspace: currentWorkspace,
143
+ workspaces: availableWorkspaces,
144
+ permissions,
145
+ user,
146
+ surfaceAccess: payload?.surfaceAccess && typeof payload.surfaceAccess === "object" ? payload.surfaceAccess : {},
147
+ pendingInvitesCount,
148
+ workspaceInvitesEnabled
149
+ },
150
+ {
151
+ source
152
+ }
153
+ );
154
+ routeGuards.enforceSurfaceAccessForCurrentRoute();
155
+ }
156
+
157
+ function clearPlacementContext(source = BOOTSTRAP_PLACEMENT_SOURCE) {
158
+ placementRuntime.setContext(
159
+ {
160
+ workspace: null,
161
+ workspaces: [],
162
+ permissions: [],
163
+ user: null,
164
+ surfaceAccess: {},
165
+ pendingInvitesCount: 0,
166
+ workspaceInvitesEnabled: false
167
+ },
168
+ {
169
+ source
170
+ }
171
+ );
172
+ routeGuards.enforceSurfaceAccessForCurrentRoute();
173
+ }
174
+
175
+ function getVuetifyThemeController() {
176
+ if (vuetifyThemeController) {
177
+ return vuetifyThemeController;
178
+ }
179
+ if (!app.has(CLIENT_MODULE_VUE_APP_TOKEN)) {
180
+ return null;
181
+ }
182
+
183
+ vuetifyThemeController = resolveVuetifyThemeController(app.make(CLIENT_MODULE_VUE_APP_TOKEN));
184
+ return vuetifyThemeController;
185
+ }
186
+
187
+ function applyThemeFromBootstrapPayload(payload = {}, reason = "manual") {
188
+ const themeController = getVuetifyThemeController();
189
+ if (!themeController) {
190
+ return;
191
+ }
192
+
193
+ try {
194
+ const nextThemeName = resolveBootstrapThemeName(payload);
195
+ setVuetifyThemeName(themeController, nextThemeName);
196
+ } catch (error) {
197
+ runtimeLogger.warn(
198
+ {
199
+ reason,
200
+ error: String(error?.message || error || "unknown error")
201
+ },
202
+ "users-web bootstrap theme apply failed."
203
+ );
204
+ }
205
+ }
206
+
207
+ async function refresh(reason = "manual") {
208
+ if (shutdownRequested) {
209
+ return;
210
+ }
211
+
212
+ const stateAtStart = resolveRouteState(placementRuntime, router);
213
+ const source = `${BOOTSTRAP_PLACEMENT_SOURCE}.${String(reason || "manual").trim() || "manual"}`;
214
+ try {
215
+ const payload = await fetchBootstrap(stateAtStart.workspaceSlug);
216
+ const stateAtApply = resolveRouteState(placementRuntime, router);
217
+ if (stateAtStart.path !== stateAtApply.path || stateAtStart.workspaceSlug !== stateAtApply.workspaceSlug) {
218
+ return;
219
+ }
220
+
221
+ writePlacementContext(payload, stateAtStart, source);
222
+ applyThemeFromBootstrapPayload(payload, reason);
223
+ if (stateAtStart.workspaceSlug) {
224
+ const sessionAuthenticated = payload?.session?.authenticated === true;
225
+ if (!sessionAuthenticated) {
226
+ setWorkspaceBootstrapStatus(stateAtStart.workspaceSlug, WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED, source);
227
+ return;
228
+ }
229
+
230
+ const requestedWorkspaceStatus = resolveRequestedWorkspaceBootstrapStatus(payload, stateAtStart.workspaceSlug);
231
+ if (requestedWorkspaceStatus) {
232
+ setWorkspaceBootstrapStatus(stateAtStart.workspaceSlug, requestedWorkspaceStatus, source);
233
+ return;
234
+ }
235
+
236
+ const availableWorkspaces = normalizeWorkspaceList(payload?.workspaces);
237
+ const currentWorkspace = findWorkspaceBySlug(availableWorkspaces, stateAtStart.workspaceSlug);
238
+ setWorkspaceBootstrapStatus(
239
+ stateAtStart.workspaceSlug,
240
+ currentWorkspace ? WORKSPACE_BOOTSTRAP_STATUS_RESOLVED : WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN,
241
+ source
242
+ );
243
+ }
244
+ } catch (error) {
245
+ const statusCode = resolveErrorStatusCode(error);
246
+ const stateAtApply = resolveRouteState(placementRuntime, router);
247
+ const sameWorkspaceRoute =
248
+ stateAtStart.path === stateAtApply.path && stateAtStart.workspaceSlug === stateAtApply.workspaceSlug;
249
+
250
+ if (statusCode === 401) {
251
+ if (stateAtStart.workspaceSlug) {
252
+ setWorkspaceBootstrapStatus(stateAtStart.workspaceSlug, WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED, source);
253
+ }
254
+ clearPlacementContext(source);
255
+ applyThemeFromBootstrapPayload(
256
+ {
257
+ session: {
258
+ authenticated: false
259
+ }
260
+ },
261
+ reason
262
+ );
263
+ return;
264
+ }
265
+ if (statusCode === 403) {
266
+ if (stateAtStart.workspaceSlug) {
267
+ setWorkspaceBootstrapStatus(stateAtStart.workspaceSlug, WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN, source);
268
+ }
269
+ if (sameWorkspaceRoute) {
270
+ clearPlacementContext(source);
271
+ }
272
+ return;
273
+ }
274
+ if (statusCode === 404) {
275
+ if (stateAtStart.workspaceSlug) {
276
+ setWorkspaceBootstrapStatus(stateAtStart.workspaceSlug, WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND, source);
277
+ }
278
+ if (sameWorkspaceRoute) {
279
+ clearPlacementContext(source);
280
+ }
281
+ return;
282
+ }
283
+
284
+ if (stateAtStart.workspaceSlug) {
285
+ setWorkspaceBootstrapStatus(stateAtStart.workspaceSlug, WORKSPACE_BOOTSTRAP_STATUS_ERROR, source);
286
+ }
287
+
288
+ runtimeLogger.warn(
289
+ {
290
+ reason,
291
+ error: String(error?.message || error || "unknown error")
292
+ },
293
+ "users-web bootstrap placement refresh failed."
294
+ );
295
+ }
296
+ }
297
+
298
+ function queueRefresh(reason = "manual") {
299
+ refreshQueue = refreshQueue
300
+ .then(() => refresh(reason))
301
+ .catch((error) => {
302
+ runtimeLogger.warn(
303
+ {
304
+ reason,
305
+ error: String(error?.message || error || "unknown error")
306
+ },
307
+ "users-web bootstrap placement queued refresh failed."
308
+ );
309
+ });
310
+ return refreshQueue;
311
+ }
312
+
313
+ async function initialize() {
314
+ routeGuards.installWorkspaceGuardEvaluator();
315
+
316
+ const contextAtInit = placementRuntime.getContext();
317
+ if (contextAtInit?.auth?.authenticated !== true) {
318
+ applyThemeFromBootstrapPayload({
319
+ session: {
320
+ authenticated: false
321
+ }
322
+ }, "init");
323
+ }
324
+
325
+ if (typeof placementRuntime.subscribe === "function") {
326
+ const unsubscribePlacement = placementRuntime.subscribe((event = {}) => {
327
+ if (event.type !== "context.updated") {
328
+ return;
329
+ }
330
+
331
+ const nextContext = placementRuntime.getContext();
332
+ const nextSignature = resolveAuthSignature(nextContext);
333
+ if (nextSignature === authSignature) {
334
+ return;
335
+ }
336
+
337
+ authSignature = nextSignature;
338
+ if (nextContext?.auth?.authenticated !== true) {
339
+ applyThemeFromBootstrapPayload({
340
+ session: {
341
+ authenticated: false
342
+ }
343
+ }, "auth");
344
+ }
345
+ void queueRefresh("auth");
346
+ });
347
+ cleanup.push(() => {
348
+ if (typeof unsubscribePlacement === "function") {
349
+ unsubscribePlacement();
350
+ }
351
+ });
352
+ }
353
+
354
+ await queueRefresh("init");
355
+
356
+ if (router && typeof router.afterEach === "function") {
357
+ const removeAfterEach = router.afterEach(() => {
358
+ const nextWorkspaceSlug = resolveRouteState(placementRuntime, router).workspaceSlug;
359
+ if (nextWorkspaceSlug === lastRouteWorkspaceSlug) {
360
+ return;
361
+ }
362
+ lastRouteWorkspaceSlug = nextWorkspaceSlug;
363
+ void queueRefresh("route");
364
+ });
365
+ cleanup.push(() => {
366
+ if (typeof removeAfterEach === "function") {
367
+ removeAfterEach();
368
+ }
369
+ });
370
+ }
371
+
372
+ if (socket && typeof socket.on === "function") {
373
+ const handleBootstrapChanged = () => {
374
+ void queueRefresh("realtime");
375
+ };
376
+ socket.on(USERS_BOOTSTRAP_CHANGED_EVENT, handleBootstrapChanged);
377
+ cleanup.push(() => {
378
+ if (typeof socket.off === "function") {
379
+ socket.off(USERS_BOOTSTRAP_CHANGED_EVENT, handleBootstrapChanged);
380
+ }
381
+ });
382
+ }
383
+ }
384
+
385
+ function shutdown() {
386
+ shutdownRequested = true;
387
+ for (const release of cleanup.splice(0, cleanup.length)) {
388
+ if (typeof release === "function") {
389
+ try {
390
+ release();
391
+ } catch {}
392
+ }
393
+ }
394
+ }
395
+
396
+ return Object.freeze({
397
+ initialize,
398
+ shutdown,
399
+ refresh: queueRefresh,
400
+ getWorkspaceBootstrapStatus,
401
+ subscribeWorkspaceBootstrapStatus
402
+ });
403
+ }
404
+
405
+ export {
406
+ USERS_WEB_BOOTSTRAP_PLACEMENT_RUNTIME_TOKEN,
407
+ WORKSPACE_BOOTSTRAP_STATUS_RESOLVED,
408
+ WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND,
409
+ WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN,
410
+ WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED,
411
+ WORKSPACE_BOOTSTRAP_STATUS_ERROR,
412
+ createBootstrapPlacementRuntime
413
+ };
@@ -0,0 +1,32 @@
1
+ const USERS_WEB_BOOTSTRAP_PLACEMENT_RUNTIME_TOKEN = "users.web.bootstrap-placement.runtime";
2
+ const BOOTSTRAP_PLACEMENT_SOURCE = "users-web.bootstrap-placement";
3
+ const WORKSPACE_BOOTSTRAP_STATUS_RESOLVED = "resolved";
4
+ const WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND = "not_found";
5
+ const WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN = "forbidden";
6
+ const WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED = "unauthenticated";
7
+ const WORKSPACE_BOOTSTRAP_STATUS_ERROR = "error";
8
+ const SHELL_GUARD_EVALUATOR_KEY = "__JSKIT_WEB_SHELL_GUARD_EVALUATOR__";
9
+ const WORKSPACE_NOT_FOUND_GUARD_REASON = "workspace-not-found";
10
+ const WORKSPACE_FORBIDDEN_GUARD_REASON = "workspace-forbidden";
11
+
12
+ const WORKSPACE_BOOTSTRAP_STATUSES = new Set([
13
+ WORKSPACE_BOOTSTRAP_STATUS_RESOLVED,
14
+ WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND,
15
+ WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN,
16
+ WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED,
17
+ WORKSPACE_BOOTSTRAP_STATUS_ERROR
18
+ ]);
19
+
20
+ export {
21
+ BOOTSTRAP_PLACEMENT_SOURCE,
22
+ SHELL_GUARD_EVALUATOR_KEY,
23
+ USERS_WEB_BOOTSTRAP_PLACEMENT_RUNTIME_TOKEN,
24
+ WORKSPACE_BOOTSTRAP_STATUSES,
25
+ WORKSPACE_BOOTSTRAP_STATUS_ERROR,
26
+ WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN,
27
+ WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND,
28
+ WORKSPACE_BOOTSTRAP_STATUS_RESOLVED,
29
+ WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED,
30
+ WORKSPACE_FORBIDDEN_GUARD_REASON,
31
+ WORKSPACE_NOT_FOUND_GUARD_REASON
32
+ };
@@ -0,0 +1,157 @@
1
+ import {
2
+ resolveRuntimePathname,
3
+ resolveSurfaceIdFromPlacementPathname
4
+ } from "@jskit-ai/shell-web/client/placement";
5
+ import { parseWorkspacePathname } from "@jskit-ai/users-core/shared/support/workspacePathModel";
6
+ import { extractWorkspaceSlugFromSurfacePathname } from "../lib/workspaceSurfacePaths.js";
7
+ import { usersWebHttpClient } from "../lib/httpClient.js";
8
+ import { buildBootstrapApiPath } from "../lib/bootstrap.js";
9
+ import {
10
+ normalizeWorkspaceBootstrapStatusValue
11
+ } from "../support/runtimeNormalization.js";
12
+ import { WORKSPACE_BOOTSTRAP_STATUSES } from "./bootstrapPlacementRuntimeConstants.js";
13
+
14
+ function createProviderLogger(app) {
15
+ return Object.freeze({
16
+ warn: (...args) => {
17
+ if (app && typeof app.warn === "function") {
18
+ app.warn(...args);
19
+ return;
20
+ }
21
+ console.warn(...args);
22
+ }
23
+ });
24
+ }
25
+
26
+ function resolveRouteState(placementRuntime, router) {
27
+ const context = placementRuntime.getContext();
28
+ const path = resolveRuntimePathname(router?.currentRoute?.value?.path);
29
+ const surfaceId = String(resolveSurfaceIdFromPlacementPathname(context, path) || "")
30
+ .trim()
31
+ .toLowerCase();
32
+ const workspaceSlugFromSurface = String(extractWorkspaceSlugFromSurfacePathname(context, surfaceId, path) || "").trim();
33
+ const workspaceSlug =
34
+ workspaceSlugFromSurface ||
35
+ String(parseWorkspacePathname(path)?.workspaceSlug || "").trim();
36
+
37
+ return Object.freeze({
38
+ context,
39
+ path,
40
+ workspaceSlug
41
+ });
42
+ }
43
+
44
+ function normalizeSearch(search = "") {
45
+ const normalizedSearch = String(search || "").trim();
46
+ if (!normalizedSearch) {
47
+ return "";
48
+ }
49
+ return normalizedSearch.startsWith("?") ? normalizedSearch : `?${normalizedSearch}`;
50
+ }
51
+
52
+ function resolveSearchFromFullPath(fullPath = "") {
53
+ const normalizedFullPath = String(fullPath || "").trim();
54
+ const queryStart = normalizedFullPath.indexOf("?");
55
+ if (queryStart < 0) {
56
+ return "";
57
+ }
58
+ const hashStart = normalizedFullPath.indexOf("#", queryStart);
59
+ const search = hashStart < 0 ? normalizedFullPath.slice(queryStart) : normalizedFullPath.slice(queryStart, hashStart);
60
+ return normalizeSearch(search);
61
+ }
62
+
63
+ function normalizeGuardPathname(pathname = "/") {
64
+ return resolveRuntimePathname(pathname);
65
+ }
66
+
67
+ function isGuardDenied(outcome) {
68
+ if (outcome === false) {
69
+ return true;
70
+ }
71
+ if (outcome == null || outcome === true || typeof outcome !== "object" || Array.isArray(outcome)) {
72
+ return false;
73
+ }
74
+ return outcome.allow === false;
75
+ }
76
+
77
+ function normalizeWorkspaceSlugKey(workspaceSlug = "") {
78
+ return String(workspaceSlug || "")
79
+ .trim()
80
+ .toLowerCase();
81
+ }
82
+
83
+ function normalizeWorkspaceBootstrapStatus(status = "") {
84
+ return normalizeWorkspaceBootstrapStatusValue(status, WORKSPACE_BOOTSTRAP_STATUSES);
85
+ }
86
+
87
+ function resolveRequestedWorkspaceBootstrapStatus(payload = {}, workspaceSlug = "") {
88
+ const normalizedWorkspaceSlug = normalizeWorkspaceSlugKey(workspaceSlug);
89
+ if (!normalizedWorkspaceSlug) {
90
+ return "";
91
+ }
92
+
93
+ const requestedWorkspace =
94
+ payload?.requestedWorkspace && typeof payload.requestedWorkspace === "object" ? payload.requestedWorkspace : null;
95
+ if (!requestedWorkspace) {
96
+ return "";
97
+ }
98
+
99
+ const requestedWorkspaceSlug = normalizeWorkspaceSlugKey(requestedWorkspace.slug);
100
+ if (!requestedWorkspaceSlug || requestedWorkspaceSlug !== normalizedWorkspaceSlug) {
101
+ return "";
102
+ }
103
+
104
+ return normalizeWorkspaceBootstrapStatus(requestedWorkspace.status);
105
+ }
106
+
107
+ function resolveAuthSignature(context = {}) {
108
+ const auth = context?.auth && typeof context.auth === "object" ? context.auth : {};
109
+ const authenticated = auth.authenticated === true ? "1" : "0";
110
+ const oauthDefaultProvider = String(auth.oauthDefaultProvider || "")
111
+ .trim()
112
+ .toLowerCase();
113
+ const oauthProviders = Array.isArray(auth.oauthProviders)
114
+ ? auth.oauthProviders
115
+ .map((entry) => String(entry?.id || "").trim().toLowerCase())
116
+ .filter(Boolean)
117
+ .join(",")
118
+ : "";
119
+
120
+ return `${authenticated}|${oauthDefaultProvider}|${oauthProviders}`;
121
+ }
122
+
123
+ function countPendingInvites(entries = []) {
124
+ if (!Array.isArray(entries)) {
125
+ return 0;
126
+ }
127
+
128
+ let total = 0;
129
+ for (const entry of entries) {
130
+ if (!entry || typeof entry !== "object") {
131
+ continue;
132
+ }
133
+ total += 1;
134
+ }
135
+ return total;
136
+ }
137
+
138
+ async function fetchBootstrapPayload(workspaceSlug = "") {
139
+ return usersWebHttpClient.request(buildBootstrapApiPath(workspaceSlug), {
140
+ method: "GET"
141
+ });
142
+ }
143
+
144
+ export {
145
+ countPendingInvites,
146
+ createProviderLogger,
147
+ fetchBootstrapPayload,
148
+ isGuardDenied,
149
+ normalizeGuardPathname,
150
+ normalizeSearch,
151
+ normalizeWorkspaceBootstrapStatus,
152
+ normalizeWorkspaceSlugKey,
153
+ resolveAuthSignature,
154
+ resolveRequestedWorkspaceBootstrapStatus,
155
+ resolveRouteState,
156
+ resolveSearchFromFullPath
157
+ };
@@ -0,0 +1,34 @@
1
+ import { unref } from "vue";
2
+
3
+ function requireRecord(value, label = "value", owner = "Contract") {
4
+ const resolved = unref(value);
5
+ if (!resolved || typeof resolved !== "object" || Array.isArray(resolved)) {
6
+ throw new TypeError(`${owner} expects ${label} to be an object.`);
7
+ }
8
+
9
+ return resolved;
10
+ }
11
+
12
+ function requireBoolean(value, label = "value", owner = "Contract") {
13
+ const resolved = unref(value);
14
+ if (typeof resolved !== "boolean") {
15
+ throw new TypeError(`${owner} expects ${label} to be a boolean.`);
16
+ }
17
+
18
+ return resolved;
19
+ }
20
+
21
+ function requireFunction(value, label = "value", owner = "Contract") {
22
+ const resolved = unref(value);
23
+ if (typeof resolved !== "function") {
24
+ throw new TypeError(`${owner} expects ${label} to be a function.`);
25
+ }
26
+
27
+ return resolved;
28
+ }
29
+
30
+ export {
31
+ requireRecord,
32
+ requireBoolean,
33
+ requireFunction
34
+ };
@@ -0,0 +1,12 @@
1
+ function matchesCurrentWorkspaceEvent(payload = {}, workspaceSlug = "") {
2
+ const payloadWorkspaceSlug = String(payload?.workspaceSlug || "").trim();
3
+ if (!payloadWorkspaceSlug) {
4
+ return true;
5
+ }
6
+
7
+ return payloadWorkspaceSlug === String(workspaceSlug || "").trim();
8
+ }
9
+
10
+ export {
11
+ matchesCurrentWorkspaceEvent
12
+ };
@@ -0,0 +1,27 @@
1
+ function normalizeRecord(value) {
2
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3
+ return {};
4
+ }
5
+ return value;
6
+ }
7
+
8
+ function resolveErrorStatusCode(error) {
9
+ const statusCode = Number(error?.statusCode || error?.status || 0);
10
+ return Number.isInteger(statusCode) && statusCode > 0 ? statusCode : 0;
11
+ }
12
+
13
+ function normalizeWorkspaceBootstrapStatusValue(status = "", allowedStatuses = null) {
14
+ const normalizedStatus = String(status || "")
15
+ .trim()
16
+ .toLowerCase();
17
+ if (!normalizedStatus || !(allowedStatuses instanceof Set)) {
18
+ return "";
19
+ }
20
+ return allowedStatuses.has(normalizedStatus) ? normalizedStatus : "";
21
+ }
22
+
23
+ export {
24
+ normalizeRecord,
25
+ normalizeWorkspaceBootstrapStatusValue,
26
+ resolveErrorStatusCode
27
+ };
@@ -0,0 +1,15 @@
1
+ import { normalizeQueryToken } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function buildWorkspaceQueryKey(kind = "", surfaceId = "", workspaceSlug = "") {
4
+ return [
5
+ "users-web",
6
+ "workspace",
7
+ String(kind || ""),
8
+ normalizeQueryToken(surfaceId),
9
+ normalizeQueryToken(workspaceSlug)
10
+ ];
11
+ }
12
+
13
+ export {
14
+ buildWorkspaceQueryKey
15
+ };