ugly-app 0.1.521 → 0.1.523

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 CHANGED
@@ -236,7 +236,7 @@ export type AppPages = typeof pages;
236
236
 
237
237
  ### `bootstrapApp()`
238
238
 
239
- The recommended entrypoint. Handles auth detection, socket creation, optional auto-login through the ugly.bot iframe, and provider wiring.
239
+ The recommended entrypoint. Handles auth detection, socket creation, silent auto-login through the ugly.bot iframe (Mode A only), and provider wiring.
240
240
 
241
241
  ```tsx
242
242
  // client/main.tsx
@@ -272,7 +272,9 @@ bootstrapApp({
272
272
  | `strings?` | Localization config — when present, wraps the tree with `<StringsProvider>`. |
273
273
  | `keyboard?: false` | Disable the framework `<KeyboardProvider>` wrapper. |
274
274
 
275
- `bootstrapApp` reads `window.__AUTH_TOKEN__` (injected by the server). If absent, it renders unauthenticated and lets the router show `loginFallback` for auth-guarded pages. If present, it connects the socket, mounts `<AppProvider>`, and renders.
275
+ `bootstrapApp` reads `window.__AUTH_TOKEN__` (injected by the server). If absent, it renders unauthenticated and lets the router's per-route auth guard surface the framework's `<AuthRoot>` (Mode A → `<LoginPopup>`; Mode B → magic-link form + optional Google button). If the token is present, it connects the socket, mounts `<AppProvider>`, and renders.
276
+
277
+ If `bootstrapApp` is loaded at `/auth/magic-link/verify` (Mode B), it renders the `<MagicLinkCallback>` component instead of the regular app shell — that path is the fallback target when the server route is shadowed by a static-assets handler.
276
278
 
277
279
  ### Routing — `createRouter()`
278
280
 
@@ -290,8 +292,8 @@ export const { RouterProvider, RouterView, useRouter } = createRouter({
290
292
 
291
293
  `createRouter` returns:
292
294
 
293
- - **`RouterProvider`** — props: `children`, `fallback?`, `loginFallback?`, `isAuthenticated?`, `autoLogin?`. Manages route state, browser history, popups, and `<AutoLoginGate>` (silent iframe-based login check against ugly.bot).
294
- - **`RouterView`** — renders the active page with animated transitions. Props: `durationMs?`, `easing?`, `transitionComponent?` (replaces `ViewFlipper`), `renderPage?` (sync alternative to `allPages` loaders).
295
+ - **`RouterProvider`** — props: `children`, `fallback?`, `isAuthenticated?`. Manages route state, browser history, popups, and the silent auto-login iframe (`<AutoLoginGate>` only in Mode A; skipped when `window.__UGLY_APP_AUTH_MODE__ === 'self'`). When `isAuthenticated()` returns false and the current route declares `auth: true`, the framework's `<AuthRoot>` is rendered in place of the page — apps cannot override this fallback.
296
+ - **`RouterView`** — renders the active page with animated transitions. Props: `durationMs?`, `easing?`, `transitionComponent?` (replaces `ViewFlipper`), `renderPage?` (sync alternative to `allPages` loaders, called with `RouterStateRaw`).
295
297
  - **`useRouter()`** — returns the router context (see below).
296
298
 
297
299
  ### Page map — `lazyPage` / `lazyPageLoader`
@@ -378,13 +380,13 @@ const {
378
380
  showPopup, // legacy popup API (prefer useRouter().openPopup)
379
381
  hidePopup,
380
382
  hideAllPopups,
381
- runAsync, // runAsync('label', async () => { ... }) — shows loading overlay
383
+ runAsync, // runAsync(label, async () => { ... }, options?) — shows loading overlay while pending
382
384
  splashDone, // mark a splash-screen step complete
383
385
  localizer, // (key, params?) => string — alias for useLocalizer
384
386
  } = useApp();
385
387
  ```
386
388
 
387
- `useAppOptional()` returns `null` outside the provider; `useLocalizer()` returns a localizer that falls back to identity.
389
+ `useApp` is generic in `TAsyncOptions` so apps can pass an option type through to a custom `loadingOverlay` element. `useAppOptional()` returns `null` outside the provider; `useLocalizer()` returns a localizer that prefers `<StringsProvider>` data, falls back to the `AppProvider` localizer prop, and finally to identity.
388
390
 
389
391
  ### `Link` component
390
392
 
