Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **@objectstack v3.0.4 Upgrade**: Upgraded all `@objectstack/*` packages from `^3.0.2` to `^3.0.4` across 42 references
- **@objectstack v3.0.0 Upgrade**: Upgraded all `@objectstack/*` packages from `^2.0.7` to `^3.0.0` across 13 package.json files
- **Breaking change migrations**:
- `Hub` namespace → `Cloud` in @object-ui/types re-exports
Expand All @@ -22,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Preview Mode** (`@object-ui/auth`): New `previewMode` prop on `AuthProvider` for auto-login with simulated identity — skip login/registration for marketplace demos and app showcases. Includes `PreviewBanner` component, `isPreviewMode` / `previewMode` on `useAuth()`, and `PreviewModeOptions` type. Aligns with `@objectstack/spec` kernel `PreviewModeConfig`.
- **Discovery Preview Detection** (`@object-ui/react`): Extended `DiscoveryInfo` with `mode` and `previewMode` fields for server-driven preview mode detection.
- **Console Preview Support**: `ConditionalAuthWrapper` auto-detects `mode === 'preview'` from server discovery and configures auth accordingly.
- **Console Bundle Optimization**: Split monolithic 3.7 MB main chunk into 17 granular cacheable chunks via `manualChunks` — main entry reduced from 1,008 KB gzip to 48.5 KB gzip (95% reduction)
- **Gzip + Brotli Compression**: Pre-compressed assets via `vite-plugin-compression2` — Brotli main entry at 40 KB
- **Bundle Analysis**: Added `rollup-plugin-visualizer` generating interactive treemap at `dist/stats.html`; new `build:analyze` script
Expand Down
14 changes: 7 additions & 7 deletions apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@
"@object-ui/plugin-view": "workspace:*",
"@object-ui/react": "workspace:*",
"@object-ui/types": "workspace:*",
"@objectstack/cli": "^3.0.2",
"@objectstack/client": "^3.0.2",
"@objectstack/driver-memory": "^3.0.2",
"@objectstack/objectql": "^3.0.2",
"@objectstack/plugin-msw": "^3.0.2",
"@objectstack/runtime": "^3.0.2",
"@objectstack/spec": "^3.0.2",
"@objectstack/cli": "^3.0.4",
"@objectstack/client": "^3.0.4",
"@objectstack/driver-memory": "^3.0.4",
"@objectstack/objectql": "^3.0.4",
"@objectstack/plugin-msw": "^3.0.4",
"@objectstack/runtime": "^3.0.4",
"@objectstack/spec": "^3.0.4",
"@tailwindcss/postcss": "^4.1.18",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
Expand Down
3 changes: 2 additions & 1 deletion apps/console/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { SchemaRendererProvider } from '@object-ui/react';
import { ObjectStackAdapter } from './dataSource';
import type { ConnectionState } from './dataSource';
import appConfig from '../objectstack.shared';
import { AuthGuard, useAuth } from '@object-ui/auth';
import { AuthGuard, useAuth, PreviewBanner } from '@object-ui/auth';

