sh3-core 0.6.0
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/README.md +9 -0
- package/dist/Shell.svelte +283 -0
- package/dist/Shell.svelte.d.ts +5 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +50 -0
- package/dist/app/admin/ApiKeysView.svelte +169 -0
- package/dist/app/admin/ApiKeysView.svelte.d.ts +3 -0
- package/dist/app/admin/AuthSettingsView.svelte +105 -0
- package/dist/app/admin/AuthSettingsView.svelte.d.ts +3 -0
- package/dist/app/admin/SystemView.svelte +73 -0
- package/dist/app/admin/SystemView.svelte.d.ts +3 -0
- package/dist/app/admin/UsersView.svelte +188 -0
- package/dist/app/admin/UsersView.svelte.d.ts +3 -0
- package/dist/app/admin/adminApp.d.ts +7 -0
- package/dist/app/admin/adminApp.js +25 -0
- package/dist/app/admin/adminShard.svelte.d.ts +4 -0
- package/dist/app/admin/adminShard.svelte.js +62 -0
- package/dist/app/store/InstalledView.svelte +246 -0
- package/dist/app/store/InstalledView.svelte.d.ts +3 -0
- package/dist/app/store/StoreView.svelte +522 -0
- package/dist/app/store/StoreView.svelte.d.ts +3 -0
- package/dist/app/store/storeApp.d.ts +10 -0
- package/dist/app/store/storeApp.js +26 -0
- package/dist/app/store/storeShard.svelte.d.ts +38 -0
- package/dist/app/store/storeShard.svelte.js +218 -0
- package/dist/apps/lifecycle.d.ts +42 -0
- package/dist/apps/lifecycle.js +184 -0
- package/dist/apps/registry.svelte.d.ts +40 -0
- package/dist/apps/registry.svelte.js +59 -0
- package/dist/apps/terminal/manifest.d.ts +8 -0
- package/dist/apps/terminal/manifest.js +13 -0
- package/dist/apps/terminal/terminal-app.d.ts +7 -0
- package/dist/apps/terminal/terminal-app.js +14 -0
- package/dist/apps/types.d.ts +93 -0
- package/dist/apps/types.js +10 -0
- package/dist/artifact.d.ts +32 -0
- package/dist/artifact.js +1 -0
- package/dist/assets/SH3.png +0 -0
- package/dist/assets/icons.svg +1126 -0
- package/dist/assets.d.ts +13 -0
- package/dist/auth/GuestBanner.svelte +134 -0
- package/dist/auth/GuestBanner.svelte.d.ts +3 -0
- package/dist/auth/SignInWall.svelte +203 -0
- package/dist/auth/SignInWall.svelte.d.ts +7 -0
- package/dist/auth/auth.svelte.d.ts +69 -0
- package/dist/auth/auth.svelte.js +165 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/types.d.ts +41 -0
- package/dist/auth/types.js +6 -0
- package/dist/build.d.ts +49 -0
- package/dist/build.js +236 -0
- package/dist/contract.d.ts +20 -0
- package/dist/contract.js +28 -0
- package/dist/createShell.d.ts +24 -0
- package/dist/createShell.js +131 -0
- package/dist/documents/backends.d.ts +17 -0
- package/dist/documents/backends.js +156 -0
- package/dist/documents/config.d.ts +7 -0
- package/dist/documents/config.js +27 -0
- package/dist/documents/handle.d.ts +6 -0
- package/dist/documents/handle.js +154 -0
- package/dist/documents/http-backend.d.ts +22 -0
- package/dist/documents/http-backend.js +78 -0
- package/dist/documents/index.d.ts +6 -0
- package/dist/documents/index.js +8 -0
- package/dist/documents/notifications.d.ts +9 -0
- package/dist/documents/notifications.js +39 -0
- package/dist/documents/types.d.ts +97 -0
- package/dist/documents/types.js +12 -0
- package/dist/env/client.d.ts +44 -0
- package/dist/env/client.js +106 -0
- package/dist/env/index.d.ts +2 -0
- package/dist/env/index.js +1 -0
- package/dist/env/types.d.ts +12 -0
- package/dist/env/types.js +8 -0
- package/dist/host-entry.d.ts +13 -0
- package/dist/host-entry.js +17 -0
- package/dist/host.d.ts +15 -0
- package/dist/host.js +86 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -0
- package/dist/layout/DragPreview.svelte +63 -0
- package/dist/layout/DragPreview.svelte.d.ts +3 -0
- package/dist/layout/LayoutRenderer.svelte +262 -0
- package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
- package/dist/layout/SlotContainer.svelte +140 -0
- package/dist/layout/SlotContainer.svelte.d.ts +8 -0
- package/dist/layout/SlotDropZone.svelte +122 -0
- package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
- package/dist/layout/drag.svelte.d.ts +45 -0
- package/dist/layout/drag.svelte.js +200 -0
- package/dist/layout/inspection.d.ts +72 -0
- package/dist/layout/inspection.js +209 -0
- package/dist/layout/ops.d.ts +100 -0
- package/dist/layout/ops.js +310 -0
- package/dist/layout/slotHostPool.svelte.d.ts +36 -0
- package/dist/layout/slotHostPool.svelte.js +229 -0
- package/dist/layout/store.svelte.d.ts +39 -0
- package/dist/layout/store.svelte.js +153 -0
- package/dist/layout/tree-walk.d.ts +15 -0
- package/dist/layout/tree-walk.js +33 -0
- package/dist/layout/types.d.ts +108 -0
- package/dist/layout/types.js +25 -0
- package/dist/migrations/shell-rename.d.ts +16 -0
- package/dist/migrations/shell-rename.js +48 -0
- package/dist/overlays/ModalFrame.svelte +87 -0
- package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
- package/dist/overlays/PopupFrame.svelte +85 -0
- package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
- package/dist/overlays/ToastItem.svelte +77 -0
- package/dist/overlays/ToastItem.svelte.d.ts +9 -0
- package/dist/overlays/focusTrap.d.ts +1 -0
- package/dist/overlays/focusTrap.js +64 -0
- package/dist/overlays/modal.d.ts +9 -0
- package/dist/overlays/modal.js +141 -0
- package/dist/overlays/popup.d.ts +9 -0
- package/dist/overlays/popup.js +108 -0
- package/dist/overlays/roots.d.ts +4 -0
- package/dist/overlays/roots.js +31 -0
- package/dist/overlays/toast.d.ts +6 -0
- package/dist/overlays/toast.js +93 -0
- package/dist/overlays/types.d.ts +31 -0
- package/dist/overlays/types.js +15 -0
- package/dist/platform/index.d.ts +10 -0
- package/dist/platform/index.js +33 -0
- package/dist/platform/tauri-backend.d.ts +15 -0
- package/dist/platform/tauri-backend.js +58 -0
- package/dist/primitives/.gitkeep +0 -0
- package/dist/primitives/ResizableSplitter.svelte +333 -0
- package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
- package/dist/primitives/TabbedPanel.svelte +305 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
- package/dist/primitives/base.css +42 -0
- package/dist/registry/client.d.ts +74 -0
- package/dist/registry/client.js +117 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.js +14 -0
- package/dist/registry/installer.d.ts +53 -0
- package/dist/registry/installer.js +168 -0
- package/dist/registry/integrity.d.ts +32 -0
- package/dist/registry/integrity.js +92 -0
- package/dist/registry/loader.d.ts +50 -0
- package/dist/registry/loader.js +145 -0
- package/dist/registry/schema.d.ts +47 -0
- package/dist/registry/schema.js +185 -0
- package/dist/registry/storage.d.ts +37 -0
- package/dist/registry/storage.js +101 -0
- package/dist/registry/types.d.ts +262 -0
- package/dist/registry/types.js +14 -0
- package/dist/server-shard/types.d.ts +67 -0
- package/dist/server-shard/types.js +13 -0
- package/dist/sh3core-shard/ShellHome.svelte +192 -0
- package/dist/sh3core-shard/ShellHome.svelte.d.ts +3 -0
- package/dist/sh3core-shard/ShellTitle.svelte +171 -0
- package/dist/sh3core-shard/ShellTitle.svelte.d.ts +3 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.d.ts +2 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +53 -0
- package/dist/shards/activate.svelte.d.ts +52 -0
- package/dist/shards/activate.svelte.js +186 -0
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +28 -0
- package/dist/shards/types.d.ts +207 -0
- package/dist/shards/types.js +20 -0
- package/dist/shell-shard/InputLine.svelte +133 -0
- package/dist/shell-shard/InputLine.svelte.d.ts +11 -0
- package/dist/shell-shard/ScrollbackView.svelte +47 -0
- package/dist/shell-shard/ScrollbackView.svelte.d.ts +7 -0
- package/dist/shell-shard/Terminal.svelte +122 -0
- package/dist/shell-shard/Terminal.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/PromptEntry.svelte +25 -0
- package/dist/shell-shard/entries/PromptEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/RichEntry.svelte +19 -0
- package/dist/shell-shard/entries/RichEntry.svelte.d.ts +8 -0
- package/dist/shell-shard/entries/StatusEntry.svelte +22 -0
- package/dist/shell-shard/entries/StatusEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/entries/TextEntry.svelte +25 -0
- package/dist/shell-shard/entries/TextEntry.svelte.d.ts +7 -0
- package/dist/shell-shard/manifest.d.ts +2 -0
- package/dist/shell-shard/manifest.js +11 -0
- package/dist/shell-shard/protocol.d.ts +90 -0
- package/dist/shell-shard/protocol.js +11 -0
- package/dist/shell-shard/registry.d.ts +69 -0
- package/dist/shell-shard/registry.js +47 -0
- package/dist/shell-shard/rich/AppCard.svelte +25 -0
- package/dist/shell-shard/rich/AppCard.svelte.d.ts +10 -0
- package/dist/shell-shard/rich/AppsTable.svelte +29 -0
- package/dist/shell-shard/rich/AppsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/EnvTable.svelte +27 -0
- package/dist/shell-shard/rich/EnvTable.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/HelpTable.svelte +29 -0
- package/dist/shell-shard/rich/HelpTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/HistoryList.svelte +37 -0
- package/dist/shell-shard/rich/HistoryList.svelte.d.ts +9 -0
- package/dist/shell-shard/rich/ShardsTable.svelte +28 -0
- package/dist/shell-shard/rich/ShardsTable.svelte.d.ts +12 -0
- package/dist/shell-shard/rich/ViewsTable.svelte +31 -0
- package/dist/shell-shard/rich/ViewsTable.svelte.d.ts +13 -0
- package/dist/shell-shard/rich/ZoneTree.svelte +19 -0
- package/dist/shell-shard/rich/ZoneTree.svelte.d.ts +8 -0
- package/dist/shell-shard/rich/ZonesTable.svelte +27 -0
- package/dist/shell-shard/rich/ZonesTable.svelte.d.ts +11 -0
- package/dist/shell-shard/scrollback.svelte.d.ts +36 -0
- package/dist/shell-shard/scrollback.svelte.js +43 -0
- package/dist/shell-shard/session-client.svelte.d.ts +23 -0
- package/dist/shell-shard/session-client.svelte.js +120 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
- package/dist/shell-shard/shellShard.svelte.js +139 -0
- package/dist/shell-shard/verbs/apps.d.ts +3 -0
- package/dist/shell-shard/verbs/apps.js +50 -0
- package/dist/shell-shard/verbs/clear.d.ts +2 -0
- package/dist/shell-shard/verbs/clear.js +7 -0
- package/dist/shell-shard/verbs/help.d.ts +2 -0
- package/dist/shell-shard/verbs/help.js +21 -0
- package/dist/shell-shard/verbs/history.d.ts +2 -0
- package/dist/shell-shard/verbs/history.js +20 -0
- package/dist/shell-shard/verbs/index.d.ts +2 -0
- package/dist/shell-shard/verbs/index.js +29 -0
- package/dist/shell-shard/verbs/session.d.ts +5 -0
- package/dist/shell-shard/verbs/session.js +65 -0
- package/dist/shell-shard/verbs/shards.d.ts +2 -0
- package/dist/shell-shard/verbs/shards.js +14 -0
- package/dist/shell-shard/verbs/views.d.ts +4 -0
- package/dist/shell-shard/verbs/views.js +90 -0
- package/dist/shell-shard/verbs/zones.d.ts +3 -0
- package/dist/shell-shard/verbs/zones.js +38 -0
- package/dist/shellRuntime.svelte.d.ts +27 -0
- package/dist/shellRuntime.svelte.js +27 -0
- package/dist/state/backends.d.ts +26 -0
- package/dist/state/backends.js +99 -0
- package/dist/state/manage.d.ts +14 -0
- package/dist/state/manage.js +40 -0
- package/dist/state/types.d.ts +55 -0
- package/dist/state/types.js +17 -0
- package/dist/state/zones.svelte.d.ts +53 -0
- package/dist/state/zones.svelte.js +141 -0
- package/dist/theme.d.ts +28 -0
- package/dist/theme.js +92 -0
- package/dist/tokens.css +102 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/package.json +60 -0
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Ambient module declarations for static asset imports.
|
|
2
|
+
// Vite resolves these to URL strings at bundle time; svelte-check/tsc
|
|
3
|
+
// need a type declaration so that `import url from './foo.svg'` typechecks.
|
|
4
|
+
|
|
5
|
+
declare module '*.svg' {
|
|
6
|
+
const url: string;
|
|
7
|
+
export default url;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module '*.png' {
|
|
11
|
+
const url: string;
|
|
12
|
+
export default url;
|
|
13
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* GuestBanner — persistent bar shown when browsing as guest.
|
|
4
|
+
* Clicking "Sign in" opens a modal-like overlay with the sign-in form.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isGuest, login } from './index';
|
|
8
|
+
|
|
9
|
+
let showSignIn = $state(false);
|
|
10
|
+
let username = $state('');
|
|
11
|
+
let password = $state('');
|
|
12
|
+
let error = $state<string | null>(null);
|
|
13
|
+
let loading = $state(false);
|
|
14
|
+
|
|
15
|
+
async function handleLogin() {
|
|
16
|
+
if (!username.trim() || !password.trim() || loading) return;
|
|
17
|
+
loading = true;
|
|
18
|
+
error = null;
|
|
19
|
+
const result = await login(username.trim(), password.trim());
|
|
20
|
+
loading = false;
|
|
21
|
+
if (result.ok) {
|
|
22
|
+
showSignIn = false;
|
|
23
|
+
} else {
|
|
24
|
+
error = result.error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div class="guest-banner-slot">
|
|
30
|
+
{#if isGuest()}
|
|
31
|
+
<div class="guest-banner">
|
|
32
|
+
<span class="guest-banner-text">Browsing as guest. Sign in to save your work.</span>
|
|
33
|
+
<button type="button" class="guest-banner-action" onclick={() => { showSignIn = true; }}>
|
|
34
|
+
Sign in
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
{#if showSignIn}
|
|
39
|
+
<div class="guest-signin-overlay" role="dialog">
|
|
40
|
+
<div class="guest-signin-card">
|
|
41
|
+
<form class="guest-signin-form" onsubmit={(e) => { e.preventDefault(); handleLogin(); }}>
|
|
42
|
+
<input class="guest-signin-input" type="text" placeholder="Username" bind:value={username} disabled={loading} autocomplete="username" />
|
|
43
|
+
<input class="guest-signin-input" type="password" placeholder="Password" bind:value={password} disabled={loading} autocomplete="current-password" />
|
|
44
|
+
<div class="guest-signin-actions">
|
|
45
|
+
<button type="submit" class="guest-signin-btn" disabled={loading || !username.trim() || !password.trim()}>
|
|
46
|
+
{loading ? 'Signing in...' : 'Sign in'}
|
|
47
|
+
</button>
|
|
48
|
+
<button type="button" class="guest-signin-cancel" onclick={() => { showSignIn = false; error = null; }}>
|
|
49
|
+
Cancel
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</form>
|
|
53
|
+
{#if error}
|
|
54
|
+
<div class="guest-signin-error">{error}</div>
|
|
55
|
+
{/if}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
{/if}
|
|
59
|
+
{/if}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<style>
|
|
63
|
+
.guest-banner {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
gap: 12px;
|
|
68
|
+
padding: 6px var(--shell-pad-md, 12px);
|
|
69
|
+
background: color-mix(in srgb, var(--shell-accent, #7c7cf0) 15%, transparent);
|
|
70
|
+
border-bottom: 1px solid var(--shell-border, #3a3a5c);
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
color: var(--shell-fg, #e0e0e0);
|
|
73
|
+
}
|
|
74
|
+
.guest-banner-action {
|
|
75
|
+
padding: 3px 10px;
|
|
76
|
+
color: var(--shell-bg, #1a1a2e);
|
|
77
|
+
font-size: 11px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
}
|
|
80
|
+
.guest-signin-overlay {
|
|
81
|
+
position: fixed;
|
|
82
|
+
inset: 0;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
background: rgba(0, 0, 0, 0.5);
|
|
87
|
+
z-index: 9999;
|
|
88
|
+
}
|
|
89
|
+
.guest-signin-card {
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
gap: 12px;
|
|
93
|
+
padding: 32px;
|
|
94
|
+
background: var(--shell-bg-elevated, #252540);
|
|
95
|
+
border: 1px solid var(--shell-border, #3a3a5c);
|
|
96
|
+
border-radius: var(--shell-radius-lg, 12px);
|
|
97
|
+
min-width: 300px;
|
|
98
|
+
}
|
|
99
|
+
.guest-signin-form {
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
gap: 10px;
|
|
103
|
+
}
|
|
104
|
+
.guest-signin-input {
|
|
105
|
+
padding: 8px 12px;
|
|
106
|
+
background: var(--shell-bg, #1a1a2e);
|
|
107
|
+
color: var(--shell-fg, #e0e0e0);
|
|
108
|
+
border: 1px solid var(--shell-border, #3a3a5c);
|
|
109
|
+
border-radius: var(--shell-radius, 6px);
|
|
110
|
+
font-size: 13px;
|
|
111
|
+
}
|
|
112
|
+
.guest-signin-input::placeholder { color: var(--shell-fg-muted, #888); }
|
|
113
|
+
.guest-signin-actions { display: flex; gap: 8px; }
|
|
114
|
+
.guest-signin-btn {
|
|
115
|
+
flex: 1;
|
|
116
|
+
padding: 8px;
|
|
117
|
+
color: var(--shell-bg, #1a1a2e);
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
}
|
|
120
|
+
.guest-signin-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
121
|
+
.guest-signin-cancel {
|
|
122
|
+
padding: 8px 12px;
|
|
123
|
+
background: transparent;
|
|
124
|
+
color: var(--shell-fg-subtle, #aaa);
|
|
125
|
+
border: 1px solid var(--shell-border, #3a3a5c);
|
|
126
|
+
}
|
|
127
|
+
.guest-signin-error {
|
|
128
|
+
padding: 6px 10px;
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
color: var(--shell-error, #d32f2f);
|
|
131
|
+
background: color-mix(in srgb, var(--shell-error, #d32f2f) 10%, transparent);
|
|
132
|
+
border-radius: var(--shell-radius, 6px);
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SignInWall — standalone sign-in screen shown before shell boots.
|
|
4
|
+
* Mounted directly to the target element by createShell().
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { login, register } from './index';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
selfRegistration: boolean;
|
|
11
|
+
onSuccess: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { selfRegistration, onSuccess }: Props = $props();
|
|
15
|
+
|
|
16
|
+
let mode = $state<'login' | 'register'>('login');
|
|
17
|
+
let username = $state('');
|
|
18
|
+
let password = $state('');
|
|
19
|
+
let displayName = $state('');
|
|
20
|
+
let error = $state<string | null>(null);
|
|
21
|
+
let loading = $state(false);
|
|
22
|
+
|
|
23
|
+
async function handleLogin() {
|
|
24
|
+
if (!username.trim() || !password.trim() || loading) return;
|
|
25
|
+
loading = true;
|
|
26
|
+
error = null;
|
|
27
|
+
const result = await login(username.trim(), password.trim());
|
|
28
|
+
loading = false;
|
|
29
|
+
if (result.ok) {
|
|
30
|
+
onSuccess();
|
|
31
|
+
} else {
|
|
32
|
+
error = result.error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function handleRegister() {
|
|
37
|
+
if (!username.trim() || !password.trim() || loading) return;
|
|
38
|
+
loading = true;
|
|
39
|
+
error = null;
|
|
40
|
+
const result = await register(
|
|
41
|
+
username.trim(),
|
|
42
|
+
password.trim(),
|
|
43
|
+
displayName.trim() || undefined,
|
|
44
|
+
);
|
|
45
|
+
loading = false;
|
|
46
|
+
if (result.ok) {
|
|
47
|
+
onSuccess();
|
|
48
|
+
} else {
|
|
49
|
+
error = result.error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function switchMode(m: 'login' | 'register') {
|
|
54
|
+
mode = m;
|
|
55
|
+
error = null;
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<div class="signin-wall">
|
|
60
|
+
<div class="signin-card">
|
|
61
|
+
<h1 class="signin-brand">SH3</h1>
|
|
62
|
+
|
|
63
|
+
{#if mode === 'login'}
|
|
64
|
+
<form class="signin-form" onsubmit={(e) => { e.preventDefault(); handleLogin(); }}>
|
|
65
|
+
<input
|
|
66
|
+
class="signin-input"
|
|
67
|
+
type="text"
|
|
68
|
+
placeholder="Username"
|
|
69
|
+
bind:value={username}
|
|
70
|
+
disabled={loading}
|
|
71
|
+
autocomplete="username"
|
|
72
|
+
/>
|
|
73
|
+
<input
|
|
74
|
+
class="signin-input"
|
|
75
|
+
type="password"
|
|
76
|
+
placeholder="Password"
|
|
77
|
+
bind:value={password}
|
|
78
|
+
disabled={loading}
|
|
79
|
+
autocomplete="current-password"
|
|
80
|
+
/>
|
|
81
|
+
<button type="submit" class="signin-btn" disabled={loading || !username.trim() || !password.trim()}>
|
|
82
|
+
{loading ? 'Signing in...' : 'Sign in'}
|
|
83
|
+
</button>
|
|
84
|
+
</form>
|
|
85
|
+
{#if selfRegistration}
|
|
86
|
+
<button type="button" class="signin-link" onclick={() => switchMode('register')}>
|
|
87
|
+
Create an account
|
|
88
|
+
</button>
|
|
89
|
+
{/if}
|
|
90
|
+
{:else}
|
|
91
|
+
<form class="signin-form" onsubmit={(e) => { e.preventDefault(); handleRegister(); }}>
|
|
92
|
+
<input
|
|
93
|
+
class="signin-input"
|
|
94
|
+
type="text"
|
|
95
|
+
placeholder="Username"
|
|
96
|
+
bind:value={username}
|
|
97
|
+
disabled={loading}
|
|
98
|
+
autocomplete="username"
|
|
99
|
+
/>
|
|
100
|
+
<input
|
|
101
|
+
class="signin-input"
|
|
102
|
+
type="text"
|
|
103
|
+
placeholder="Display name (optional)"
|
|
104
|
+
bind:value={displayName}
|
|
105
|
+
disabled={loading}
|
|
106
|
+
/>
|
|
107
|
+
<input
|
|
108
|
+
class="signin-input"
|
|
109
|
+
type="password"
|
|
110
|
+
placeholder="Password"
|
|
111
|
+
bind:value={password}
|
|
112
|
+
disabled={loading}
|
|
113
|
+
autocomplete="new-password"
|
|
114
|
+
/>
|
|
115
|
+
<button type="submit" class="signin-btn" disabled={loading || !username.trim() || !password.trim()}>
|
|
116
|
+
{loading ? 'Creating...' : 'Create account'}
|
|
117
|
+
</button>
|
|
118
|
+
</form>
|
|
119
|
+
<button type="button" class="signin-link" onclick={() => switchMode('login')}>
|
|
120
|
+
Back to sign in
|
|
121
|
+
</button>
|
|
122
|
+
{/if}
|
|
123
|
+
|
|
124
|
+
{#if error}
|
|
125
|
+
<div class="signin-error">{error}</div>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<style>
|
|
131
|
+
.signin-wall {
|
|
132
|
+
position: absolute;
|
|
133
|
+
inset: 0;
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
background: var(--shell-grad-bg, var(--shell-bg, #1a1a2e));
|
|
138
|
+
color: var(--shell-fg, #e0e0e0);
|
|
139
|
+
font-family: system-ui, sans-serif;
|
|
140
|
+
}
|
|
141
|
+
.signin-card {
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-direction: column;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 16px;
|
|
146
|
+
padding: 48px 40px;
|
|
147
|
+
background: var(--shell-grad-bg-elevated, var(--shell-bg-elevated, #252540));
|
|
148
|
+
border: 1px solid var(--shell-border, #3a3a5c);
|
|
149
|
+
border-radius: var(--shell-radius-lg, 12px);
|
|
150
|
+
min-width: 320px;
|
|
151
|
+
}
|
|
152
|
+
.signin-brand {
|
|
153
|
+
margin: 0 0 8px;
|
|
154
|
+
font-size: 42px;
|
|
155
|
+
color: var(--shell-accent, #7c7cf0);
|
|
156
|
+
letter-spacing: 2px;
|
|
157
|
+
}
|
|
158
|
+
.signin-form {
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
gap: 12px;
|
|
162
|
+
width: 100%;
|
|
163
|
+
}
|
|
164
|
+
.signin-input {
|
|
165
|
+
padding: 10px 14px;
|
|
166
|
+
background: var(--shell-bg, #1a1a2e);
|
|
167
|
+
color: var(--shell-fg, #e0e0e0);
|
|
168
|
+
border: 1px solid var(--shell-border, #3a3a5c);
|
|
169
|
+
border-radius: var(--shell-radius, 6px);
|
|
170
|
+
font-size: 14px;
|
|
171
|
+
}
|
|
172
|
+
.signin-input::placeholder {
|
|
173
|
+
color: var(--shell-fg-muted, #888);
|
|
174
|
+
}
|
|
175
|
+
.signin-btn {
|
|
176
|
+
padding: 10px 16px;
|
|
177
|
+
color: var(--shell-bg, #1a1a2e);
|
|
178
|
+
font-weight: 600;
|
|
179
|
+
font-size: 14px;
|
|
180
|
+
}
|
|
181
|
+
.signin-btn:disabled {
|
|
182
|
+
opacity: 0.6;
|
|
183
|
+
cursor: not-allowed;
|
|
184
|
+
}
|
|
185
|
+
.signin-link {
|
|
186
|
+
background: none;
|
|
187
|
+
color: var(--shell-accent, #7c7cf0);
|
|
188
|
+
font-size: 13px;
|
|
189
|
+
padding: 0;
|
|
190
|
+
}
|
|
191
|
+
.signin-link:hover {
|
|
192
|
+
text-decoration: underline;
|
|
193
|
+
}
|
|
194
|
+
.signin-error {
|
|
195
|
+
padding: 8px 12px;
|
|
196
|
+
font-size: 13px;
|
|
197
|
+
color: var(--shell-error, #d32f2f);
|
|
198
|
+
background: color-mix(in srgb, var(--shell-error, #d32f2f) 10%, transparent);
|
|
199
|
+
border-radius: var(--shell-radius, 6px);
|
|
200
|
+
width: 100%;
|
|
201
|
+
text-align: center;
|
|
202
|
+
}
|
|
203
|
+
</style>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side auth — session-based identity for SH3.
|
|
3
|
+
*
|
|
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.
|
|
8
|
+
*
|
|
9
|
+
* .svelte.ts because it uses $state for reactive auth status.
|
|
10
|
+
*/
|
|
11
|
+
import type { AuthUser, AuthSession, BootConfig } from './types';
|
|
12
|
+
/**
|
|
13
|
+
* Initialize auth from boot config. Called once by createShell()
|
|
14
|
+
* after fetching /api/boot.
|
|
15
|
+
*/
|
|
16
|
+
export declare function initFromBoot(url: string, config: BootConfig): void;
|
|
17
|
+
/**
|
|
18
|
+
* Log in with username + password. On success, updates reactive state.
|
|
19
|
+
* Returns { ok: true } or { ok: false, error: string }.
|
|
20
|
+
*/
|
|
21
|
+
export declare function login(username: string, password: string): Promise<{
|
|
22
|
+
ok: true;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Register a new account (when self-registration is enabled).
|
|
29
|
+
* On success, auto-logs in and updates reactive state.
|
|
30
|
+
*/
|
|
31
|
+
export declare function register(username: string, password: string, displayName?: string): Promise<{
|
|
32
|
+
ok: true;
|
|
33
|
+
} | {
|
|
34
|
+
ok: false;
|
|
35
|
+
error: string;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Log out — clear session on server and client.
|
|
39
|
+
*
|
|
40
|
+
* If the boot policy forbids guest browsing (auth.required &&
|
|
41
|
+
* !auth.guestAllowed), trigger a full page reload so the boot-time
|
|
42
|
+
* hard gate in createShell.ts re-runs and shows the sign-in wall.
|
|
43
|
+
* This keeps the policy authoritative in a single place rather than
|
|
44
|
+
* duplicating it here.
|
|
45
|
+
*/
|
|
46
|
+
export declare function logout(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Mark this session as local-owner — auto-elevate to admin without
|
|
49
|
+
* server verification. Called by the host in Tauri / dev environments.
|
|
50
|
+
*/
|
|
51
|
+
export declare function setLocalOwner(): void;
|
|
52
|
+
/** Reactive — true when running as local owner (Tauri / dev). */
|
|
53
|
+
export declare function isLocalOwner(): boolean;
|
|
54
|
+
/** Reactive — true when the user has admin role. */
|
|
55
|
+
export declare function isAdmin(): boolean;
|
|
56
|
+
/** Reactive — true when the user has a valid session. */
|
|
57
|
+
export declare function isAuthenticated(): boolean;
|
|
58
|
+
/** Reactive — true when browsing without a session. */
|
|
59
|
+
export declare function isGuest(): boolean;
|
|
60
|
+
/** Get the current user (reactive). */
|
|
61
|
+
export declare function getUser(): AuthUser | null;
|
|
62
|
+
/** Get the current session (reactive). */
|
|
63
|
+
export declare function getSession(): AuthSession | null;
|
|
64
|
+
/**
|
|
65
|
+
* Build an Authorization header value for authenticated fetch calls
|
|
66
|
+
* that need explicit headers (e.g. non-cookie contexts).
|
|
67
|
+
* Returns null if not authenticated.
|
|
68
|
+
*/
|
|
69
|
+
export declare function getAuthHeader(): string | null;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side auth — session-based identity for SH3.
|
|
3
|
+
*
|
|
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.
|
|
8
|
+
*
|
|
9
|
+
* .svelte.ts because it uses $state for reactive auth status.
|
|
10
|
+
*/
|
|
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. */
|
|
16
|
+
let serverUrl = '';
|
|
17
|
+
/** Boot auth policy, captured at initFromBoot. Used by logout() to
|
|
18
|
+
* decide whether a post-logout reload is required to re-run the
|
|
19
|
+
* boot-time hard gate. Non-reactive — no consumer observes this. */
|
|
20
|
+
let authConfig = null;
|
|
21
|
+
/**
|
|
22
|
+
* Initialize auth from boot config. Called once by createShell()
|
|
23
|
+
* after fetching /api/boot.
|
|
24
|
+
*/
|
|
25
|
+
export function initFromBoot(url, config) {
|
|
26
|
+
serverUrl = url;
|
|
27
|
+
authConfig = config.auth;
|
|
28
|
+
currentUser = config.user;
|
|
29
|
+
currentSession = config.session;
|
|
30
|
+
guest = !config.session && !config.user;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Log in with username + password. On success, updates reactive state.
|
|
34
|
+
* Returns { ok: true } or { ok: false, error: string }.
|
|
35
|
+
*/
|
|
36
|
+
export async function login(username, password) {
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(`${serverUrl}/api/auth/login`, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json' },
|
|
41
|
+
credentials: 'include',
|
|
42
|
+
body: JSON.stringify({ username, password }),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const body = await res.json().catch(() => ({}));
|
|
46
|
+
return { ok: false, error: body.error || 'Login failed' };
|
|
47
|
+
}
|
|
48
|
+
const body = await res.json();
|
|
49
|
+
currentUser = body.user;
|
|
50
|
+
currentSession = body.session;
|
|
51
|
+
guest = false;
|
|
52
|
+
return { ok: true };
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
return { ok: false, error: 'Network error' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Register a new account (when self-registration is enabled).
|
|
60
|
+
* On success, auto-logs in and updates reactive state.
|
|
61
|
+
*/
|
|
62
|
+
export async function register(username, password, displayName) {
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(`${serverUrl}/api/auth/register`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
credentials: 'include',
|
|
68
|
+
body: JSON.stringify({ username, password, displayName }),
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
const body = await res.json().catch(() => ({}));
|
|
72
|
+
return { ok: false, error: body.error || 'Registration failed' };
|
|
73
|
+
}
|
|
74
|
+
const body = await res.json();
|
|
75
|
+
currentUser = body.user;
|
|
76
|
+
currentSession = body.session;
|
|
77
|
+
guest = false;
|
|
78
|
+
return { ok: true };
|
|
79
|
+
}
|
|
80
|
+
catch (_a) {
|
|
81
|
+
return { ok: false, error: 'Network error' };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Log out — clear session on server and client.
|
|
86
|
+
*
|
|
87
|
+
* If the boot policy forbids guest browsing (auth.required &&
|
|
88
|
+
* !auth.guestAllowed), trigger a full page reload so the boot-time
|
|
89
|
+
* hard gate in createShell.ts re-runs and shows the sign-in wall.
|
|
90
|
+
* This keeps the policy authoritative in a single place rather than
|
|
91
|
+
* duplicating it here.
|
|
92
|
+
*/
|
|
93
|
+
export async function logout() {
|
|
94
|
+
try {
|
|
95
|
+
await fetch(`${serverUrl}/api/auth/logout`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
credentials: 'include',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (_a) {
|
|
101
|
+
// Best effort
|
|
102
|
+
}
|
|
103
|
+
if ((authConfig === null || authConfig === void 0 ? void 0 : authConfig.required) && !authConfig.guestAllowed) {
|
|
104
|
+
// Policy forbids guest browsing — re-run the boot-time hard gate.
|
|
105
|
+
// Do not touch reactive state: the page is leaving.
|
|
106
|
+
window.location.reload();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
currentUser = null;
|
|
110
|
+
currentSession = null;
|
|
111
|
+
guest = true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Mark this session as local-owner — auto-elevate to admin without
|
|
115
|
+
* server verification. Called by the host in Tauri / dev environments.
|
|
116
|
+
*/
|
|
117
|
+
export function setLocalOwner() {
|
|
118
|
+
currentUser = {
|
|
119
|
+
id: 'local',
|
|
120
|
+
username: 'local',
|
|
121
|
+
displayName: 'Local Owner',
|
|
122
|
+
role: 'admin',
|
|
123
|
+
createdAt: '',
|
|
124
|
+
updatedAt: '',
|
|
125
|
+
};
|
|
126
|
+
currentSession = {
|
|
127
|
+
token: 'local',
|
|
128
|
+
userId: 'local',
|
|
129
|
+
role: 'admin',
|
|
130
|
+
expiresAt: Infinity,
|
|
131
|
+
};
|
|
132
|
+
guest = false;
|
|
133
|
+
}
|
|
134
|
+
/** Reactive — true when running as local owner (Tauri / dev). */
|
|
135
|
+
export function isLocalOwner() {
|
|
136
|
+
return (currentSession === null || currentSession === void 0 ? void 0 : currentSession.token) === 'local';
|
|
137
|
+
}
|
|
138
|
+
/** Reactive — true when the user has admin role. */
|
|
139
|
+
export function isAdmin() {
|
|
140
|
+
return (currentSession === null || currentSession === void 0 ? void 0 : currentSession.role) === 'admin';
|
|
141
|
+
}
|
|
142
|
+
/** Reactive — true when the user has a valid session. */
|
|
143
|
+
export function isAuthenticated() {
|
|
144
|
+
return currentSession !== null;
|
|
145
|
+
}
|
|
146
|
+
/** Reactive — true when browsing without a session. */
|
|
147
|
+
export function isGuest() {
|
|
148
|
+
return guest;
|
|
149
|
+
}
|
|
150
|
+
/** Get the current user (reactive). */
|
|
151
|
+
export function getUser() {
|
|
152
|
+
return currentUser;
|
|
153
|
+
}
|
|
154
|
+
/** Get the current session (reactive). */
|
|
155
|
+
export function getSession() {
|
|
156
|
+
return currentSession;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build an Authorization header value for authenticated fetch calls
|
|
160
|
+
* that need explicit headers (e.g. non-cookie contexts).
|
|
161
|
+
* Returns null if not authenticated.
|
|
162
|
+
*/
|
|
163
|
+
export function getAuthHeader() {
|
|
164
|
+
return currentSession ? `Bearer ${currentSession.token}` : null;
|
|
165
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { initFromBoot, login, logout, register, isAdmin, isLocalOwner, 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
|
+
}
|