sh3-core 0.5.2 → 0.5.5
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.
Potentially problematic release.
This version of sh3-core might be problematic. Click here for more details.
- package/dist/Shell.svelte +6 -3
- package/dist/admin/AuthSettingsView.svelte +105 -0
- package/dist/admin/AuthSettingsView.svelte.d.ts +3 -0
- package/dist/admin/SystemView.svelte +73 -0
- package/dist/admin/SystemView.svelte.d.ts +3 -0
- package/dist/admin/UsersView.svelte +189 -0
- package/dist/admin/UsersView.svelte.d.ts +3 -0
- package/dist/admin/adminApp.d.ts +7 -0
- package/dist/admin/adminApp.js +24 -0
- package/dist/admin/adminShard.svelte.d.ts +4 -0
- package/dist/admin/adminShard.svelte.js +52 -0
- package/dist/api.d.ts +2 -1
- package/dist/api.js +1 -1
- package/dist/apps/lifecycle.d.ts +6 -1
- package/dist/apps/lifecycle.js +28 -4
- package/dist/apps/registry.svelte.d.ts +5 -2
- package/dist/apps/registry.svelte.js +6 -7
- package/dist/apps/types.d.ts +13 -0
- package/dist/auth/GuestBanner.svelte +144 -0
- package/dist/auth/GuestBanner.svelte.d.ts +3 -0
- package/dist/auth/SignInWall.svelte +213 -0
- package/dist/auth/SignInWall.svelte.d.ts +8 -0
- package/dist/auth/auth.svelte.d.ts +42 -31
- package/dist/auth/auth.svelte.js +106 -89
- package/dist/auth/index.d.ts +2 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/types.d.ts +41 -0
- package/dist/auth/types.js +6 -0
- package/dist/build.js +70 -16
- package/dist/createShell.d.ts +2 -2
- package/dist/createShell.js +78 -33
- package/dist/diagnostic/DiagnosticPromptModal.svelte +1 -1
- package/dist/host-entry.d.ts +2 -1
- package/dist/host-entry.js +2 -2
- package/dist/host.d.ts +0 -2
- package/dist/host.js +11 -25
- package/dist/layout/DragPreview.svelte +1 -1
- package/dist/overlays/ModalFrame.svelte +1 -1
- package/dist/overlays/PopupFrame.svelte +1 -1
- package/dist/overlays/ToastItem.svelte +1 -1
- package/dist/primitives/TabbedPanel.svelte +1 -1
- package/dist/registry/installer.js +0 -2
- package/dist/shards/activate.svelte.d.ts +13 -6
- package/dist/shards/activate.svelte.js +19 -8
- package/dist/shards/types.d.ts +11 -0
- package/dist/shell-shard/ShellHome.svelte +32 -118
- package/dist/store/InstalledView.svelte +7 -7
- package/dist/store/StoreView.svelte +16 -16
- package/dist/store/storeApp.js +1 -1
- package/dist/store/storeShard.svelte.js +5 -4
- package/dist/tokens.css +14 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/auth/auth.svelte.js
CHANGED
|
@@ -1,127 +1,144 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client-side
|
|
2
|
+
* Client-side auth — session-based identity for SH3.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The boot flow (createShell) calls initFromBoot() with the server's
|
|
5
|
+
* boot config. After that, login/logout call the server and update
|
|
6
|
+
* the reactive state. isAdmin/isGuest/isAuthenticated are reactive
|
|
7
|
+
* getters consumed by shell components.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* remains usable without admin access when the server is slow or offline.
|
|
10
|
-
*
|
|
11
|
-
* OS analogy: sudo / elevated permissions, not web login.
|
|
12
|
-
*
|
|
13
|
-
* .svelte.ts because it uses $state for reactive admin status.
|
|
9
|
+
* .svelte.ts because it uses $state for reactive auth status.
|
|
14
10
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
let admin = $state(false);
|
|
21
|
-
/** Server base URL, set once during initAuth. */
|
|
11
|
+
/** Reactive auth state. */
|
|
12
|
+
let currentUser = $state(null);
|
|
13
|
+
let currentSession = $state(null);
|
|
14
|
+
let guest = $state(false);
|
|
15
|
+
/** Server base URL, set during boot. */
|
|
22
16
|
let serverUrl = '';
|
|
23
17
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
18
|
+
* Initialize auth from boot config. Called once by createShell()
|
|
19
|
+
* after fetching /api/boot.
|
|
26
20
|
*/
|
|
27
|
-
|
|
21
|
+
export function initFromBoot(url, config) {
|
|
22
|
+
serverUrl = url;
|
|
23
|
+
currentUser = config.user;
|
|
24
|
+
currentSession = config.session;
|
|
25
|
+
guest = !config.session && !config.user;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Log in with username + password. On success, updates reactive state.
|
|
29
|
+
* Returns { ok: true } or { ok: false, error: string }.
|
|
30
|
+
*/
|
|
31
|
+
export async function login(username, password) {
|
|
28
32
|
try {
|
|
29
|
-
const
|
|
33
|
+
const res = await fetch(`${serverUrl}/api/auth/login`, {
|
|
30
34
|
method: 'POST',
|
|
31
|
-
headers: {
|
|
32
|
-
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
credentials: 'include',
|
|
37
|
+
body: JSON.stringify({ username, password }),
|
|
33
38
|
});
|
|
34
|
-
if (!
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const body = await res.json().catch(() => ({}));
|
|
41
|
+
return { ok: false, error: body.error || 'Login failed' };
|
|
42
|
+
}
|
|
43
|
+
const body = await res.json();
|
|
44
|
+
currentUser = body.user;
|
|
45
|
+
currentSession = body.session;
|
|
46
|
+
guest = false;
|
|
47
|
+
return { ok: true };
|
|
38
48
|
}
|
|
39
49
|
catch (_a) {
|
|
40
|
-
return false;
|
|
50
|
+
return { ok: false, error: 'Network error' };
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* If a key is stored from a previous session, verifies it against the
|
|
47
|
-
* server with a 3-second timeout. If verification fails (key revoked,
|
|
48
|
-
* server down, timeout), the shell boots without admin access — the
|
|
49
|
-
* stored key is cleared only on explicit rejection (401), not on
|
|
50
|
-
* network failure (so a temporary outage doesn't force re-entry).
|
|
51
|
-
*
|
|
52
|
-
* @param url - Server base URL ('' for same-origin).
|
|
54
|
+
* Register a new account (when self-registration is enabled).
|
|
55
|
+
* On success, auto-logs in and updates reactive state.
|
|
53
56
|
*/
|
|
54
|
-
export async function
|
|
55
|
-
serverUrl = url;
|
|
56
|
-
const stored = state.user.apiKey;
|
|
57
|
-
if (!stored)
|
|
58
|
-
return;
|
|
59
|
-
const controller = new AbortController();
|
|
60
|
-
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
57
|
+
export async function register(username, password, displayName) {
|
|
61
58
|
try {
|
|
62
|
-
const
|
|
59
|
+
const res = await fetch(`${serverUrl}/api/auth/register`, {
|
|
63
60
|
method: 'POST',
|
|
64
|
-
headers: {
|
|
65
|
-
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
credentials: 'include',
|
|
63
|
+
body: JSON.stringify({ username, password, displayName }),
|
|
66
64
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (body.valid === true) {
|
|
71
|
-
admin = true;
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// Server explicitly rejected the key — clear it.
|
|
76
|
-
if (response.status === 401 || response.status === 403) {
|
|
77
|
-
state.user.apiKey = null;
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const body = await res.json().catch(() => ({}));
|
|
67
|
+
return { ok: false, error: body.error || 'Registration failed' };
|
|
78
68
|
}
|
|
79
|
-
|
|
69
|
+
const body = await res.json();
|
|
70
|
+
currentUser = body.user;
|
|
71
|
+
currentSession = body.session;
|
|
72
|
+
guest = false;
|
|
73
|
+
return { ok: true };
|
|
80
74
|
}
|
|
81
75
|
catch (_a) {
|
|
82
|
-
|
|
83
|
-
// Network error or timeout: keep the key, boot unelevated.
|
|
84
|
-
// User can re-elevate manually once connectivity returns.
|
|
76
|
+
return { ok: false, error: 'Network error' };
|
|
85
77
|
}
|
|
86
78
|
}
|
|
87
79
|
/**
|
|
88
|
-
*
|
|
89
|
-
* before storing. Returns true on success, false if the key is invalid.
|
|
80
|
+
* Log out — clear session on server and client.
|
|
90
81
|
*/
|
|
91
|
-
export async function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
82
|
+
export async function logout() {
|
|
83
|
+
try {
|
|
84
|
+
await fetch(`${serverUrl}/api/auth/logout`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
credentials: 'include',
|
|
87
|
+
});
|
|
96
88
|
}
|
|
97
|
-
|
|
89
|
+
catch (_a) {
|
|
90
|
+
// Best effort
|
|
91
|
+
}
|
|
92
|
+
currentUser = null;
|
|
93
|
+
currentSession = null;
|
|
94
|
+
guest = true;
|
|
98
95
|
}
|
|
99
96
|
/**
|
|
100
|
-
*
|
|
97
|
+
* Mark this session as local-owner — auto-elevate to admin without
|
|
98
|
+
* server verification. Called by the host in Tauri / dev environments.
|
|
101
99
|
*/
|
|
102
|
-
export function
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
export function setLocalOwner() {
|
|
101
|
+
currentUser = {
|
|
102
|
+
id: 'local',
|
|
103
|
+
username: 'local',
|
|
104
|
+
displayName: 'Local Owner',
|
|
105
|
+
role: 'admin',
|
|
106
|
+
createdAt: '',
|
|
107
|
+
updatedAt: '',
|
|
108
|
+
};
|
|
109
|
+
currentSession = {
|
|
110
|
+
token: 'local',
|
|
111
|
+
userId: 'local',
|
|
112
|
+
role: 'admin',
|
|
113
|
+
expiresAt: Infinity,
|
|
114
|
+
};
|
|
115
|
+
guest = false;
|
|
105
116
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Reactive getter — true when the user has elevated to admin mode.
|
|
108
|
-
*/
|
|
117
|
+
/** Reactive — true when the user has admin role. */
|
|
109
118
|
export function isAdmin() {
|
|
110
|
-
return admin;
|
|
119
|
+
return (currentSession === null || currentSession === void 0 ? void 0 : currentSession.role) === 'admin';
|
|
111
120
|
}
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*/
|
|
117
|
-
export function
|
|
118
|
-
|
|
121
|
+
/** Reactive — true when the user has a valid session. */
|
|
122
|
+
export function isAuthenticated() {
|
|
123
|
+
return currentSession !== null;
|
|
124
|
+
}
|
|
125
|
+
/** Reactive — true when browsing without a session. */
|
|
126
|
+
export function isGuest() {
|
|
127
|
+
return guest;
|
|
128
|
+
}
|
|
129
|
+
/** Get the current user (reactive). */
|
|
130
|
+
export function getUser() {
|
|
131
|
+
return currentUser;
|
|
132
|
+
}
|
|
133
|
+
/** Get the current session (reactive). */
|
|
134
|
+
export function getSession() {
|
|
135
|
+
return currentSession;
|
|
119
136
|
}
|
|
120
137
|
/**
|
|
121
|
-
* Build an Authorization header value for authenticated fetch calls
|
|
122
|
-
*
|
|
138
|
+
* Build an Authorization header value for authenticated fetch calls
|
|
139
|
+
* that need explicit headers (e.g. non-cookie contexts).
|
|
140
|
+
* Returns null if not authenticated.
|
|
123
141
|
*/
|
|
124
142
|
export function getAuthHeader() {
|
|
125
|
-
|
|
126
|
-
return key ? `Bearer ${key}` : null;
|
|
143
|
+
return currentSession ? `Bearer ${currentSession.token}` : null;
|
|
127
144
|
}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { initFromBoot, login, logout, register, isAdmin, isAuthenticated, isGuest, getUser, getSession, getAuthHeader, setLocalOwner, } from './auth.svelte';
|
|
2
|
+
export type { AuthUser, AuthSession, BootConfig, GlobalSettings } from './types';
|
package/dist/auth/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { initFromBoot, login, logout, register, isAdmin, isAuthenticated, isGuest, getUser, getSession, getAuthHeader, setLocalOwner, } from './auth.svelte';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared auth types — used by both client and server.
|
|
3
|
+
* Kept in sh3-core so the server can import them at build time
|
|
4
|
+
* and the client uses them directly.
|
|
5
|
+
*/
|
|
6
|
+
/** Public user shape (never includes passwordHash). */
|
|
7
|
+
export interface AuthUser {
|
|
8
|
+
id: string;
|
|
9
|
+
username: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
role: 'admin' | 'user';
|
|
12
|
+
createdAt: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
/** Session shape returned to the client. */
|
|
16
|
+
export interface AuthSession {
|
|
17
|
+
token: string;
|
|
18
|
+
userId: string;
|
|
19
|
+
role: 'admin' | 'user';
|
|
20
|
+
expiresAt: number;
|
|
21
|
+
}
|
|
22
|
+
/** Response from GET /api/boot. */
|
|
23
|
+
export interface BootConfig {
|
|
24
|
+
auth: {
|
|
25
|
+
required: boolean;
|
|
26
|
+
guestAllowed: boolean;
|
|
27
|
+
selfRegistration: boolean;
|
|
28
|
+
};
|
|
29
|
+
user: AuthUser | null;
|
|
30
|
+
session: AuthSession | null;
|
|
31
|
+
tenantId: string;
|
|
32
|
+
}
|
|
33
|
+
/** Global settings shape. */
|
|
34
|
+
export interface GlobalSettings {
|
|
35
|
+
auth: {
|
|
36
|
+
required: boolean;
|
|
37
|
+
guestAllowed: boolean;
|
|
38
|
+
sessionTTL: number;
|
|
39
|
+
selfRegistration: boolean;
|
|
40
|
+
};
|
|
41
|
+
}
|
package/dist/build.js
CHANGED
|
@@ -112,7 +112,7 @@ export function sh3Artifact(options = {}) {
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
async closeBundle() {
|
|
115
|
-
var _a, _b, _c, _d;
|
|
115
|
+
var _a, _b, _c, _d, _e;
|
|
116
116
|
if (!entryFileName)
|
|
117
117
|
return;
|
|
118
118
|
const clientSrc = join(outDir, entryFileName);
|
|
@@ -122,7 +122,7 @@ export function sh3Artifact(options = {}) {
|
|
|
122
122
|
try {
|
|
123
123
|
source = readFileSync(clientSrc, 'utf-8');
|
|
124
124
|
}
|
|
125
|
-
catch (
|
|
125
|
+
catch (_f) {
|
|
126
126
|
console.warn('[sh3-artifact] Could not read entry chunk:', clientSrc);
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
@@ -131,17 +131,71 @@ export function sh3Artifact(options = {}) {
|
|
|
131
131
|
writeFileSync(clientDest, source);
|
|
132
132
|
unlinkSync(clientSrc);
|
|
133
133
|
}
|
|
134
|
-
// --- Extract manifest fields
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const label = extract(/\blabel\s*:\s*["']([^"']+)["']/);
|
|
141
|
-
const version = extract(/\bversion\s*:\s*["']([^"']+)["']/);
|
|
142
|
-
const hasShard = /\bviews\s*:\s*\[/.test(source);
|
|
134
|
+
// --- Extract manifest fields from App or Shard block ---
|
|
135
|
+
//
|
|
136
|
+
// The bundle may contain both an App manifest (has `requiredShards`)
|
|
137
|
+
// and a Shard manifest (has `views: [`). We extract id/label/version
|
|
138
|
+
// from the App block first, then fall back to the Shard block.
|
|
139
|
+
// If neither block exists, the build fails with a clear error.
|
|
143
140
|
const hasApp = /\brequiredShards\s*:\s*\[/.test(source);
|
|
141
|
+
const hasShard = /\bviews\s*:\s*\[/.test(source);
|
|
142
|
+
if (!hasApp && !hasShard) {
|
|
143
|
+
throw new Error('[sh3-artifact] Could not find an App manifest (requiredShards) or Shard manifest (views) in the entry chunk. '
|
|
144
|
+
+ 'Ensure the entry exports an App or Shard object.');
|
|
145
|
+
}
|
|
144
146
|
const type = hasShard && hasApp ? 'combo' : hasApp ? 'app' : 'shard';
|
|
147
|
+
/**
|
|
148
|
+
* Extract id, label, and version from the manifest object block that
|
|
149
|
+
* contains `anchor`. Walks backwards from the anchor to find the
|
|
150
|
+
* opening `{`, then forward to find the matching `}`, and extracts
|
|
151
|
+
* fields from within that slice.
|
|
152
|
+
*/
|
|
153
|
+
function extractFromBlock(anchor) {
|
|
154
|
+
const anchorMatch = anchor.exec(source);
|
|
155
|
+
if (!anchorMatch)
|
|
156
|
+
return null;
|
|
157
|
+
// Walk backwards from the anchor to find the enclosing `{`.
|
|
158
|
+
let depth = 0;
|
|
159
|
+
let blockStart = anchorMatch.index;
|
|
160
|
+
for (let i = anchorMatch.index - 1; i >= 0; i--) {
|
|
161
|
+
if (source[i] === '}')
|
|
162
|
+
depth++;
|
|
163
|
+
if (source[i] === '{') {
|
|
164
|
+
if (depth === 0) {
|
|
165
|
+
blockStart = i;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
depth--;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Walk forwards from the anchor to find the matching `}`.
|
|
172
|
+
depth = 0;
|
|
173
|
+
let blockEnd = source.length;
|
|
174
|
+
for (let i = blockStart; i < source.length; i++) {
|
|
175
|
+
if (source[i] === '{')
|
|
176
|
+
depth++;
|
|
177
|
+
if (source[i] === '}') {
|
|
178
|
+
depth--;
|
|
179
|
+
if (depth === 0) {
|
|
180
|
+
blockEnd = i + 1;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const block = source.slice(blockStart, blockEnd);
|
|
186
|
+
const get = (pattern) => {
|
|
187
|
+
const m = block.match(pattern);
|
|
188
|
+
return m ? m[1] : '';
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
id: get(/\bid\s*:\s*["']([^"']+)["']/),
|
|
192
|
+
label: get(/\blabel\s*:\s*["']([^"']+)["']/),
|
|
193
|
+
version: get(/\bversion\s*:\s*["']([^"']+)["']/),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// App first, then Shard.
|
|
197
|
+
const extracted = (_a = extractFromBlock(/\brequiredShards\s*:\s*\[/)) !== null && _a !== void 0 ? _a : extractFromBlock(/\bviews\s*:\s*\[/);
|
|
198
|
+
const { id, label, version } = extracted;
|
|
145
199
|
// --- Optional server bundle ---
|
|
146
200
|
let hasServer = false;
|
|
147
201
|
if (options.serverEntry && existsSync(options.serverEntry)) {
|
|
@@ -156,13 +210,13 @@ export function sh3Artifact(options = {}) {
|
|
|
156
210
|
pkgDescription = typeof pkg.description === 'string' ? pkg.description : undefined;
|
|
157
211
|
pkgAuthor = typeof pkg.author === 'string'
|
|
158
212
|
? pkg.author
|
|
159
|
-
: typeof ((
|
|
213
|
+
: typeof ((_b = pkg.author) === null || _b === void 0 ? void 0 : _b.name) === 'string' ? pkg.author.name : undefined;
|
|
160
214
|
}
|
|
161
|
-
catch ( /* no package.json or unreadable */
|
|
215
|
+
catch ( /* no package.json or unreadable */_g) { /* no package.json or unreadable */ }
|
|
162
216
|
// --- Write manifest.json ---
|
|
163
|
-
const overrides = (
|
|
164
|
-
const finalDescription = (
|
|
165
|
-
const finalAuthor = (
|
|
217
|
+
const overrides = (_c = options.manifest) !== null && _c !== void 0 ? _c : {};
|
|
218
|
+
const finalDescription = (_d = overrides.description) !== null && _d !== void 0 ? _d : pkgDescription;
|
|
219
|
+
const finalAuthor = (_e = overrides.author) !== null && _e !== void 0 ? _e : pkgAuthor;
|
|
166
220
|
if (!finalDescription) {
|
|
167
221
|
throw new Error('[sh3-artifact] Missing "description". Add it to package.json or pass it via sh3Artifact({ manifest: { description } }).');
|
|
168
222
|
}
|
package/dist/createShell.d.ts
CHANGED
|
@@ -2,8 +2,6 @@ import type { Shard, App } from './index';
|
|
|
2
2
|
export interface ShellConfig {
|
|
3
3
|
/** Framework shard IDs to exclude (all included by default) */
|
|
4
4
|
excludeShards?: string[];
|
|
5
|
-
/** Framework app IDs to exclude (all included by default) */
|
|
6
|
-
excludeApps?: string[];
|
|
7
5
|
/** Additional shards to register */
|
|
8
6
|
shards?: Shard[];
|
|
9
7
|
/** Additional apps to register */
|
|
@@ -20,5 +18,7 @@ export interface ShellConfig {
|
|
|
20
18
|
}>;
|
|
21
19
|
/** Mount target — CSS selector or element (defaults to '#app') */
|
|
22
20
|
target?: string | HTMLElement;
|
|
21
|
+
/** Server base URL ('' for same-origin) */
|
|
22
|
+
serverUrl?: string;
|
|
23
23
|
}
|
|
24
24
|
export declare function createShell(config?: ShellConfig): Promise<void>;
|
package/dist/createShell.js
CHANGED
|
@@ -3,20 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Consumers call this from their own main.ts instead of manually
|
|
5
5
|
* importing registerShard / registerApp / bootstrap / Shell. The
|
|
6
|
-
* factory handles platform detection,
|
|
7
|
-
* mounting in the correct order.
|
|
6
|
+
* factory handles platform detection, boot config, auth gating,
|
|
7
|
+
* registration, bootstrap, and mounting in the correct order.
|
|
8
8
|
*/
|
|
9
|
-
import { mount } from 'svelte';
|
|
9
|
+
import { mount, unmount } from 'svelte';
|
|
10
10
|
import { Shell } from './index';
|
|
11
11
|
import { registerShard, registerApp, bootstrap, __setBackend, setLocalOwner, } from './host';
|
|
12
12
|
import { resolvePlatform } from './platform/index';
|
|
13
13
|
import { hydrateTokenOverrides } from './theme';
|
|
14
14
|
import { __setEnvServerUrl } from './env/index';
|
|
15
|
+
import { __setTenantId } from './documents/config';
|
|
16
|
+
import { initFromBoot } from './auth/index';
|
|
17
|
+
import SignInWall from './auth/SignInWall.svelte';
|
|
15
18
|
export async function createShell(config) {
|
|
16
|
-
var _a, _b;
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
// auto-elevate to admin.
|
|
19
|
+
var _a, _b, _c;
|
|
20
|
+
const sUrl = (_a = config === null || config === void 0 ? void 0 : config.serverUrl) !== null && _a !== void 0 ? _a : '';
|
|
21
|
+
// 1. Platform detection
|
|
20
22
|
const platform = await resolvePlatform();
|
|
21
23
|
if (platform.backends) {
|
|
22
24
|
__setBackend('workspace', platform.backends.workspace);
|
|
@@ -25,15 +27,52 @@ export async function createShell(config) {
|
|
|
25
27
|
if (platform.localOwner) {
|
|
26
28
|
setLocalOwner();
|
|
27
29
|
}
|
|
28
|
-
|
|
29
|
-
// this if it knows a specific URL, but this ensures env() works for
|
|
30
|
-
// same-origin deployments without explicit configuration.
|
|
31
|
-
__setEnvServerUrl('');
|
|
32
|
-
// 1c. Apply persisted theme token overrides before any component mounts,
|
|
33
|
-
// so the first frame renders with the user's chosen theme.
|
|
30
|
+
__setEnvServerUrl(sUrl);
|
|
34
31
|
hydrateTokenOverrides();
|
|
35
|
-
//
|
|
36
|
-
|
|
32
|
+
// 2. Resolve mount target early (needed for both sign-in wall and shell)
|
|
33
|
+
const target = typeof (config === null || config === void 0 ? void 0 : config.target) === 'string'
|
|
34
|
+
? document.querySelector(config.target)
|
|
35
|
+
: (_b = config === null || config === void 0 ? void 0 : config.target) !== null && _b !== void 0 ? _b : document.getElementById('app');
|
|
36
|
+
if (!target) {
|
|
37
|
+
throw new Error('SH3: mount target not found');
|
|
38
|
+
}
|
|
39
|
+
// 3. Fetch boot config (skip for local-owner platforms like Tauri/dev)
|
|
40
|
+
let bootConfig = null;
|
|
41
|
+
if (!platform.localOwner) {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(`${sUrl}/api/boot`, { credentials: 'include' });
|
|
44
|
+
if (res.ok) {
|
|
45
|
+
bootConfig = await res.json();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (_d) {
|
|
49
|
+
// Server unreachable — boot without auth (offline mode)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 4. Auth decision point
|
|
53
|
+
if (platform.localOwner) {
|
|
54
|
+
// Local-owner (Tauri/dev): no auth, no sign-in, tenant is 'local'.
|
|
55
|
+
// setLocalOwner() already called above — admin is assumed.
|
|
56
|
+
__setTenantId('local');
|
|
57
|
+
}
|
|
58
|
+
else if (bootConfig) {
|
|
59
|
+
initFromBoot(sUrl, bootConfig);
|
|
60
|
+
__setTenantId(bootConfig.tenantId);
|
|
61
|
+
const { auth, session } = bootConfig;
|
|
62
|
+
// Hard gate: no session, auth required, no guest allowed → sign-in wall
|
|
63
|
+
if (!session && auth.required && !auth.guestAllowed) {
|
|
64
|
+
await showSignInWall(target, sUrl, bootConfig);
|
|
65
|
+
// After successful sign-in, re-fetch boot config
|
|
66
|
+
const res = await fetch(`${sUrl}/api/boot`, { credentials: 'include' });
|
|
67
|
+
if (res.ok) {
|
|
68
|
+
bootConfig = await res.json();
|
|
69
|
+
initFromBoot(sUrl, bootConfig);
|
|
70
|
+
__setTenantId(bootConfig.tenantId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 5. Load server-discovered packages
|
|
75
|
+
if ((_c = config === null || config === void 0 ? void 0 : config.discoveredPackages) === null || _c === void 0 ? void 0 : _c.length) {
|
|
37
76
|
const { loadBundleModule } = await import('./registry/loader');
|
|
38
77
|
for (const pkg of config.discoveredPackages) {
|
|
39
78
|
try {
|
|
@@ -55,33 +94,39 @@ export async function createShell(config) {
|
|
|
55
94
|
}
|
|
56
95
|
}
|
|
57
96
|
}
|
|
58
|
-
//
|
|
59
|
-
// bootstrap() so they appear in registeredShards, but framework
|
|
60
|
-
// shards activate first (insertion-order guarantee in bootstrap).
|
|
97
|
+
// 6. Register consumer-provided shards and apps
|
|
61
98
|
if (config === null || config === void 0 ? void 0 : config.shards) {
|
|
62
|
-
for (const shard of config.shards)
|
|
99
|
+
for (const shard of config.shards)
|
|
63
100
|
registerShard(shard);
|
|
64
|
-
}
|
|
65
101
|
}
|
|
66
102
|
if (config === null || config === void 0 ? void 0 : config.apps) {
|
|
67
|
-
for (const app of config.apps)
|
|
103
|
+
for (const app of config.apps)
|
|
68
104
|
registerApp(app);
|
|
69
|
-
}
|
|
70
105
|
}
|
|
71
|
-
//
|
|
72
|
-
// filtered by the exclude lists.
|
|
106
|
+
// 7. Bootstrap
|
|
73
107
|
const bootstrapConfig = {};
|
|
74
108
|
if (config === null || config === void 0 ? void 0 : config.excludeShards)
|
|
75
109
|
bootstrapConfig.excludeShards = config.excludeShards;
|
|
76
|
-
if (config === null || config === void 0 ? void 0 : config.excludeApps)
|
|
77
|
-
bootstrapConfig.excludeApps = config.excludeApps;
|
|
78
110
|
await bootstrap(bootstrapConfig);
|
|
79
|
-
//
|
|
80
|
-
const target = typeof (config === null || config === void 0 ? void 0 : config.target) === 'string'
|
|
81
|
-
? document.querySelector(config.target)
|
|
82
|
-
: (_b = config === null || config === void 0 ? void 0 : config.target) !== null && _b !== void 0 ? _b : document.getElementById('app');
|
|
83
|
-
if (!target) {
|
|
84
|
-
throw new Error('SH3: mount target not found');
|
|
85
|
-
}
|
|
111
|
+
// 8. Mount the shell
|
|
86
112
|
mount(Shell, { target });
|
|
87
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Show the sign-in wall and wait until the user authenticates.
|
|
116
|
+
* Returns a promise that resolves after successful login.
|
|
117
|
+
*/
|
|
118
|
+
function showSignInWall(target, serverUrl, bootConfig) {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const instance = mount(SignInWall, {
|
|
121
|
+
target,
|
|
122
|
+
props: {
|
|
123
|
+
serverUrl,
|
|
124
|
+
selfRegistration: bootConfig.auth.selfRegistration,
|
|
125
|
+
onSuccess: () => {
|
|
126
|
+
unmount(instance);
|
|
127
|
+
resolve();
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
background: var(--shell-accent-muted);
|
|
74
74
|
color: var(--shell-fg);
|
|
75
75
|
border: 1px solid var(--shell-border-strong);
|
|
76
|
-
border-radius:
|
|
76
|
+
border-radius: var(--shell-radius-sm);
|
|
77
77
|
cursor: pointer;
|
|
78
78
|
}
|
|
79
79
|
button:hover { background: var(--shell-accent); }
|
package/dist/host-entry.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { HttpDocumentBackend } from './documents/http-backend';
|
|
|
7
7
|
export { __setEnvServerUrl } from './env/index';
|
|
8
8
|
export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledPackages, } from './registry/index';
|
|
9
9
|
export type { InstalledPackage, InstallResult, PackageMeta } from './registry/types';
|
|
10
|
-
export {
|
|
10
|
+
export { initFromBoot, login, logout, register, setLocalOwner as setLocalOwnerAuth } from './auth/index';
|
|
11
|
+
export type { AuthUser, AuthSession, BootConfig, GlobalSettings } from './auth/types';
|
|
11
12
|
export { createShell } from './createShell';
|
|
12
13
|
export type { ShellConfig } from './createShell';
|
package/dist/host-entry.js
CHANGED
|
@@ -11,7 +11,7 @@ export { HttpDocumentBackend } from './documents/http-backend';
|
|
|
11
11
|
export { __setEnvServerUrl } from './env/index';
|
|
12
12
|
// Install API (host-only).
|
|
13
13
|
export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledPackages, } from './registry/index';
|
|
14
|
-
//
|
|
15
|
-
export {
|
|
14
|
+
// Auth (host-only — session lifecycle, boot initialization).
|
|
15
|
+
export { initFromBoot, login, logout, register, setLocalOwner as setLocalOwnerAuth } from './auth/index';
|
|
16
16
|
// Shell boot factory.
|
|
17
17
|
export { createShell } from './createShell';
|
package/dist/host.d.ts
CHANGED
|
@@ -10,8 +10,6 @@ export { registerApp };
|
|
|
10
10
|
export interface BootstrapConfig {
|
|
11
11
|
/** Framework shard IDs to skip registration for */
|
|
12
12
|
excludeShards?: string[];
|
|
13
|
-
/** Framework app IDs to skip registration for */
|
|
14
|
-
excludeApps?: string[];
|
|
15
13
|
}
|
|
16
14
|
export declare function bootstrap(config?: BootstrapConfig): Promise<void>;
|
|
17
15
|
export { installPackage, listInstalledPackages } from './registry/installer';
|