# SWAI Web App (.swai) File Guide

This document explains how to create a SWAI Web App by writing a `.swai` YAML file. The file describes your web app's metadata, the main URL to load, and navigation/security behavior. SWAI reads this file to create an Electron-based window and enforce navigation rules.

## Field reference

Required
- app_name (string): Display name used for the window/titlebar.
- app_id (string): Unique identifier. Used for per-app data paths and window classing.
- main_url (string): Initial URL to load.
- allowed_urls (string[]): List of URL patterns the app is allowed to navigate to internally. Supports wildcards `*`. Outside URLs will redirect to a system browser.

Common optional
- not_allowed_urls (string[]): URL patterns that are explicitly disallowed (overrides allowed_urls). If a navigation matches here, SWAI will open it in the system browser instead of the app.
- icon_path/icon_url (string): Local path to an icon file. Alternatively, some test apps use icon_url. Either can be left empty.
- categories (string[]): FreeDesktop categories for desktop integration.
- keywords (string[]): Extra search keywords. Used for stillCenter.
- comment (string): Short description. Used for stillCenter.
- mimetypes (string[]): Declares mimetypes the app can handle (planned/optional). This does nothing yet, but will in the future.
- allow_multi_window (boolean, default true): Controls how new windows are opened. If true, links that match allowed_urls open in new app windows. If false, such links open in transient modal windows instead. This also controls whether users can open multiple instances of the same app.
- ecosystem (string): Groups related apps to share session/cookies and integrate with the SwaiRegistry (DBus). Apps in the same ecosystem share authentication and can open URLs in each other (e.g., Google Drive opening a document in Google Docs). When a URL is blocked but another ecosystem app can handle it, SWAI will route to that app instead of opening a browser.
- reuse_window_for_ecosystem_navigation (boolean, default false): When this app hands a URL to another app in the same ecosystem, reuse the current window instead of opening a new one. SWAI keeps the existing WebContents, switches to the destination app's navigation rules and metadata, changes the native window icon, and loads the requested URL. This is useful for sites whose actions depend on continuing in the same browser tab. The destination app controls subsequent handoffs after the switch.
- extensions (string[]): Reserved for future browser extension support.
- login_urls (string[]): URL patterns that trigger "login mode" for external authentication flows. When on a login URL, the app enters login mode which allows navigation to any URL (including external auth providers like Okta, SAML, etc.). Login mode stays active until the user returns to an allowed_urls page that is NOT a login URL. During login mode, navigation happens in the current window (not new windows) to maintain the auth flow.
- use_dynamic_titlebar_colors (boolean, default true): If true, SWAI will automatically detect and apply the website's theme color to the titlebar (similar to Safari's web app feature). Set to false to disable this feature and use the default system titlebar colors.
- use_manifest (boolean, default false): If true, SWAI will fetch the website's manifest.json and extend the app configuration with properties from it. Specifically, it will add URLs from `scope`, `scope_extensions`, and `start_url` to the allowed_urls list. This is useful for progressive web apps that define their navigation scope in the manifest.
- **custom_js** (string): Custom JavaScript injected into the web app once the DOM is fully loaded. This must be written directly in the `.swai` file as an inline raw string using YAML's multi-line format. 

Other notes parsed from the .swai file are stored but may be unused in the current codebase; they are kept for forward compatibility.

## Navigation behavior

SWAI enforces navigation rules at runtime:
- Will-navigate: If a navigation target does not pass the navigation check, it is prevented and opened either in the default system browser or another ecosystem app that can handle it.
- Did-navigate and Did-navigate-in-page: The window titlebar is updated to reflect the current page title, and the titlebar receives a url-changed event to update back/forward availability.

### Navigation rules (in order of priority)

1) **not_allowed_urls block**: If navigationUrl matches not_allowed_urls → Blocked. Opens in browser or another ecosystem app if one can handle it.

2) **Login mode allows all**: If login mode is active (currently in an external auth flow), all navigation is allowed until returning to a non-login allowed URL.

3) **Login URLs enable login mode**: If currentUrl matches login_urls → Login mode is enabled and navigation is allowed. This allows external authentication providers (like Okta, Google OAuth, etc.) to redirect through their auth flow.

4) **allowed/login URLs permit**: If navigationUrl matches allowed_urls OR login_urls → Allowed.

5) **Default block**: Otherwise → Blocked. Opens in browser or another ecosystem app if one can handle it.

### Login mode behavior

Login mode is a special state that allows navigation to any URL during authentication flows:
- **Enabled** when the user is on a URL matching `login_urls`
- **Stays active** as the user navigates through external auth providers (e.g., Okta → Identity Provider → Okta callback)
- **Disabled** when the user lands on an `allowed_urls` page that is NOT in `login_urls`
- Standard navigation during login mode stays in the current window, unless the page explicitly requests a new window (e.g.,target="_blank").

This ensures seamless third-party authentication without blocking external auth redirects.

### New window handling

When a page attempts to open a new window (via `window.open`, `target="_blank"`, etc.):
- If `allow_multi_window: true` AND the URL matches `allowed_urls` or `login_urls`→ Opens in a new app window
- If `allow_multi_window: false` OR URL doesn't match `allowed_urls` → Opens in a transient modal window
- If navigation is blocked → Opens in browser or ecosystem app

