JSPM

  • Created
  • Published
  • Downloads 1106
  • Score
    100M100P100Q98914F
  • License MIT

ExGuard RBAC client with cache-first Redis support for maximum performance in EmpowerX applications

Package Exports

  • exguard-client
  • exguard-client/setup

Readme

exguard-client

ExGuard RBAC (Role-Based Access Control) client library with cache-first Redis support for EmpowerX applications.

๐Ÿš€ Cache-First Performance: Prioritizes Redis cache over API calls for maximum speed and efficiency.

Features

  • ๐Ÿ” RBAC Authentication: Token verification and user access management
  • ๐Ÿ”„ Realtime Updates: WebSocket-based real-time RBAC changes
  • ๐ŸŽฏ Permission Guards: React components for route and feature protection
  • ๐Ÿช React Hooks: Easy-to-use hooks for permission checking
  • ๐Ÿ“ฆ TypeScript: Full type safety with TypeScript definitions
  • โšก Auto-Configuration: Automatically detects API URL based on environment
  • ๐Ÿš€ Zero Setup: Works out of the box with sensible defaults
  • ๐Ÿ’พ Cache-First Redis: Prioritizes Redis cache, minimizes API calls
  • ๐Ÿ”— Cross-Tab Sync: Synchronized data across browser tabs
  • โšก Backend Integration: Seamless integration with exguard-cached backend package

Cache-First Strategy

This package implements a pure cache-first approach:

  1. โœ… Always check Redis cache first
  2. ๐Ÿ“ก Call API ONLY if cache is completely empty
  3. ๐Ÿšซ Never call API for permission checks
  4. ๐Ÿ”„ Rely on backend cache invalidation for updates

Prerequisites

Required Dependencies

pnpm add react react-dom react-router axios socket.io-client

Redis Setup

You need a Redis server running and accessible from your frontend application:

Option 1: Direct Redis Connection

# Install Redis server
# Ubuntu/Debian: sudo apt-get install redis-server
# macOS: brew install redis
# Windows: Use Docker or WSL

# Start Redis server
redis-server

Set up a WebSocket/HTTP proxy to Redis for browser compatibility.

Environment Variables

Create a .env file in your project root:

# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your_redis_password
REDIS_DB=0
REDIS_KEY_PREFIX=exguard:frontend:
REDIS_TLS=false

# ExGuard API Configuration
EXGUARD_API_URL=http://localhost:3000

Installation

pnpm add exguard-client

Quick Start

1. Initialize Redis Client

Add this to your app setup (e.g., App.tsx or main.tsx):

import { initializeCacheFirstRedisClient } from 'exguard-client';

// Initialize once at app startup
initializeCacheFirstRedisClient({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379'),
  password: process.env.REDIS_PASSWORD,
  db: parseInt(process.env.REDIS_DB || '0'),
  keyPrefix: 'exguard:frontend:',
  tls: process.env.REDIS_TLS === 'true',
});

2. Wrap with Realtime Provider

import { ExGuardRealtimeProvider } from 'exguard-client';

function App() {
  return (
    <ExGuardRealtimeProvider>
      <Router>
        <Routes>
          <Route path="/protected" element={<ProtectedRoute />} />
        </Routes>
      </Router>
    </ExGuardRealtimeProvider>
  );
}

3. Use Cache-First Hook

import { useUserAccessCacheFirst } from 'exguard-client';

function ProtectedRoute() {
  const { userAccess, isLoading, hasPermission } = useUserAccessCacheFirst();
  
  // Permission checking uses ONLY cached data - no API calls!
  const canViewUsers = hasPermission('exGUARD', 'users:view');
  const canEditRoles = hasPermission('exGUARD', 'roles:edit');
  
  if (isLoading) return <div>Loading from cache...</div>;
  
  return (
    <div>
      <h1>Welcome {userAccess?.user?.username}</h1>
      {canViewUsers && <UserManagement />}
      {canEditRoles && <RoleEditor />}
    </div>
  );
}

Cache-First Hook API

useUserAccessCacheFirst()

Returns an object with the following properties:

interface UseUserAccessCacheFirstReturn {
  userAccess: UserAccessData | null;           // Cached user data
  isLoading: boolean;                          // Loading from cache
  isFetching: boolean;                        // Fetching from API (rare)
  error: Error | null;                        // Error state
  hasPermission: (module: string, permission: string) => boolean;
  hasModuleAccess: (module: string) => boolean;
  getModulePermissions: (module: string) => string[];
  refetch: (force?: boolean) => Promise<void>; // Force API call
  refetchSilent: () => Promise<void>;         // Silent refresh
  invalidateCache: () => void;                // Invalidate cache
  clearCache: () => Promise<void>;            // Clear cache
}

Permission Checking

const { hasPermission, hasModuleAccess } = useUserAccessCacheFirst();

// Check specific permission (uses cached data only)
const canView = hasPermission('exGUARD', 'users:view');

// Check module access
const hasUserModule = hasModuleAccess('exGUARD');

