@jakobcooldown/react-csr-sdk
v1.0.1
Published
Mockery SDK for dynamic bundle loading in web applications
Maintainers
Readme
Mockery React CSR SDK
Dynamic bundle loading for full React app replacement based on user/tenant context.
Overview
The Mockery React CSR SDK enables you to completely replace your React application bundle based on who is logged in.
How It Works
- HTML loads → Bundle loader checks localStorage → Loads custom OR default bundle → Single React app starts
- No React component conflicts or DOM manipulation issues
- True full-app replacement, not overlay customizations
Security Architecture
The SDK follows a secure server-to-server architecture:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Browser │ │ Vendor │ │ Mockery │
│ (SDK) │ │ Backend │ │ API │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ GET /api/bundle │ │
├─────────────────►│ │
│ │ GET /vendor-api │
│ ├─────────────────►│
│ │ (with API key) │
│ │ │
│ │ bundle URL │
│ │◄─────────────────┤
│ bundle URL │ │
│◄─────────────────┤ │
│ │ │
SDK loads bundle │ │- Browser: Never sees API keys, calls vendor backend
- Vendor Backend: Authenticates users, calls Mockery API server-to-server
- Mockery API: Returns bundle URLs based on tenant configuration
Installation
npm install @mockery/react-csr-sdkQuick Start
1. Include the Bundle Loader
Add the bundle loader script to your HTML before any React bundles:
<!DOCTYPE html>
<html>
<head>
<!-- Include Mockery bundle loader FIRST -->
<script src="node_modules/@mockery/react-csr-sdk/dist/bundle-loader.js"></script>
<!-- Mark your default bundle -->
<script data-mockery-default-bundle type="module" src="./assets/index-abc123.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>2. Create Vendor Backend Endpoint
Your backend should authenticate users and call the Mockery API:
// GET /api/user/bundle
app.get('/api/user/bundle', authenticateUser, async (req, res) => {
const user = req.user; // Your authenticated user
if (!user.tenantId) {
return res.json({ bundleUrl: undefined });
}
try {
// Call Mockery API server-to-server with your API key
const response = await fetch(
`${MOCKERY_API_URL}/vendor-api/products/${PRODUCT_SLUG}/tenants/${user.tenantId}/bundle`,
{
headers: {
'X-API-Key': process.env.MOCKERY_API_KEY, // Keep API keys on server
'Content-Type': 'application/json',
},
}
);
if (response.ok) {
const data = await response.json();
res.json({ bundleUrl: data.bundle_url });
} else {
res.json({ bundleUrl: undefined });
}
} catch (error) {
console.error('Failed to fetch bundle from Mockery:', error);
res.json({ bundleUrl: undefined });
}
});3. Use SDK in Frontend
import { setCustomBundle, clearCustomBundle } from '@mockery/react-csr-sdk';
// Handle user login - call your own backend
async function handleUserLogin(credentials) {
try {
// 1. Authenticate with your backend (your existing auth flow)
const authResponse = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
credentials: 'include'
});
if (authResponse.ok) {
// 2. Get bundle URL from your backend
const bundleResponse = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (bundleResponse.ok) {
const { bundleUrl } = await bundleResponse.json();
// setCustomBundle will exit early if bundleUrl is unchanged
setCustomBundle(bundleUrl); // Page reloads only if bundle changed
}
}
} catch (error) {
console.error('Login failed:', error);
}
}
// Clear custom bundle on logout
async function handleUserLogout() {
try {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
clearCustomBundle(); // Reloads with default bundle
} catch (error) {
console.error('Logout failed:', error);
}
}API Reference
Bundle Management
setCustomBundle(bundleUrl, reload?)
Sets a custom bundle URL and optionally reloads the page. Exits early (no reload) if the bundle URL is unchanged.
setCustomBundle('https://cdn.example.com/custom-bundle.js', true);
setCustomBundle(undefined); // Clear bundleParameters:
bundleUrl: string | undefined- Bundle URL or undefined to clearreload: boolean- Whether to reload page (default: true)
getCustomBundle()
Gets the currently set custom bundle URL.
const currentBundle = getCustomBundle();
// Returns: string | undefinedclearCustomBundle(reload?)
Clears the custom bundle and optionally reloads to use the default.
clearCustomBundle(true);reloadWithBundle(bundleUrl)
Sets a bundle URL and immediately reloads the page.
reloadWithBundle('https://cdn.example.com/new-bundle.js');
reloadWithBundle(undefined); // Clear and reloadUtility Functions
setDebugMode(enabled)
Enable/disable debug logging.
setDebugMode(true); // See bundle loading logsIntegration Patterns
User Login Flow
async function handleUserLogin(credentials) {
// 1. Authenticate with your backend
const user = await authenticateUser(credentials);
// 2. Get bundle URL from your backend
if (user) {
try {
const response = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (response.ok) {
const { bundleUrl } = await response.json();
// Only reloads if bundle URL changed
setCustomBundle(bundleUrl);
}
} catch (error) {
console.error('Failed to load custom bundle:', error);
// App continues with default bundle
}
}
}User Logout Flow
async function handleUserLogout() {
// 1. Clear session with your backend
await logoutUser();
// 2. Clear custom bundle and reload with default
clearCustomBundle();
}Tenant Switching
async function switchTenant(newTenantId) {
// 1. Update tenant in your backend
await updateUserTenant(newTenantId);
// 2. Get new tenant's bundle URL
const response = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (response.ok) {
const { bundleUrl } = await response.json();
setCustomBundle(bundleUrl); // Page reloads only if bundle changed
}
}Error Handling
async function loadUserBundle() {
try {
const response = await fetch('/api/user/bundle', {
credentials: 'include'
});
if (response.ok) {
const { bundleUrl } = await response.json();
setCustomBundle(bundleUrl);
}
} catch (error) {
console.error('Bundle loading failed:', error);
// Optional: Show user notification
showNotification('Custom features unavailable, using default app');
// App continues with default bundle - no action needed
}
}Global API (No Module Import Required)
If you can't use ES modules, the bundle loader exposes global functions:
// Available on window.mockery after bundle-loader.js loads
window.mockery.setCustomBundle(url);
window.mockery.getCustomBundle();
window.mockery.clearCustomBundle();
window.mockery.reloadWithBundle(url);
window.mockery.setDebug(enabled);How Bundle Replacement Works
- Bundle Loader Script: Runs before any React code
- localStorage Check: Looks for
mockery_bundle_url - Bundle Decision:
- If custom URL found → Prevents default bundle, loads custom bundle
- If no custom URL → Allows default bundle to load normally
- Single App: Only one React application ever starts
Bundle Requirements
Your custom bundles should:
- Be complete, standalone React applications
- Include all dependencies (React, ReactDOM, etc.)
- Mount to the same DOM element (
#root) - Handle their own routing and state management
Backend Integration Requirements
Your backend endpoint (/api/user/bundle or similar) should:
- Authenticate the user (session, JWT, etc.)
- Determine user's tenant ID from your user database
- Call Mockery API server-to-server with your API key
- Return bundle URL to frontend (or undefined if no custom bundle)
Example response format:
{
"bundleUrl": "https://cdn.example.com/tenant-123/bundle.js"
}Or if no custom bundle:
{
"bundleUrl": undefined
}Security Benefits
- API Keys Protected: Never exposed to browser
- User Authentication: Your backend validates identity
- Authorization: Your backend ensures user can access their tenant
- Audit Trail: Your backend can log bundle requests
- Rate Limiting: Your backend can implement limits
Debugging
Enable debug mode to see bundle loading logs:
import { setDebugMode } from '@mockery/react-csr-sdk';
setDebugMode(true);
// Or globally:
window.mockery.setDebug(true);This will log:
- Bundle URL resolution
- Bundle loading attempts
- Fallback scenarios
- Error details
TypeScript Support
The SDK includes full TypeScript definitions:
import type { MockeryConfig } from '@mockery/react-csr-sdk';Examples
See the examples/ directory for complete implementation examples.
Support
For issues or questions, please see the Mockery documentation or contact support.