Tip: Always include all internal routes in allowed_urls. For SPAs, wildcard patterns like `https://app.example.com/*` are recommended.

## Custom JavaScript Injection
SWAI allows you to inject custom JavaScript into any web app automatically when the `dom-ready` event fires. 

Inline JavaScript Strings:
For quick, single-purpose scripts, write your JavaScript directly in the `.swai` file using YAML's multi-line string pipe (`|`):
```yaml
custom_js: |
  const banner = document.querySelector('.desktop-download-banner');
  if (banner) {
    banner.style.display = 'none';
    console.log('[SWAI] Desktop banner hidden');
  }
``` 

## Ecosystem behavior

If ecosystem is set, SWAI:
- Shares the session partition between all apps with the same ecosystem so cookies/auth persist across them.
- Registers an internal window title via SwaiRegistry, while keeping a live display title in the UI.
- Routes a handoff in the current window when the initiating app sets `reuse_window_for_ecosystem_navigation: true`; otherwise it opens or focuses the destination app's window as usual.

Example:
```yaml
app_name: My Documents
app_id: com.example.documents
main_url: https://docs.example.com/
allowed_urls:
  - https://docs.example.com/*
ecosystem: example-suite
reuse_window_for_ecosystem_navigation: true
```

This enables seamless SSO and cohesive behavior across a suite (e.g., Microsoft 365 apps).

## Manifest integration

When `use_manifest: true` is set, SWAI will fetch the website's web app manifest (manifest.json) after the page loads and extend the app's configuration with the following properties:

- **scope**: Defines the navigation scope for the web app. SWAI adds this as a wildcard pattern to allowed_urls.
- **scope_extensions**: An array of additional origins that the web app can navigate to. Each origin is added to allowed_urls.
- **start_url**: The preferred URL to launch the app. Added to allowed_urls if not already present.

This is particularly useful for Progressive Web Apps (PWAs) that define their navigation boundaries in the manifest rather than requiring manual configuration.

Example:
```yaml
app_name: My PWA
app_id: com.example.mypwa
main_url: https://example.com/app/
allowed_urls:
  - "https://example.com/*"
use_manifest: true  # Will automatically add scope and scope_extensions from manifest
```

Note: The manifest properties **extend** the existing allowed_urls—they don't replace them. Your .swai file configuration takes precedence.

### Offline PWAs

Offline support is automatic for PWAs that register a service worker and cache an
offline experience. SWAI stores service workers, Cache Storage, IndexedDB, and other
site data in the app's persistent session. It also remembers registered worker scopes
and starts the matching worker before the app's first navigation on later launches.
This allows a cached PWA to handle a cold start without a network connection. If the
PWA has no cached response, SWAI shows its native **No Network Connection** page.

## Dynamic titlebar colors

SWAI automatically detects and applies the website's theme color to the titlebar, similar to Safari's web app feature. The detection order is:

1. `<meta name="theme-color">` tags (with media query support for light/dark modes)
2. Fixed/sticky header elements at the top of the page
3. Theme color from the web app manifest
4. Background color from the `<body>` or `<html>` element

The titlebar color updates dynamically as you navigate or when the page changes (scroll, resize, DOM mutations).

To disable this feature for a specific app, set `use_dynamic_titlebar_colors: false` in your .swai file.

## Best practices

- Use broad but safe allowed_urls patterns to cover all in-app routes you need.
- Add not_allowed_urls for domains you explicitly want opening in the external browser.
- Include login_urls for your provider's auth endpoints to ensure multi-step auth can redirect properly.
- Keep app_id unique and stable; changing it will create a new data/session partition.

## Troubleshooting

- App says that browser is not supported
  - You might want to use a custom user agent by adding `custom_user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.7444.235 Safari/537.36"` to your `.swai` file.
  - The default user agent that SWAI uses is highly compatible with most sites, but some websites may block it.
  - Note: Using this custom user agent may cause problems with signing in with Google.

- Links open in the external browser unexpectedly
  - Ensure the navigationUrl is covered by allowed_urls (and not blocked by not_allowed_urls).
  - If using SSO/OAuth, add your auth provider's URLs to login_urls (e.g., `https://login.okta.com/*`, `https://accounts.google.com/*`).
  - Check console logs for messages like "Navigation to X blocked" to diagnose the issue.

- Login flow keeps getting blocked
  - Add all URLs in the auth flow to login_urls. Common patterns include:
    - `https://login.microsoftonline.com/*`
    - `https://accounts.google.com/*`
    - `https://*.okta.com/*`
    - `https://auth.yourcompany.com/*`
  - Login mode activates when you're on a login_urls page and stays active until you return to a non-login allowed page.

- New windows don't open as separate app windows
  - Set allow_multi_window: true. When false, SWAI uses transient modal windows for window.open calls.
  - Even with allow_multi_window: true, URLs must match allowed_urls to open in a new app window.

- App doesn't retain login state across related apps (e.g., Word → Excel)
  - Set the same ecosystem value for related apps to share sessions and cookies.
  - Ensure all related apps have the same ecosystem string (e.g., `ecosystem: microsoft365`).

## Creating and placing the file

1) Create a file named something like myapp.swai with the YAML content above.
2) Place it in your chosen directory (e.g., testapps/ during development).
3) Launch SWAI and point it at your .swai file or include it in your registry as configured in your environment.

That's it—your SWAI Web App will start with the configured URL and navigation rules.
