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
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package installer -- coordinates storing, loading, and registering packages.
|
|
3
|
+
*
|
|
4
|
+
* This is the bridge between the store shard (which fetches and verifies
|
|
5
|
+
* bundles) and the framework (which registers shards/apps). The install API
|
|
6
|
+
* is host-only; shards and apps must not import from here.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. `installPackage(bundle, meta)` -- load module from bytes, verify
|
|
10
|
+
* declared type matches actual type, persist to IndexedDB, register
|
|
11
|
+
* with framework.
|
|
12
|
+
* 2. `uninstallPackage(id)` -- deactivate if active, remove from storage.
|
|
13
|
+
* 3. `loadInstalledPackages()` -- called at boot, re-loads all installed
|
|
14
|
+
* packages from IndexedDB and registers them.
|
|
15
|
+
*/
|
|
16
|
+
import { loadBundleModule } from './loader';
|
|
17
|
+
import { savePackage, loadBundle, listInstalled, removePackage } from './storage';
|
|
18
|
+
import { verifyIntegrity } from './integrity';
|
|
19
|
+
import { registerShard, deactivateShard } from '../shards/activate.svelte';
|
|
20
|
+
import { registerApp } from '../apps/registry.svelte';
|
|
21
|
+
/**
|
|
22
|
+
* Install a package from raw bundle bytes and metadata.
|
|
23
|
+
*
|
|
24
|
+
* Loads the ESM module, verifies the declared type matches the actual module
|
|
25
|
+
* shape, persists to IndexedDB, and registers with the framework. If
|
|
26
|
+
* registration fails (e.g. duplicate id from a shard that was already
|
|
27
|
+
* glob-discovered), the package is still persisted but `hotLoaded` is false.
|
|
28
|
+
*
|
|
29
|
+
* @param bundle - Raw verified ESM bundle bytes.
|
|
30
|
+
* @param meta - Provenance metadata for the install record.
|
|
31
|
+
* @returns Result object indicating success/failure and hot-load status.
|
|
32
|
+
*/
|
|
33
|
+
export async function installPackage(bundle, meta) {
|
|
34
|
+
// 1. Verify bundle integrity before executing any code.
|
|
35
|
+
try {
|
|
36
|
+
await verifyIntegrity(bundle, meta.integrity);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
hotLoaded: false,
|
|
42
|
+
error: `Integrity check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// 2. Load the module from verified bytes.
|
|
46
|
+
let loaded;
|
|
47
|
+
try {
|
|
48
|
+
loaded = await loadBundleModule(bundle);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
hotLoaded: false,
|
|
54
|
+
error: `Failed to load bundle: ${err instanceof Error ? err.message : String(err)}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// 3. Verify the bundle contains the declared type.
|
|
58
|
+
if (meta.type === 'shard' && loaded.shards.length === 0) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
hotLoaded: false,
|
|
62
|
+
error: `Package "${meta.id}" declared type "shard" but bundle contains no valid shard`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (meta.type === 'app' && loaded.apps.length === 0) {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
hotLoaded: false,
|
|
69
|
+
error: `Package "${meta.id}" declared type "app" but bundle contains no valid app`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// 4. Persist to IndexedDB.
|
|
73
|
+
const record = {
|
|
74
|
+
id: meta.id,
|
|
75
|
+
type: meta.type,
|
|
76
|
+
version: meta.version,
|
|
77
|
+
sourceRegistry: meta.sourceRegistry,
|
|
78
|
+
contractVersion: meta.contractVersion,
|
|
79
|
+
installedAt: new Date().toISOString(),
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
await savePackage(meta.id, bundle, record);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
hotLoaded: false,
|
|
88
|
+
error: `Failed to persist package: ${err instanceof Error ? err.message : String(err)}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// 5. Register all shards and apps from the bundle.
|
|
92
|
+
let hotLoaded = true;
|
|
93
|
+
try {
|
|
94
|
+
for (const shard of loaded.shards)
|
|
95
|
+
registerShard(shard);
|
|
96
|
+
for (const app of loaded.apps)
|
|
97
|
+
registerApp(app);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.warn(`[sh3] Package "${meta.id}" installed but registration failed (will retry on next boot):`, err instanceof Error ? err.message : err);
|
|
101
|
+
hotLoaded = false;
|
|
102
|
+
}
|
|
103
|
+
return { success: true, package: record, hotLoaded };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Uninstall a package by id.
|
|
107
|
+
*
|
|
108
|
+
* If the package is a shard and currently active, deactivates it first.
|
|
109
|
+
* Removes both the bundle and metadata from IndexedDB.
|
|
110
|
+
*
|
|
111
|
+
* @param id - The package id to uninstall.
|
|
112
|
+
*/
|
|
113
|
+
export async function uninstallPackage(id) {
|
|
114
|
+
// Attempt deactivation (no-op if not active or not a shard).
|
|
115
|
+
try {
|
|
116
|
+
deactivateShard(id);
|
|
117
|
+
}
|
|
118
|
+
catch (_a) {
|
|
119
|
+
// Ignore -- may not be a shard, or may not be active.
|
|
120
|
+
}
|
|
121
|
+
await removePackage(id);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* List all installed packages.
|
|
125
|
+
*
|
|
126
|
+
* Delegates directly to the storage layer. Returns metadata records only,
|
|
127
|
+
* not the bundle bytes.
|
|
128
|
+
*/
|
|
129
|
+
export async function listInstalledPackages() {
|
|
130
|
+
return listInstalled();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Load all installed packages from IndexedDB and register them.
|
|
134
|
+
*
|
|
135
|
+
* Called once at boot by `bootstrap()`, before any glob-discovered shards
|
|
136
|
+
* or the shell shard are registered. Individual package failures are logged
|
|
137
|
+
* as warnings but do not prevent the shell from booting.
|
|
138
|
+
*/
|
|
139
|
+
export async function loadInstalledPackages() {
|
|
140
|
+
let packages;
|
|
141
|
+
try {
|
|
142
|
+
packages = await listInstalled();
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
console.warn('[sh3] Failed to read installed packages from storage:', err instanceof Error ? err.message : err);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const pkg of packages) {
|
|
149
|
+
try {
|
|
150
|
+
const bytes = await loadBundle(pkg.id);
|
|
151
|
+
if (!bytes) {
|
|
152
|
+
console.warn(`[sh3] No bundle found for installed package "${pkg.id}", skipping`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const loaded = await loadBundleModule(bytes);
|
|
156
|
+
for (const shard of loaded.shards)
|
|
157
|
+
registerShard(shard);
|
|
158
|
+
for (const app of loaded.apps)
|
|
159
|
+
registerApp(app);
|
|
160
|
+
if (loaded.shards.length === 0 && loaded.apps.length === 0) {
|
|
161
|
+
console.warn(`[sh3] Package "${pkg.id}" contains no valid shards or apps, skipping`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
console.warn(`[sh3] Failed to load installed package "${pkg.id}":`, err instanceof Error ? err.message : err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SRI integrity verification for downloaded bundles.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Web Crypto API (available in browsers and Tauri WebView).
|
|
5
|
+
* Accepts "sha384-<base64>" format matching the W3C SRI spec.
|
|
6
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Verify that `data` matches the expected SRI integrity string.
|
|
10
|
+
*
|
|
11
|
+
* Uses the Web Crypto API to compute the digest and compares it against the
|
|
12
|
+
* base64 hash in `integrity`. Throws with a descriptive message on any
|
|
13
|
+
* mismatch, invalid format, or unsupported algorithm.
|
|
14
|
+
*
|
|
15
|
+
* @param data - Raw bytes of the downloaded bundle.
|
|
16
|
+
* @param integrity - SRI hash string, e.g. `"sha384-abc123..."`.
|
|
17
|
+
* @returns Resolves on success; throws on mismatch or bad format.
|
|
18
|
+
* @throws If the format is invalid, the algorithm is unsupported, or the hash does not match.
|
|
19
|
+
*/
|
|
20
|
+
export declare function verifyIntegrity(data: ArrayBuffer, integrity: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Compute an SRI hash for the given data.
|
|
23
|
+
*
|
|
24
|
+
* Useful for publishing tools that need to generate the `integrity` field
|
|
25
|
+
* for a `registry.json` entry. Defaults to `sha384`, which is the recommended
|
|
26
|
+
* algorithm for the SH3 registry protocol.
|
|
27
|
+
*
|
|
28
|
+
* @param data - Raw bytes of the bundle to hash.
|
|
29
|
+
* @param algorithm - Hash algorithm to use. Defaults to `"sha384"`.
|
|
30
|
+
* @returns SRI string in the form `"<algorithm>-<base64hash>"`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function computeIntegrity(data: ArrayBuffer, algorithm?: 'sha256' | 'sha384' | 'sha512'): Promise<string>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SRI integrity verification for downloaded bundles.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Web Crypto API (available in browsers and Tauri WebView).
|
|
5
|
+
* Accepts "sha384-<base64>" format matching the W3C SRI spec.
|
|
6
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
|
7
|
+
*/
|
|
8
|
+
const SUPPORTED_ALGORITHMS = ['sha256', 'sha384', 'sha512'];
|
|
9
|
+
/**
|
|
10
|
+
* Parse an SRI integrity string into its algorithm and base64 hash components.
|
|
11
|
+
*
|
|
12
|
+
* @param integrity - SRI string in the form `"<algorithm>-<base64hash>"`.
|
|
13
|
+
* @returns Object with `algorithm` and `hash` fields.
|
|
14
|
+
* @throws If the format is invalid or the algorithm is not supported.
|
|
15
|
+
*/
|
|
16
|
+
function parseSri(integrity) {
|
|
17
|
+
const dashIndex = integrity.indexOf('-');
|
|
18
|
+
if (dashIndex === -1) {
|
|
19
|
+
throw new Error(`Invalid SRI format: ${integrity}`);
|
|
20
|
+
}
|
|
21
|
+
const algorithm = integrity.slice(0, dashIndex);
|
|
22
|
+
const hash = integrity.slice(dashIndex + 1);
|
|
23
|
+
if (!SUPPORTED_ALGORITHMS.includes(algorithm)) {
|
|
24
|
+
throw new Error(`Unsupported SRI algorithm: ${algorithm}. Supported: ${SUPPORTED_ALGORITHMS.join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
if (!hash) {
|
|
27
|
+
throw new Error(`Empty hash in SRI: ${integrity}`);
|
|
28
|
+
}
|
|
29
|
+
return { algorithm, hash };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Map an SRI algorithm name to its Web Crypto API equivalent.
|
|
33
|
+
*
|
|
34
|
+
* @param alg - SRI algorithm name (`"sha256"`, `"sha384"`, `"sha512"`).
|
|
35
|
+
* @returns Web Crypto algorithm name (e.g. `"SHA-384"`).
|
|
36
|
+
*/
|
|
37
|
+
function algorithmToWebCrypto(alg) {
|
|
38
|
+
return `SHA-${alg.slice(3)}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Encode an `ArrayBuffer` as a base64 string.
|
|
42
|
+
*
|
|
43
|
+
* @param buffer - Raw bytes to encode.
|
|
44
|
+
* @returns Base64 string representation of `buffer`.
|
|
45
|
+
*/
|
|
46
|
+
function bufferToBase64(buffer) {
|
|
47
|
+
const bytes = new Uint8Array(buffer);
|
|
48
|
+
let binary = '';
|
|
49
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
50
|
+
binary += String.fromCharCode(bytes[i]);
|
|
51
|
+
}
|
|
52
|
+
return btoa(binary);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Verify that `data` matches the expected SRI integrity string.
|
|
56
|
+
*
|
|
57
|
+
* Uses the Web Crypto API to compute the digest and compares it against the
|
|
58
|
+
* base64 hash in `integrity`. Throws with a descriptive message on any
|
|
59
|
+
* mismatch, invalid format, or unsupported algorithm.
|
|
60
|
+
*
|
|
61
|
+
* @param data - Raw bytes of the downloaded bundle.
|
|
62
|
+
* @param integrity - SRI hash string, e.g. `"sha384-abc123..."`.
|
|
63
|
+
* @returns Resolves on success; throws on mismatch or bad format.
|
|
64
|
+
* @throws If the format is invalid, the algorithm is unsupported, or the hash does not match.
|
|
65
|
+
*/
|
|
66
|
+
export async function verifyIntegrity(data, integrity) {
|
|
67
|
+
const { algorithm, hash: expectedHash } = parseSri(integrity);
|
|
68
|
+
const webCryptoAlg = algorithmToWebCrypto(algorithm);
|
|
69
|
+
const digest = await crypto.subtle.digest(webCryptoAlg, data);
|
|
70
|
+
const actualHash = bufferToBase64(digest);
|
|
71
|
+
if (actualHash !== expectedHash) {
|
|
72
|
+
throw new Error(`Integrity check failed for ${algorithm}. ` +
|
|
73
|
+
`Expected: ${expectedHash}, got: ${actualHash}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Compute an SRI hash for the given data.
|
|
78
|
+
*
|
|
79
|
+
* Useful for publishing tools that need to generate the `integrity` field
|
|
80
|
+
* for a `registry.json` entry. Defaults to `sha384`, which is the recommended
|
|
81
|
+
* algorithm for the SH3 registry protocol.
|
|
82
|
+
*
|
|
83
|
+
* @param data - Raw bytes of the bundle to hash.
|
|
84
|
+
* @param algorithm - Hash algorithm to use. Defaults to `"sha384"`.
|
|
85
|
+
* @returns SRI string in the form `"<algorithm>-<base64hash>"`.
|
|
86
|
+
*/
|
|
87
|
+
export async function computeIntegrity(data, algorithm = 'sha384') {
|
|
88
|
+
const webCryptoAlg = algorithmToWebCrypto(algorithm);
|
|
89
|
+
const digest = await crypto.subtle.digest(webCryptoAlg, data);
|
|
90
|
+
const hash = bufferToBase64(digest);
|
|
91
|
+
return `${algorithm}-${hash}`;
|
|
92
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime loader -- loads a pre-built ESM bundle from raw bytes via blob URL.
|
|
3
|
+
*
|
|
4
|
+
* The install API hands us an ArrayBuffer (the verified bundle), we wrap it
|
|
5
|
+
* in a blob URL, dynamic-import it, then immediately revoke the URL so the
|
|
6
|
+
* blob can be GC'd. The module's default export must carry a `manifest`
|
|
7
|
+
* field; we use duck-typing to distinguish shards from apps.
|
|
8
|
+
*
|
|
9
|
+
* Bare specifier rewriting: bundles are built with external dependencies
|
|
10
|
+
* (`sh3`, `svelte`, `svelte/internal/client`, etc.). Blob URLs can't
|
|
11
|
+
* resolve bare specifiers, so we expose each dependency's runtime exports
|
|
12
|
+
* at a stable blob URL (a "shim") and rewrite bare specifiers in the
|
|
13
|
+
* bundle source before loading.
|
|
14
|
+
*/
|
|
15
|
+
import type { Shard } from '../shards/types';
|
|
16
|
+
import type { App } from '../apps/types';
|
|
17
|
+
/**
|
|
18
|
+
* Result of loading a bundle — may contain a shard, an app, or both.
|
|
19
|
+
* Combo bundles (one shard + its companion app) are a supported pattern.
|
|
20
|
+
*/
|
|
21
|
+
export interface LoadedBundle {
|
|
22
|
+
shards: Shard[];
|
|
23
|
+
apps: App[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Load a pre-built ESM bundle from raw bytes.
|
|
27
|
+
*
|
|
28
|
+
* Scans all module exports (default + named) for objects with a `manifest`
|
|
29
|
+
* property and classifies each as a shard or app. Combo bundles that export
|
|
30
|
+
* both a shard and its companion app are supported.
|
|
31
|
+
*
|
|
32
|
+
* @param bytes - Raw bundle bytes (the verified ESM file contents).
|
|
33
|
+
* @returns All shards and apps found in the module's exports.
|
|
34
|
+
* @throws If the blob URL cannot be created or the import fails.
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadBundleModule(bytes: ArrayBuffer): Promise<LoadedBundle>;
|
|
37
|
+
/**
|
|
38
|
+
* Type guard: returns true if the loaded module is a shard.
|
|
39
|
+
*
|
|
40
|
+
* A shard has an `activate` function and `manifest.views` array. An app
|
|
41
|
+
* has neither — it has `initialLayout` and `manifest.requiredShards`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isShard(mod: Shard | App): mod is Shard;
|
|
44
|
+
/**
|
|
45
|
+
* Type guard: returns true if the loaded module is an app.
|
|
46
|
+
*
|
|
47
|
+
* An app has `initialLayout` and `manifest.requiredShards`. A shard has
|
|
48
|
+
* `activate` and `manifest.views` instead.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isApp(mod: Shard | App): mod is App;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime loader -- loads a pre-built ESM bundle from raw bytes via blob URL.
|
|
3
|
+
*
|
|
4
|
+
* The install API hands us an ArrayBuffer (the verified bundle), we wrap it
|
|
5
|
+
* in a blob URL, dynamic-import it, then immediately revoke the URL so the
|
|
6
|
+
* blob can be GC'd. The module's default export must carry a `manifest`
|
|
7
|
+
* field; we use duck-typing to distinguish shards from apps.
|
|
8
|
+
*
|
|
9
|
+
* Bare specifier rewriting: bundles are built with external dependencies
|
|
10
|
+
* (`sh3`, `svelte`, `svelte/internal/client`, etc.). Blob URLs can't
|
|
11
|
+
* resolve bare specifiers, so we expose each dependency's runtime exports
|
|
12
|
+
* at a stable blob URL (a "shim") and rewrite bare specifiers in the
|
|
13
|
+
* bundle source before loading.
|
|
14
|
+
*/
|
|
15
|
+
import * as api from '../api';
|
|
16
|
+
import * as svelte from 'svelte';
|
|
17
|
+
// @ts-expect-error — svelte/internal/client has no public type declarations
|
|
18
|
+
import * as svelteInternalClient from 'svelte/internal/client';
|
|
19
|
+
// ---- shim infrastructure ---------------------------------------------------
|
|
20
|
+
/**
|
|
21
|
+
* Create a blob URL that re-exports every runtime key from a module
|
|
22
|
+
* previously stashed on globalThis. The shim is created once and never
|
|
23
|
+
* revoked — it must outlive every bundle that references it.
|
|
24
|
+
*/
|
|
25
|
+
// JS reserved words that can't appear in destructuring or as bare export names.
|
|
26
|
+
const RESERVED = new Set([
|
|
27
|
+
'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
28
|
+
'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'false',
|
|
29
|
+
'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let',
|
|
30
|
+
'new', 'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try',
|
|
31
|
+
'typeof', 'var', 'void', 'while', 'with', 'yield',
|
|
32
|
+
]);
|
|
33
|
+
function createShimUrl(globalKey, mod) {
|
|
34
|
+
const names = Object.keys(mod);
|
|
35
|
+
const safe = names.filter(n => !RESERVED.has(n));
|
|
36
|
+
const reserved = names.filter(n => RESERVED.has(n));
|
|
37
|
+
const lines = [`const __m__ = globalThis.${globalKey};`];
|
|
38
|
+
// Safe names can use destructuring.
|
|
39
|
+
if (safe.length > 0) {
|
|
40
|
+
lines.push(`export const { ${safe.join(', ')} } = __m__;`);
|
|
41
|
+
}
|
|
42
|
+
// Reserved words need individual re-exports via a renamed binding.
|
|
43
|
+
for (const name of reserved) {
|
|
44
|
+
lines.push(`const __r_${name}__ = __m__['${name}']; export { __r_${name}__ as ${name} };`);
|
|
45
|
+
}
|
|
46
|
+
lines.push(`export default __m__.default;`);
|
|
47
|
+
return URL.createObjectURL(new Blob([lines.join('\n')], { type: 'application/javascript' }));
|
|
48
|
+
}
|
|
49
|
+
// Expose modules on globalThis so shims can read them.
|
|
50
|
+
const g = globalThis;
|
|
51
|
+
g.__sh3__ = api;
|
|
52
|
+
g.__sh3_svelte__ = svelte;
|
|
53
|
+
g.__sh3_svelte_internal_client__ = svelteInternalClient;
|
|
54
|
+
// Build shim URLs once at module load time.
|
|
55
|
+
const shimUrls = {
|
|
56
|
+
'sh3-core': createShimUrl('__sh3__', api),
|
|
57
|
+
'svelte': createShimUrl('__sh3_svelte__', svelte),
|
|
58
|
+
'svelte/internal/client': createShimUrl('__sh3_svelte_internal_client__', svelteInternalClient),
|
|
59
|
+
};
|
|
60
|
+
// `svelte/internal/disclose-version` is a side-effect-only import (no exports).
|
|
61
|
+
// Create an empty shim so the import doesn't fail.
|
|
62
|
+
shimUrls['svelte/internal/disclose-version'] = URL.createObjectURL(new Blob([''], { type: 'application/javascript' }));
|
|
63
|
+
// `sh3/tokens.css` — design tokens are already loaded by the host shell.
|
|
64
|
+
// Bundles that import this path get a no-op so the import doesn't fail.
|
|
65
|
+
shimUrls['sh3-core/tokens.css'] = URL.createObjectURL(new Blob([''], { type: 'application/javascript' }));
|
|
66
|
+
// ---- bare specifier rewriting ----------------------------------------------
|
|
67
|
+
/**
|
|
68
|
+
* Match bare specifier imports/re-exports:
|
|
69
|
+
* from 'svelte/internal/client'
|
|
70
|
+
* from "sh3-core"
|
|
71
|
+
* import "svelte/internal/disclose-version"
|
|
72
|
+
*
|
|
73
|
+
* Captures the quote char and the specifier. Only rewrites specifiers
|
|
74
|
+
* that have a shim URL registered above.
|
|
75
|
+
*/
|
|
76
|
+
const BARE_IMPORT_RE = /(?:from|import)\s*(['"])(sh3-core(?:\/[^'"]*)?|svelte(?:\/[^'"]*)?)\1/g;
|
|
77
|
+
function rewriteBareImports(source) {
|
|
78
|
+
return source.replace(BARE_IMPORT_RE, (match, _quote, specifier) => {
|
|
79
|
+
const shim = shimUrls[specifier];
|
|
80
|
+
if (!shim)
|
|
81
|
+
return match; // unknown specifier — leave as-is
|
|
82
|
+
// Preserve the keyword (from vs import) from the original match.
|
|
83
|
+
const keyword = match.startsWith('from') ? 'from' : 'import';
|
|
84
|
+
return `${keyword} '${shim}'`;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Load a pre-built ESM bundle from raw bytes.
|
|
89
|
+
*
|
|
90
|
+
* Scans all module exports (default + named) for objects with a `manifest`
|
|
91
|
+
* property and classifies each as a shard or app. Combo bundles that export
|
|
92
|
+
* both a shard and its companion app are supported.
|
|
93
|
+
*
|
|
94
|
+
* @param bytes - Raw bundle bytes (the verified ESM file contents).
|
|
95
|
+
* @returns All shards and apps found in the module's exports.
|
|
96
|
+
* @throws If the blob URL cannot be created or the import fails.
|
|
97
|
+
*/
|
|
98
|
+
export async function loadBundleModule(bytes) {
|
|
99
|
+
const source = new TextDecoder().decode(bytes);
|
|
100
|
+
const rewritten = rewriteBareImports(source);
|
|
101
|
+
const blob = new Blob([rewritten], { type: 'application/javascript' });
|
|
102
|
+
const url = URL.createObjectURL(blob);
|
|
103
|
+
try {
|
|
104
|
+
const mod = await import(/* @vite-ignore */ url);
|
|
105
|
+
const result = { shards: [], apps: [] };
|
|
106
|
+
for (const value of Object.values(mod)) {
|
|
107
|
+
if (!value || typeof value !== 'object' || !('manifest' in value)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const candidate = value;
|
|
111
|
+
if (isShard(candidate))
|
|
112
|
+
result.shards.push(candidate);
|
|
113
|
+
else if (isApp(candidate))
|
|
114
|
+
result.apps.push(candidate);
|
|
115
|
+
}
|
|
116
|
+
if (result.shards.length === 0 && result.apps.length === 0) {
|
|
117
|
+
throw new Error('Bundle has no exports with a "manifest" property — expected at least one shard or app');
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
URL.revokeObjectURL(url);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Type guard: returns true if the loaded module is a shard.
|
|
127
|
+
*
|
|
128
|
+
* A shard has an `activate` function and `manifest.views` array. An app
|
|
129
|
+
* has neither — it has `initialLayout` and `manifest.requiredShards`.
|
|
130
|
+
*/
|
|
131
|
+
export function isShard(mod) {
|
|
132
|
+
return ('activate' in mod &&
|
|
133
|
+
typeof mod.activate === 'function' &&
|
|
134
|
+
Array.isArray(mod.manifest.views));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Type guard: returns true if the loaded module is an app.
|
|
138
|
+
*
|
|
139
|
+
* An app has `initialLayout` and `manifest.requiredShards`. A shard has
|
|
140
|
+
* `activate` and `manifest.views` instead.
|
|
141
|
+
*/
|
|
142
|
+
export function isApp(mod) {
|
|
143
|
+
return ('initialLayout' in mod &&
|
|
144
|
+
Array.isArray(mod.manifest.requiredShards));
|
|
145
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime validation for registry index JSON.
|
|
3
|
+
*
|
|
4
|
+
* No external schema library — plain runtime checks. The registry index
|
|
5
|
+
* is fetched from untrusted URLs so all fields are validated before any
|
|
6
|
+
* typed access occurs. Validation errors carry a JSON-path string so the
|
|
7
|
+
* caller can surface actionable diagnostics in the store UI.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```ts
|
|
11
|
+
* const raw = await response.json();
|
|
12
|
+
* const index = validateRegistryIndex(raw); // throws RegistryValidationError on bad input
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import type { RegistryIndex } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* Thrown when a registry index document fails validation.
|
|
18
|
+
*
|
|
19
|
+
* The `path` property is a JSON-path string pointing to the field that
|
|
20
|
+
* caused the failure (e.g. `"$.packages[2].versions[0].integrity"`).
|
|
21
|
+
* Consumers may display this directly in diagnostic/error UI.
|
|
22
|
+
*/
|
|
23
|
+
export declare class RegistryValidationError extends Error {
|
|
24
|
+
/**
|
|
25
|
+
* JSON-path of the failing field (e.g. `"$.packages[0].type"`).
|
|
26
|
+
*/
|
|
27
|
+
readonly path: string;
|
|
28
|
+
/**
|
|
29
|
+
* @param path - JSON-path string of the failing field.
|
|
30
|
+
* @param message - Human-readable description of why validation failed.
|
|
31
|
+
*/
|
|
32
|
+
constructor(path: string, message: string);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate an unknown value as a `RegistryIndex`.
|
|
36
|
+
*
|
|
37
|
+
* Performs a full deep validation of all required fields and their types.
|
|
38
|
+
* Returns a correctly typed `RegistryIndex` on success.
|
|
39
|
+
* Throws `RegistryValidationError` on the first validation failure.
|
|
40
|
+
*
|
|
41
|
+
* Call this immediately after `response.json()` before touching any field.
|
|
42
|
+
*
|
|
43
|
+
* @param data - The raw parsed JSON value from a registry fetch.
|
|
44
|
+
* @returns A fully validated `RegistryIndex`.
|
|
45
|
+
* @throws `RegistryValidationError` if any field is missing or has the wrong type.
|
|
46
|
+
*/
|
|
47
|
+
export declare function validateRegistryIndex(data: unknown): RegistryIndex;
|