// Get all permissions for a module
const permissions = getModulePermissions('exGUARD');

Cache Behavior

When API is Called ๐Ÿ“ก

The API endpoint is called ONLY in these rare cases:

  1. First visit with completely empty cache
  2. Cache expired and real-time update invalidates
  3. Manual refetch is explicitly called
  4. Cache corruption detected

When API is NOT Called ๐Ÿšซ

The API is NEVER called for:

  1. Permission checks - uses cached data only
  2. Module access checks - uses cached data only
  3. User roles/groups - uses cached data only
  4. Field office data - uses cached data only

Cache Performance

// โœ… INSTANT - Uses cached data (0ms)
const canView = hasPermission('exGUARD', 'users:view');

// ๐Ÿ“ก SLOW - API call only when cache empty
// (happens once per session or on cache invalidation)
const { userAccess } = useUserAccessCacheFirst();

Backend Integration

For optimal performance, ensure your backend uses the exguard-cached package:

Backend Setup

# Install backend package
pnpm add exguard-cached

# Auto-configure (recommended)
npx exguard-cached setup

Backend Endpoint

import { Controller, Get, UseGuards } from '@nestjs/common';
import { RequireCachedUserPermission, CachedUser } from 'exguard-cached';

@Controller('guard')
export class GuardController {
  
  @Get('me')
  @RequireCachedUserPermission('exGUARD', 'profile:view')
  async getMe(@CachedUser() cachedUser: CachedUserData) {
    return {
      success: true,
      data: {
        user: cachedUser.user,
        groups: cachedUser.groups,
        roles: cachedUser.roles,
        modules: cachedUser.modules,
        fieldOffices: cachedUser.fieldOffices,
      }
    };
  }
}

Cache Synchronization

The backend automatically invalidates cache when:

  • User permissions change
  • Roles are updated
  • Groups are modified
  • Real-time WebSocket updates are received

Advanced Usage

Direct Redis Client Access

import { getCacheFirstRedisClient } from 'exguard-client';

const redisClient = getCacheFirstRedisClient();

// Direct Redis operations
await redisClient.set('custom:key', data, { ttl: 300 });
const cached = await redisClient.get('custom:key');
await redisClient.del('custom:key');

Custom Cache Keys

import { RedisCacheClient } from 'exguard-client';

const customClient = new RedisCacheClient({
  host: 'localhost',
  port: 6379,
  keyPrefix: 'myapp:custom:',
});

await customClient.set('user:123', userData, { ttl: 600 });

Fallback to LocalStorage

If Redis is unavailable, the client gracefully falls back to localStorage:

// The hook automatically handles Redis failures
const { userAccess } = useUserAccessCacheFirst();
// Works even if Redis is down (uses localStorage fallback)

Migration Guide

From localStorage

// Before (localStorage)
import { useUserAccessSingleton } from 'exguard-client';
const { userAccess } = useUserAccessSingleton();

// After (Cache-First Redis)
import { useUserAccessCacheFirst, initializeCacheFirstRedisClient } from 'exguard-client';

// Initialize once
initializeCacheFirstRedisClient({
  host: 'localhost',
  port: 6379,
});

// Use in components - cache-first approach
const { userAccess, hasPermission } = useUserAccessCacheFirst();

From Basic Redis

// Before (basic Redis)
import { useUserAccessRedis, initializeRedisClient } from 'exguard-client';
initializeRedisClient(config);

// After (Cache-First Redis)
import { useUserAccessCacheFirst, initializeCacheFirstRedisClient } from 'exguard-client';
initializeCacheFirstRedisClient(config);

Performance Benefits

Feature localStorage Basic Redis Cache-First Redis
API Calls Frequent Moderate Minimal
Cross-tab Sync โŒ โœ… โœ…
Backend Sync โŒ โœ… โœ…
Permission Speed โšก Fast ๐Ÿš€ Fast โšกโšก Instant
Cache Hits 30% 70% 95%+

Troubleshooting

Common Issues

Redis Connection Failed

# Check Redis server
redis-cli ping

# Check configuration
echo $REDIS_HOST
echo $REDIS_PORT

Cache Not Updating

// Force cache refresh
const { refetch } = useUserAccessCacheFirst();
await refetch(true); // Force API call

Permission Check Returns False

// Debug permission check
const { userAccess } = useUserAccessCacheFirst();
console.log('User modules:', userAccess?.modules);
console.log('Permissions:', getModulePermissions('exGUARD'));

Debug Mode

Enable debug logging:

// Add to environment
EXGUARD_DEBUG=true

// Check browser console for cache logs
// [ExGuard Cache-First] โœ… Cache hit - using Redis data
// [ExGuard Cache-First] ๐Ÿ“ก No cache found, calling API as last resort

License

MIT License - see LICENSE file for details.

Support

For issues and questions:

  1. Check the troubleshooting section
  2. Verify Redis connectivity
  3. Ensure backend uses exguard-cached package
  4. Check environment variables

๐Ÿš€ Cache-First Performance: Built for speed, designed for scale.