// Components (eagerly loaded — always needed)
import { ConsoleLayout } from './components/ConsoleLayout';
Expand Down Expand Up @@ -387,6 +387,7 @@ export function App() {
<ThemeProvider defaultTheme="system" storageKey="object-ui-theme">
<ConsoleToaster position="bottom-right" />
<ConditionalAuthWrapper authUrl="/api/auth">
<PreviewBanner />
<BrowserRouter basename={import.meta.env.BASE_URL?.replace(/\/$/, '') || '/'}>
<Suspense fallback={<LoadingScreen />}>
<Routes>
Expand Down
38 changes: 32 additions & 6 deletions apps/console/src/components/ConditionalAuthWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
*
* This component fetches discovery information from the server and conditionally
* enables/disables authentication based on the server's auth service status.
* Also detects preview mode from the server and configures the auth provider accordingly.
*/

import { useState, useEffect, ReactNode } from 'react';
import { ObjectStackAdapter } from '../dataSource';
import { AuthProvider } from '@object-ui/auth';
import type { PreviewModeOptions } from '@object-ui/auth';
import { LoadingScreen } from './LoadingScreen';
import type { DiscoveryInfo } from '@object-ui/react';

Expand All @@ -23,11 +25,12 @@ interface ConditionalAuthWrapperProps {
* 1. Creates a temporary data source connection
* 2. Fetches discovery information from the server
* 3. Checks if auth.enabled is true in the discovery response
* 4. Conditionally wraps children with AuthProvider if auth is enabled
* 5. Bypasses auth if discovery indicates auth is disabled (development/demo mode)
* 4. Detects preview mode from discovery (mode === 'preview')
* 5. Conditionally wraps children with AuthProvider with the appropriate config
*/
export function ConditionalAuthWrapper({ children, authUrl }: ConditionalAuthWrapperProps) {
const [authEnabled, setAuthEnabled] = useState<boolean | null>(null);
const [previewMode, setPreviewMode] = useState<PreviewModeOptions | undefined>(undefined);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
Expand All @@ -47,10 +50,24 @@ export function ConditionalAuthWrapper({ children, authUrl }: ConditionalAuthWra
const discovery = await adapter.getDiscovery() as DiscoveryInfo | null;

if (!cancelled) {
// Check if auth is enabled in discovery
// Default to true if discovery doesn't provide this information
const isAuthEnabled = discovery?.services?.auth?.enabled ?? true;
setAuthEnabled(isAuthEnabled);
// Detect preview mode from discovery
if (discovery?.mode === 'preview') {
setPreviewMode({
autoLogin: discovery.previewMode?.autoLogin ?? true,
simulatedRole: discovery.previewMode?.simulatedRole ?? 'admin',
simulatedUserName: discovery.previewMode?.simulatedUserName ?? 'Preview User',
readOnly: discovery.previewMode?.readOnly ?? false,
expiresInSeconds: discovery.previewMode?.expiresInSeconds ?? 0,
bannerMessage: discovery.previewMode?.bannerMessage,
});
// In preview mode, auth is effectively bypassed
setAuthEnabled(false);
} else {
// Check if auth is enabled in discovery
// Default to true if discovery doesn't provide this information
const isAuthEnabled = discovery?.services?.auth?.enabled ?? true;
setAuthEnabled(isAuthEnabled);
}
}
} catch (error) {
if (!cancelled) {
Expand All @@ -76,6 +93,15 @@ export function ConditionalAuthWrapper({ children, authUrl }: ConditionalAuthWra
return <LoadingScreen />;
}

// If in preview mode, wrap with a preview-configured AuthProvider
if (previewMode) {
return (
<AuthProvider authUrl={authUrl} previewMode={previewMode}>
{children}
</AuthProvider>
);
}

// If auth is enabled, wrap with AuthProvider
if (authEnabled) {
return (
Expand Down
2 changes: 1 addition & 1 deletion examples/crm/console-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
* import { ConsolePlugin } from './console-plugin';
* kernel.use(ConsolePlugin);
*/
export { ConsolePlugin } from '@object-ui/console';
export { ConsolePlugin } from '../../apps/console/plugin';
2 changes: 1 addition & 1 deletion examples/crm/objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { OrderObject } from './src/objects/order.object';
import { UserObject } from './src/objects/user.object';
import { ProjectObject } from './src/objects/project.object';
import { EventObject } from './src/objects/event.object';
import { ConsolePlugin } from '@object-ui/console';
import { ConsolePlugin } from '../../apps/console/plugin';

export default defineStack({
objects: [
Expand Down
17 changes: 8 additions & 9 deletions examples/crm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,19 @@
},
"dependencies": {
"@hono/node-server": "^1.19.9",
"@object-ui/console": "workspace:*",
"@objectstack/core": "^3.0.2",
"@objectstack/driver-memory": "^3.0.2",
"@objectstack/objectql": "^3.0.2",
"@objectstack/plugin-auth": "^3.0.2",
"@objectstack/plugin-hono-server": "^3.0.2",
"@objectstack/runtime": "^3.0.2",
"@objectstack/spec": "^3.0.2",
"@objectstack/core": "^3.0.4",
"@objectstack/driver-memory": "^3.0.4",
"@objectstack/objectql": "^3.0.4",
"@objectstack/plugin-auth": "^3.0.4",
"@objectstack/plugin-hono-server": "^3.0.4",
"@objectstack/runtime": "^3.0.4",
"@objectstack/spec": "^3.0.4",
"hono": "^4.11.9",
"pino": "^8.21.0",
"pino-pretty": "^13.1.3"
},
"devDependencies": {
"@objectstack/cli": "^3.0.2",
"@objectstack/cli": "^3.0.4",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
Expand Down
2 changes: 1 addition & 1 deletion examples/crm/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { InMemoryDriver } from '@objectstack/driver-memory';
import { AuthPlugin } from '@objectstack/plugin-auth';
import config from './objectstack.config';
import { pino } from 'pino';
import { ConsolePlugin } from '@object-ui/console';
import { ConsolePlugin } from '../../apps/console/plugin';

async function startServer() {
const logger = pino({
Expand Down
4 changes: 2 additions & 2 deletions examples/kitchen-sink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
"build": "objectstack compile objectstack.config.ts"
},
"dependencies": {
"@objectstack/spec": "^3.0.2"
"@objectstack/spec": "^3.0.4"
},
"devDependencies": {
"@objectstack/cli": "^3.0.2",
"@objectstack/cli": "^3.0.4",
"typescript": "^5.9.3"
}
}
12 changes: 6 additions & 6 deletions examples/msw-todo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
},
"dependencies": {
"@object-ui/example-todo": "workspace:*",
"@objectstack/client": "^3.0.2",
"@objectstack/driver-memory": "^3.0.2",
"@objectstack/objectql": "^3.0.2",
"@objectstack/plugin-msw": "^3.0.2",
"@objectstack/runtime": "^3.0.2",
"@objectstack/spec": "^3.0.2",
"@objectstack/client": "^3.0.4",
"@objectstack/driver-memory": "^3.0.4",
"@objectstack/objectql": "^3.0.4",
"@objectstack/plugin-msw": "^3.0.4",
"@objectstack/runtime": "^3.0.4",
"@objectstack/spec": "^3.0.4",
"react": "19.2.4",
"react-dom": "19.2.4"
},
Expand Down
6 changes: 3 additions & 3 deletions examples/todo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"build": "objectstack compile objectstack.config.ts"
},
"dependencies": {
"@objectstack/client": "^3.0.2",
"@objectstack/spec": "^3.0.2"
"@objectstack/client": "^3.0.4",
"@objectstack/spec": "^3.0.4"
},
"devDependencies": {
"@objectstack/cli": "^3.0.2",
"@objectstack/cli": "^3.0.4",
"typescript": "^5.9.3"
}
}
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@
"devDependencies": {
"@changesets/cli": "^2.29.8",
"@eslint/js": "^9.39.2",
"@objectstack/cli": "^3.0.2",
"@objectstack/core": "^3.0.2",
"@objectstack/driver-memory": "^3.0.2",
"@objectstack/objectql": "^3.0.2",
"@objectstack/plugin-msw": "^3.0.2",
"@objectstack/runtime": "^3.0.2",
"@objectstack/spec": "^3.0.2",
"@objectstack/cli": "^3.0.4",
"@objectstack/core": "^3.0.4",
"@objectstack/driver-memory": "^3.0.4",
"@objectstack/objectql": "^3.0.4",
"@objectstack/plugin-msw": "^3.0.4",
"@objectstack/runtime": "^3.0.4",
"@objectstack/spec": "^3.0.4",
"@playwright/test": "^1.58.2",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
Expand Down Expand Up @@ -132,8 +132,8 @@
},
"dependencies": {
"@hono/node-server": "^1.19.9",
"@objectstack/plugin-hono-server": "^3.0.2",
"@objectstack/studio": "^3.0.2",
"@objectstack/plugin-hono-server": "^3.0.4",
"@objectstack/studio": "^3.0.4",
"coverage-v8": "0.0.1-security",
"hono": "^4.11.9",
"pino": "^8.21.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/auth/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@

- @object-ui/types@3.0.1

### Added

- **Preview Mode** (`previewMode` prop on `AuthProvider`): Auto-login with simulated identity for marketplace demos and app showcases. Configurable role, display name, session expiry, read-only mode, and banner message.
- **PreviewBanner** component: Renders a status banner when preview mode is active.
- `isPreviewMode` and `previewMode` fields exposed on `AuthContextValue` / `useAuth()` hook.
- New `PreviewModeOptions` type mirroring spec's `PreviewModeConfig`.

### Changed

- Upgraded `@objectstack/spec` from `^3.0.2` to `^3.0.4`.

## 3.0.0

### Minor Changes
Expand Down
94 changes: 93 additions & 1 deletion packages/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Authentication system for Object UI — AuthProvider, guards, login/register for
- 👤 **UserMenu** - Display authenticated user info with sign-out support
- 🔑 **Auth Client Factory** - `createAuthClient` for pluggable backend integration
- 🌐 **Authenticated Fetch** - `createAuthenticatedFetch` for automatic token injection
- 👀 **Preview Mode** - Auto-login with simulated identity for marketplace demos and app showcases
- 🎯 **Type-Safe** - Full TypeScript support with exported types

## Installation
Expand Down Expand Up @@ -71,9 +72,31 @@ Wraps your application with authentication context:
Hook for accessing auth state and methods:

```tsx
const { user, session, signIn, signOut, signUp, isAuthenticated, isLoading } = useAuth();
const {
user,
session,
signIn,
signOut,
signUp,
isAuthenticated,
isLoading,
isPreviewMode,
previewMode,
} = useAuth();
```

| Property | Type | Description |
| --- | --- | --- |
| `user` | `AuthUser \| null` | Current authenticated user |
| `session` | `AuthSession \| null` | Current session information |
| `isAuthenticated` | `boolean` | Whether the user is authenticated |
| `isLoading` | `boolean` | Whether auth state is loading |
| `isPreviewMode` | `boolean` | Whether the app is running in preview mode |
| `previewMode` | `PreviewModeOptions \| null` | Preview mode configuration (only set when `isPreviewMode` is true) |
| `signIn` | `(email, password) => Promise` | Sign in with credentials |
| `signOut` | `() => Promise` | Sign out the current user |
| `signUp` | `(name, email, password) => Promise` | Register a new user |

### AuthGuard

Protects children from unauthenticated access:
Expand Down Expand Up @@ -110,6 +133,75 @@ Creates a fetch wrapper that injects auth tokens into DataSource requests:
const authedFetch = createAuthenticatedFetch({ getToken: () => session.token });
```

## Preview Mode

Preview mode allows visitors (e.g. marketplace customers) to explore the platform without registering or logging in. The `AuthProvider` auto-authenticates with a simulated user identity and bypasses login/registration screens.

This feature aligns with the `PreviewModeConfig` from `@objectstack/spec/kernel` ([spec PR #676](https://github.com/objectstack-ai/spec/pull/676)).

### Usage

```tsx
import { AuthProvider, PreviewBanner } from '@object-ui/auth';

function App() {
return (
<AuthProvider
authUrl="/api/auth"
previewMode={{
simulatedRole: 'admin',
simulatedUserName: 'Demo Admin',
readOnly: false,
bannerMessage: 'You are exploring a demo — data will be reset periodically.',
}}
>
<PreviewBanner />
<Dashboard />
</AuthProvider>
);
}
```

### PreviewModeOptions

| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `autoLogin` | `boolean` | `true` | Auto-login as simulated user, skipping login/registration pages |
| `simulatedRole` | `'admin' \| 'user' \| 'viewer'` | `'admin'` | Permission role for the simulated preview user |
| `simulatedUserName` | `string` | `'Preview User'` | Display name for the simulated preview user |
| `readOnly` | `boolean` | `false` | Restrict the preview session to read-only operations |
| `expiresInSeconds` | `number` | `0` | Preview session duration in seconds (0 = no expiration) |
| `bannerMessage` | `string` | — | Banner message displayed in the UI during preview mode |

### PreviewBanner

A component that renders a status banner when preview mode is active. Shows `bannerMessage` from the preview config, or a default message.

```tsx
import { PreviewBanner } from '@object-ui/auth';

// Only renders when isPreviewMode is true
<PreviewBanner />
```

### Detecting Preview Mode

Use the `useAuth` hook to check if the app is in preview mode:

```tsx
function MyComponent() {
const { isPreviewMode, previewMode } = useAuth();

if (isPreviewMode && previewMode?.readOnly) {
// Disable write operations
}

return <div>...</div>;
}
```

> **⚠️ Security:** Preview mode should **never** be used in production environments.

## License

MIT
Loading