@@ -419,35 +421,59 @@ For pure HTTP (no WebSocket), use `createHttpClient({ requests, token?, baseUrl?
419
421
 
420
422
  ugly-app uses HttpOnly cookies and server-side JWT injection — no `localStorage`, no client-side token handling.
421
423
 
422
- **Flow:**
424
+ Two modes, picked from the `auth` block in the project's `.uglyapp` config:
425
+
426
+ - **Mode A — `mode: 'uglybot'`** (default when no `auth` block is present). Auth is delegated to ugly.bot OAuth. AI / email / push proxies bill the end user's ugly.bot credits.
427
+ - **Mode B — `mode: 'self'`**. The app issues its own sessions via magic-link email and/or Google OAuth. AI / email / push proxies bill the developer's ugly.bot account using the project's `AI_PROXY_TOKEN`.
428
+
429
+ `window.__UGLY_APP_AUTH_MODE__` is injected into every page so client code (incl. `<AuthRoot>` and the router's `<AutoLoginGate>`) can branch correctly.
430
+
431
+ ### Mode A — ugly.bot OAuth (default)
432
+
433
+ 1. Unauthenticated user lands on a page. The router's `<AutoLoginGate>` opens a hidden iframe to `${UGLY_BOT_URL}/iframe-auth` to check for an existing platform session (4 s timeout).
434
+ 2. If found, the iframe `postMessage`s an OAuth code back. The client POSTs to `/auth/verify`; the server exchanges it through `uglyBotAuthProvider`, sets the `auth_token` HttpOnly cookie, and reloads.
435
+ 3. If not found (or timeout), per-route auth checks surface the framework's `<AuthRoot>` → `<LoginPopup>`, which opens `${UGLY_BOT_URL}/oauth` in a popup window.
436
+ 4. On every authenticated page request, the server verifies the cookie token by calling `${UGLY_BOT_URL}/verify`, then injects `window.__AUTH_TOKEN__` into the HTML so the client can attach it to the WebSocket handshake.
423
437
 
424
- 1. Unauthenticated user lands on a page; the optional `<AutoLoginGate>` opens a hidden iframe to `https://ugly.bot/iframe-auth` to check for an existing platform session.
425
- 2. If found, the iframe posts an OAuth code back; the client POSTs to `/auth/verify`; the server exchanges it through `uglyBotAuthProvider`, sets the `auth_token` HttpOnly cookie, and the page reloads authenticated.
426
- 3. If not found (or timeout after 4 s), `<RouterProvider>` shows `loginFallback` for auth-required routes. The default fallback is `<LoginPopup>`, which opens `https://ugly.bot/oauth` in a popup window.
427
- 4. On every authenticated request, the server verifies the cookie token by calling `${UGLY_BOT_URL}/verify` and injects `window.__AUTH_TOKEN__` into the HTML so the client can pass it on the WebSocket handshake.
438
+ ### Mode B Self-issued sessions
428
439
 
429
- **Token-in-URL embed mode:** any GET request with `?token=<JWT>` will, if the token verifies against ugly.bot, set the cookie and 302-redirect to the same URL without the token — letting any page be embedded in an iframe.
440
+ When `.uglyapp` sets `auth.mode: 'self'`, `buildAuth()` wires the magic-link provider as primary; if `providers.google.clientId` is configured it adds Google as an extra provider on the same router.
430
441
 
431
- **Built-in routes** (mounted on every app):
442
+ - `<AuthRoot>` renders `<MagicLinkForm>` (plus the Google button when configured) instead of `<LoginPopup>`.
443
+ - The `<AutoLoginGate>` is skipped entirely — the ugly.bot iframe would either silently fail or set a cookie that won't verify against the local session.
444
+ - `POST /auth/magic-link/request` accepts an email, mints a one-time token (default 15 min lifetime, configurable via `auth.magicLinkExpiresMin`), and emails a link.
445
+ - `GET /auth/magic-link/verify?token=…` validates the token, calls `onUserCreate` on first login, sets the session cookie, and redirects.
446
+ - Google login lives at `GET /auth/google/url` + `POST /auth/google/callback` when enabled.
447
+
448
+ ### Token-in-URL embed
449
+
450
+ Any GET request with `?token=<JWT>` will, if the token verifies, set the cookie and 302-redirect to the same URL without the token parameter — useful for embedding any page in an iframe.
451
+
452
+ ### Built-in routes
453
+
454
+ Mounted on every app:
432
455
 
433
456
  | Endpoint | Description |
434
457
  |----------|-------------|
435
458
  | `POST /auth/verify` | Exchange an OAuth `code` for a session cookie. |
436
459
  | `POST /auth/logout` | Clear the cookie. |
437
- | `GET /auth/token` | Refresh and return the current token (used by clients that need an explicit token). |
460
+ | `GET /auth/token` | Refresh and return the current token. |
438
461
  | `GET /auth/url` | Return the OAuth popup URL. |
462
+ | `POST /auth/magic-link/request` | Mode B only — send a magic-link email. |
463
+ | `GET /auth/magic-link/verify` | Mode B only — verify a magic-link token and set the cookie. |
464
+ | `GET /auth/google/url`, `POST /auth/google/callback` | Mode B only — Google OAuth, registered when configured. |
439
465
 
440
- **Custom provider:**
466
+ ### Custom provider
441
467
 
442
468
  ```typescript
443
469
  configurator.setAuth({
444
470
  verify: async (code) => ({ userId: '...', token: 'platform-issued-jwt' }),
445
471
  authUrl: (origin) => `https://my-oauth.example/authorize?origin=${origin}`,
446
- registerRoutes: (router) => { /* optional extra routes */ },
472
+ registerRoutes: (router, { db, onUserCreate }) => { /* extra routes */ },
447
473
  });
448
474
  ```
449
475
 
450
- **Server-side helpers** (`ugly-app`):
476
+ ### Server-side helpers (`ugly-app`)
451
477
 
452
478
  - `verifyToken(token)` — verifies a token against ugly.bot and returns the `userId`.
453
479
  - `getRequestUser(req)` — synchronous decode of the per-project `UGLY_PROJECT_TOKEN` cookie set by ugly.bot's wake-on-traffic gate. Returns `{ userId } | null` without a network round-trip; safe to use as the primary auth check in deployed-app handlers.
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.1.521";
1
+ export declare const CLI_VERSION = "0.1.523";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.521";
2
+ export const CLI_VERSION = "0.1.523";
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-app",
3
- "version": "0.1.521",
3
+ "version": "0.1.523",
4
4
  "type": "module",
5
5
  "main": "./dist/server/index.js",
6
6
  "exports": {
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.521";
2
+ export const CLI_VERSION = "0.1.523";