Host Startup, Installation & View Rendering
3. Application Startup Sequence
When the user launches Asyar, the startup process follows a strict sequence to guarantee that OS hooks, system services, and extensions are securely bolted together before the UI is presented:
- Rust Initialization (
main.rs->lib.rs:run()):- Tauri builder starts.
- Core plugins are initialized (
fs,http,global_shortcut,clipboard_manager,opener).
- Custom Protocol Registration:
.register_uri_scheme_protocol("asyar-extension", ...)sets up the handler for Tier 2 extension asset resolution.
- App Setup phase (
setup_app):- System tray icon and menu are configured.
- The main window (
SPOTLIGHT_LABEL) is retrieved and OS-specific composition (e.g., Vibrancy on macOS, Blur on Windows) is applied. - A global shortcut (defaulting to
Cmd+K/Ctrl+K) is registered to toggle window visibility.
- WebView Hydration (
+page.sveltemounted):- SvelteKit boots in the webview.
onMountcallsappInitializer.init().
- Auth Initialization:
authService.init()is called (Tauri-only, skipped in browser mode).- Loads encrypted token + cached entitlements from
auth.datviaauth_load_cachedTauri command. - If a cached session is found,
AuthState(Rust managed state) is populated immediately — the UI can show the logged-in state before any network call. - A background
auth_refresh_entitlementscall fetches fresh entitlements fromhttps://asyar.org/api/entitlements. On failure, cached entitlements are used (7-day grace period). If the cache is older than 7 days and the network is unreachable, entitlements are cleared and a non-blocking notification is shown.
- Theme Application (if persisted):
extensionManager.init()readssettings.appearance.activeThemeimmediately aftersettingsService.init().- If a theme ID is stored,
applyTheme(themeId)is called fire-and-forget (.catch()) so it does not block extension loading. - The Rust
get_theme_definitioncommand readstheme.jsonfrom the extension directory and returns the CSS variable map and font declarations to the frontend.
- Extension Discovery & Loading (
extensionLoaderService.ts):loadAllExtensions()is triggered.- Tier 1: Discovered synchronously via Vite's
import.meta.glob('/src/built-in-features/*/index.ts', { eager: true }). - Tier 2: Rust command
discover_extensionsis queried. Returns all installed extension records (manifest + enabled state + path). - Theme extensions (
type === "theme") are present in the discovered records but skipped byExtensionLoaderService— they have no JS module to load.
- Command Index Synchronization:
- Once all manifests are loaded,
ExtensionManager.syncCommandIndex()is fired. - All extension
commandsare formatted intocmd_${extension.id}_${command.id}objects. - The Rust-backed search engine updates the index via invoke hooks (
index_item,delete_item).
- Once all manifests are loaded,
- Ready State:
ExtensionManager.isReadyevaluates totrue.- The UI enables the search bar and waits for user input.
5. Extension Installation Pipeline
Two installation paths exist: store/URL download and local file install.
Path A — Store / URL download
When a user discovers a third-party extension in the Store:
- User Interaction: The user clicks "Install" in the
DetailView.svelteof the built-in Store. - Download Handlers: The UI invokes the Tauri command
install_extension_from_url. - Rust Processing:
- Downloads the extension archive from the provided URL using the
reqwestHTTP client. - Saves the downloaded payload to a temporary file (
NamedTempFile). - Verifies the SHA-256 checksum against the value stored in the Asyar Store API. Mismatches abort installation.
- Extracts the zip archive into
$APP_DATA/extensions/{extensionId}/.
- Downloads the extension archive from the provided URL using the
- Live Reload:
extensionLoaderService.loadAllExtensions()picks up the new extension without a restart. - Indexing:
ExtensionManager.syncCommandIndex()pushes the new commands into the Rust search index. - Failure Cleanup: If download or extraction fails, Rust purges the installation directory before returning an error.
Path B — Local file install (.asyar)
Users can also install from a local .asyar file (a renamed ZIP) via Settings → Extensions → Install from File.... This supports all extension types — view, result, and theme.
- User Interaction: User clicks "Install from File..." → native file picker filtered to
*.asyar. - Frontend: Calls
show_open_extension_dialogTauri command (returns the selected path), theninstall_extension_from_filewith that path. - Rust processing (
install_from_file):- Validates the file has a
.asyarextension. - Extracts the ZIP to a temp directory (reuses
extract_zip()with zip-slip protection). - Reads and validates
manifest.json(reusesread_manifest()+validate_compatibility()). - Runs
validate_package_structure():- Required fields:
id,name,version,type. - ID format: alphanumeric, hyphens, dots, underscores — no
... - Type-specific:
theme→ requirestheme.json;view/result→ requiresindex.html.
- Required fields:
- Runs
validate_theme_json()for theme packages: validates font file extensions (.woff2,.ttf,.otfonly), path traversal, and CSS injection in font family names. - Version conflict check: same ID + higher version → upgrade; same/lower → error; new ID → fresh install.
- Moves the extracted directory to
$APP_DATA/extensions/{id}/.
- Validates the file has a
- Live Reload: Same as Path A —
discover_extensionsevent triggers extension list refresh.
6. Extension View Rendering Pipeline
This flow details exactly how Asyar transitions from a user pressing Enter on a search result to rendering an extension view.
For Tier 1 (Built-in)
- User highlights a command in
<ResultsList>and hitsEnter(handleEnterKey/handleCommandAction). - The Action object invokes
extensionManager.handleCommandAction(commandObjectId). - The command handler matches the internal route and executes
this.navigateToView('clipboard-history/DefaultView'). - The
$activeViewstore (managed byviewManager) updates. - In
src/routes/+page.svelte, the main reactive block evaluatesisBuiltInExtension('clipboard-history'), yieldingtrue. - The frontend fetches the native ES module via
extensionManager.getLoadedExtensionModule('clipboard-history'). - The target Svelte class is extracted via standard fallback logic:
component = module?.[viewName] ?? module?.default?.[viewName] ?? module?.default; <svelte:component this={component} />mounts cleanly inside the main DOM.
For Tier 2 (Installed)
- User highlights a command for
xyz-pluginand hitsEnter. extensionManager.handleCommandAction()invokes the generic command handler hook.- Because evaluating
isBuiltInExtension('xyz-plugin')yieldsfalse, the fallback generic handler issuesthis.navigateToView('xyz-plugin/DefaultView'). - The
$activeViewstore updates. - In
src/routes/+page.svelte, becauseisBuiltInis false, it mounts<ExtensionIframe extensionId="xyz-plugin" view="xyz-plugin/DefaultView" />. ExtensionIframe.sveltereactively generates thesrcattribute string:asyar-extension://xyz-plugin/index.html?view=DefaultView- The
<iframe sandbox="allow-scripts allow-same-origin allow-forms allow-popups">DOM node is attached. - Protocol Interception (
lib.rs): The network request forasyar-extension://is intercepted by the Tauri Rust core. - Path Resolution:
- Rust strips query parameters (
?view=DefaultView) using.split('?').next().unwrap(). - It iterates through paths in a strict, security-focused priority order: it checks Host bundle resources (Priority 2) first, and only if not found does it check the user AppData directory (Priority 3). Priority 1 is reserved exclusively for a localized development source directory and is only evaluated in debug builds.
- Rust strips query parameters (
- Delivery: Rust reads
index.htmlfrom disk, attaches Content-Security-Policy headers, and sets the mime type (text/html), serving it as bytes directly to the iframe webview. - Extension Boot: The extension's
index.htmlexecutes its imported JS chunk (main.ts). - SDK Hook: Extension code executes
new ExtensionContext(), taking over the iframe window and establishing theMessageBrokerlistener back towindow.parent. - The extension's internal framework layout mounts, and the screen updates with external UI pixels.