@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.
- package/package.descriptor.mjs +507 -0
- package/package.json +31 -0
- package/src/client/components/ConsoleSettingsClientElement.vue +24 -0
- package/src/client/components/MembersAdminClientElement.vue +404 -0
- package/src/client/components/ProfileClientElement.vue +242 -0
- package/src/client/components/UsersProfileSurfaceSwitchMenuItem.vue +39 -0
- package/src/client/components/UsersShellMenuLinkItem.vue +140 -0
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +87 -0
- package/src/client/components/UsersWorkspaceMembersMenuItem.vue +36 -0
- package/src/client/components/UsersWorkspacePermissionMenuItem.vue +90 -0
- package/src/client/components/UsersWorkspaceSelector.vue +237 -0
- package/src/client/components/UsersWorkspaceSettingsMenuItem.vue +39 -0
- package/src/client/components/UsersWorkspaceToolsWidget.vue +23 -0
- package/src/client/components/WorkspaceMembersClientElement.vue +663 -0
- package/src/client/components/WorkspaceSettingsClientElement.vue +230 -0
- package/src/client/components/WorkspacesClientElement.vue +514 -0
- package/src/client/composables/accountSettingsAvatarUploadRuntime.js +241 -0
- package/src/client/composables/accountSettingsInvitesRuntime.js +88 -0
- package/src/client/composables/accountSettingsRuntimeConstants.js +77 -0
- package/src/client/composables/accountSettingsRuntimeHelpers.js +75 -0
- package/src/client/composables/errorMessageHelpers.js +66 -0
- package/src/client/composables/internal/useOperationScope.js +144 -0
- package/src/client/composables/modelStateHelpers.js +49 -0
- package/src/client/composables/operationUiHelpers.js +121 -0
- package/src/client/composables/operationValidationHelpers.js +52 -0
- package/src/client/composables/refValueHelpers.js +19 -0
- package/src/client/composables/scopeHelpers.js +145 -0
- package/src/client/composables/useAccess.js +109 -0
- package/src/client/composables/useAccountSettingsRuntime.js +533 -0
- package/src/client/composables/useAddEdit.js +135 -0
- package/src/client/composables/useAddEditCore.js +137 -0
- package/src/client/composables/useBootstrapQuery.js +52 -0
- package/src/client/composables/useCommand.js +112 -0
- package/src/client/composables/useCommandCore.js +130 -0
- package/src/client/composables/useEndpointResource.js +104 -0
- package/src/client/composables/useFieldErrorBag.js +61 -0
- package/src/client/composables/useList.js +85 -0
- package/src/client/composables/useListCore.js +65 -0
- package/src/client/composables/usePagedCollection.js +125 -0
- package/src/client/composables/usePaths.js +108 -0
- package/src/client/composables/useRealtimeQueryInvalidation.js +105 -0
- package/src/client/composables/useScopeRuntime.js +107 -0
- package/src/client/composables/useSurfaceRouteContext.js +31 -0
- package/src/client/composables/useUiFeedback.js +96 -0
- package/src/client/composables/useView.js +89 -0
- package/src/client/composables/useViewCore.js +104 -0
- package/src/client/composables/useWorkspaceRouteContext.js +28 -0
- package/src/client/composables/useWorkspaceSurfaceId.js +43 -0
- package/src/client/index.js +7 -0
- package/src/client/lib/bootstrap.js +95 -0
- package/src/client/lib/httpClient.js +67 -0
- package/src/client/lib/menuIcons.js +192 -0
- package/src/client/lib/permissions.js +34 -0
- package/src/client/lib/profileSurfaceMenuLinks.js +142 -0
- package/src/client/lib/surfaceAccessPolicy.js +350 -0
- package/src/client/lib/theme.js +99 -0
- package/src/client/lib/workspaceLinkResolver.js +207 -0
- package/src/client/lib/workspaceSurfaceContext.js +82 -0
- package/src/client/lib/workspaceSurfacePaths.js +163 -0
- package/src/client/providers/UsersWebClientProvider.js +85 -0
- package/src/client/runtime/bootstrapPlacementRouteGuards.js +371 -0
- package/src/client/runtime/bootstrapPlacementRuntime.js +413 -0
- package/src/client/runtime/bootstrapPlacementRuntimeConstants.js +32 -0
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +157 -0
- package/src/client/support/contractGuards.js +34 -0
- package/src/client/support/realtimeWorkspace.js +12 -0
- package/src/client/support/runtimeNormalization.js +27 -0
- package/src/client/support/workspaceQueryKeys.js +15 -0
- package/templates/packages/main/src/client/components/AccountPendingInvitesCue.vue +162 -0
- package/templates/src/components/WorkspaceNotFoundCard.vue +33 -0
- package/templates/src/components/account/settings/AccountSettingsClientElement.vue +153 -0
- package/templates/src/components/account/settings/AccountSettingsInvitesSection.vue +77 -0
- package/templates/src/components/account/settings/AccountSettingsNotificationsSection.vue +55 -0
- package/templates/src/components/account/settings/AccountSettingsPreferencesSection.vue +125 -0
- package/templates/src/components/account/settings/AccountSettingsProfileSection.vue +94 -0
- package/templates/src/composables/useWorkspaceNotFoundState.js +48 -0
- package/templates/src/pages/account/index.vue +17 -0
- package/templates/src/pages/admin/members/index.vue +7 -0
- package/templates/src/pages/admin/workspace/settings/index.vue +16 -0
- package/templates/src/pages/console/settings/index.vue +16 -0
- package/templates/src/surfaces/admin/index.vue +29 -0
- package/templates/src/surfaces/admin/root.vue +20 -0
- package/templates/src/surfaces/app/index.vue +27 -0
- package/templates/src/surfaces/app/root.vue +20 -0
- package/test/bootstrap.test.js +38 -0
- package/test/bootstrapPlacementRuntime.test.js +991 -0
- package/test/errorMessageHelpers.test.js +28 -0
- package/test/exportsContract.test.js +39 -0
- package/test/menuIcons.test.js +33 -0
- package/test/permissions.test.js +35 -0
- package/test/profileSurfaceMenuLinks.test.js +207 -0
- package/test/refValueHelpers.test.js +14 -0
- package/test/scopeHelpers.test.js +57 -0
- package/test/surfaceAccessPolicy.test.js +129 -0
- package/test/theme.test.js +95 -0
- package/test/workspaceLinkResolver.test.js +61 -0
- 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
|
+
};
|