@omni-gate/frame-bridge
v0.3.2
Published
A TypeScript framework for parent-child window communication, providing comprehensive APIs for user management, permissions, JWT expiry management, and unified message broadcasting.
Maintainers
Readme
@omni-gate/frame-bridge
A TypeScript framework for parent-child window communication, providing comprehensive APIs for user management, permissions, JWT expiry management, and unified message broadcasting.
简体中文 | English
🚀 Features
- Parent-Child Window Communication - Secure message bridge for iframe communication
- Trusted Origin Validation - Configurable whitelist for security
- Centralized Data Management - Parent window handles all API calls and caching
- User Management - Get current user information
- Permission Control - Application and module-level permission management
- Data Dictionaries - Hierarchical data options with intelligent caching
- Module Navigation - Cross-window module opening via message bridge
- Multi-language Support - Internationalized error messages
- Smart Caching - Multi-tier caching strategy for optimal performance
- Heartbeat Mechanism - User activity monitoring and session management
- JWT Expiry Management - Automatic JWT expiry detection with async callback support
- Unified Logoff Broadcast - Automatically notify all child windows on user logout
📦 Installation
npm install @omni-gate/frame-bridge🏗️ Architecture
This library provides two main classes:
OmniParentApi- For the parent window, handles all API requests and cachingOmniClientApi- For child windows (iframes), communicates with parent window
┌─────────────────────────────────────────────────────────────┐
│ Parent Window │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ OmniParentApi │ │
│ │ • RestService (API access) │ │
│ │ • MessageBridgeManager │ │
│ │ • Centralized caching │ │
│ │ • Trusted origin validation │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Message Bridge (postMessage) │
│ │ │
└──────────────────────────┼──────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ iframe 1 │ │ iframe 2 │ │ iframe 3 │
│ │ │ │ │ │
│OmniClient│ │OmniClient│ │OmniClient│
│ Api │ │ Api │ │ Api │
└──────────┘ └──────────┘ └──────────┘🏗️ Quick Start
Parent Window Setup
import { OmniParentApi, OmniFrameOptions } from '@omni-gate/frame-bridge';
import { MessageBridgeManager } from '@ticatec/iframe-message-bridge';
import RestService from '@ticatec/restful_service_api';
// Create REST service proxy
const serviceProxy = new RestService('https://api.example.com');
// Create message bridge manager
const bridgeManager = new MessageBridgeManager();
// Configure SDK options (optional)
const options: OmniFrameOptions = {
jwtStorage: sessionStorage, // JWT token storage location (default: sessionStorage)
jwtStorageKey: 'omni-token', // JWT token storage key (default: 'omni-token')
isDebug: false, // Enable debug mode (default: false)
jwtCheckInterval: 30 * 1000, // JWT check interval in ms (default: 30000)
jwtWarningTime: 5 * 60 * 1000 // JWT warning time in ms (default: 300000)
};
// Configure trusted origins (optional but recommended)
const trustedOrigins = [
'https://trusted-app1.com',
'https://trusted-app2.com',
'http://localhost:3000'
];
// Initialize parent API
const parentApi = OmniParentApi.initialize(
serviceProxy,
bridgeManager,
trustedOrigins, // Optional: restrict to specific origins
options // Optional: SDK configuration
);
// Get instance
const api = OmniParentApi.getInstance();Child Window (iframe) Setup
import OmniClientApi from '@omni-gate/frame-bridge';
import { MessageBridgeClient } from '@ticatec/iframe-message-bridge';
// Create message bridge client
const bridge = new MessageBridgeClient('https://parent-domain.com');
// Initialize client API (no need for RestService!)
OmniClientApi.initialize(bridge);
// Get instance
const api = OmniClientApi.getInstance();📖 API Reference
OmniParentApi (Parent Window)
Initialization
initialize(serviceProxy, bridgeManager, trustedOrigins?)
Initialize the parent API instance.
const parentApi = OmniParentApi.initialize(
serviceProxy,
bridgeManager,
['https://trusted-app.com'] // Optional: trusted origins
);getInstance()
Get the parent API instance.
const api = OmniParentApi.getInstance();Default Message Handlers
All message handlers automatically validate the origin before processing.
onHeartbeat()
Handles heartbeat messages from child windows.
onGetToken(customHandler?)
Handles JWT token requests. Returns token from localStorage by default.
onGetMe(customHandler?)
Handles current user information requests.
onGetPermission(customHandler?)
Handles application permission requests.
onGetModulePermission(customHandler?)
Handles module permission requests.
onGetOptionsData(customHandler?)
Handles data dictionary requests.
onGetChildrenOptions(customHandler?)
Handles hierarchical data requests.
onGetErrorMessages(customHandler?)
Handles error message requests.
onGetSupportedLanguages(customHandler?)
Handles supported languages list requests.
onGetCurrentLanguage(customHandler?)
Handles current language requests.
Custom Handlers
registerHandler(eventName, handler, skipOriginValidation?)
Register a custom message handler.
parentApi.registerHandler('customEvent', (data, sourceWindow, sourceOrigin) => {
console.log('Received from:', sourceOrigin);
return { result: 'ok' };
});
// Skip origin validation (not recommended for security-sensitive operations)
parentApi.registerHandler('publicEvent', handler, true);unregisterHandler(eventName)
Unregister a message handler.
parentApi.unregisterHandler('customEvent');Broadcast Messages
broadcast(eventName, data, targetOrigin?)
Send a broadcast message to all child windows.
parentApi.broadcast('user-login-changed', { userId: 123, userName: 'Alice' });broadcastThemeChanged(theme)
Broadcast theme change.
broadcastLogoff()
Broadcast user logoff message. When the parent window user logs out, call this method to notify all child windows.
// When user logs out
parentApi.broadcastLogoff();Broadcast Handlers
onBroadcast(eventName, handler)
Register a broadcast message handler (parent window can also receive broadcasts).
parentApi.onBroadcast('theme-changed', (data) => {
console.log('Theme changed to:', data.theme);
applyTheme(data.theme);
});offBroadcast(eventName)
Unregister a broadcast message handler.
parentApi.offBroadcast('theme-changed');Cache Management
clearAllCache()
Clear all cached data from both localStorage and the configured JWT storage. This includes user info, permissions, dictionaries, error messages, and JWT tokens.
parentApi.clearAllCache();clearUserData()
Clear user-specific data (user info and permissions) when the logged-in user changes.
// After login, check if user changed
const oldMe = await parentApi.getMe();
const newMe = await performLogin(username, password);
if (oldMe.id !== newMe.id) {
// User changed, clear user data
parentApi.clearUserData();
}clearOptionsData()
Clear dictionary/options data when the tenant changes.
// Check if tenant changed
const oldMe = await parentApi.getMe();
const newMe = await getUserInfo();
if (oldMe.tenantId !== newMe.tenantId) {
// Tenant changed, clear options data
parentApi.clearOptionsData();
}Utility Methods
getBridgeManager()
Get the underlying MessageBridgeManager instance.
clearAllHandlers()
Clear all handlers (both message and broadcast handlers).
destroy()
Clean up and destroy the instance.
OmniClientApi (Child Window)
Initialization
initialize(bridge)
Initialize the client API instance.
const clientApi = OmniClientApi.initialize(bridge);getInstance()
Get the client API instance.
const api = OmniClientApi.getInstance();User Management
getMe(): Promise<User>
Get current logged-in user information from parent window.
const user = await clientApi.getMe();
console.log('Current user:', user);Response:
{
"accountCode": "admin@saas",
"name": "System Administrator",
"tenant": "company-001",
"branch": "main-branch"
}Permission Management
getPermission(appCode: string): Promise<Permission>
Get application permissions from parent window.
const permissions = await clientApi.getPermission('saas-platform');
console.log('App permissions:', permissions);Response:
{
"user-management": {
"maintain": true,
"delete": true
},
"role-management": {
"maintain": true,
"assign": true
}
}getModulePermission(appCode: string, modCode: string): Promise<Permission>
Get specific module permissions.
const modulePerms = await clientApi.getModulePermission('saas-platform', 'user-management');
const canMaintain = modulePerms.maintain === true;Data Dictionary Management
getOptionsData(dicNames: string | string[]): Promise<OptionsData>
Get dictionary data from parent window.
// Single dictionary
const genderOptions = await clientApi.getOptionsData('gender');
// Multiple dictionaries
const options = await clientApi.getOptionsData([
'gender',
'card-type',
'nationality'
]);
console.log('Gender options:', options.gender);getChildrenOptions(dic: string, code: string): Promise<Option[]>
Get child options for hierarchical data.
const regions = await clientApi.getChildrenOptions('region', '130100');
console.log('Sub-regions:', regions);Error Message Management
getErrorMessages(appCode: string): Promise<ErrorMessages>
Get localized error messages. Language is determined by the X-language request header which is automatically set based on the language setting in localStorage.
const errors = await clientApi.getErrorMessages('saas-platform');
// Language is automatically determined by the X-language headerLanguage Handling:
- Language is read from localStorage's
languagekey - Sent to backend via
X-languagerequest header - Backend returns error messages in the corresponding language
Language Management
getSupportedLanguages(): Promise<Language[]>
Get the list of supported languages from parent window.
const languages = await clientApi.getSupportedLanguages();
console.log('Supported languages:', languages);
// Returns: ['en', 'zh-CN', 'ja', 'ko', ...]getCurrentLanguage(): Promise<LanguageResponse>
Get the current language setting from parent window.
const response = await clientApi.getCurrentLanguage();
const currentLang = response.language;
console.log('Current language:', currentLang);
// Returns: { language: 'zh-CN' }Token Management
getJwtToken(): Promise<string>
Get JWT token from parent window.
const token = await clientApi.getJwtToken();
console.log('JWT Token:', token);Module Navigation
openModule(modHash: string, params: any): void
Open a module via message bridge communication.
clientApi.openModule('/#/plan/PlanHomePage', {
patientId: '12345',
planType: 'follow-up'
});Broadcast Handlers
onBroadcast(eventName, handler)
Register a broadcast message handler to receive broadcasts from parent window.
clientApi.onBroadcast('theme-changed', (data) => {
console.log('Theme changed to:', data.theme);
applyTheme(data.theme);
});onLogoff(handler)
Register a user logout event handler. When the parent window user logs out, the child window will be notified and call the registered handler.
clientApi.onLogoff((data) => {
console.log('User logged out at:', data.timestamp);
// Perform logout-related cleanup operations
// For example: clear local data, redirect to login page, etc.
});offBroadcast(eventName)
Unregister a broadcast message handler.
clientApi.offBroadcast('theme-changed');offLogoff()
Unregister the user logout event handler.
clientApi.offLogoff();clearBroadcastHandlers()
Clear all broadcast message handlers.
clientApi.clearBroadcastHandlers();Utility Methods
destroy()
Clean up and destroy the instance.
clientApi.destroy();🔧 Caching Strategy
| Data Type | Storage | TTL | Managed By | |-----------|---------|-----|------------| | User Info (me) | localStorage | Session | Parent Window | | Permissions | localStorage | 15 minutes | Parent Window | | Dictionary Data | localStorage | 60 minutes | Parent Window | | Error Messages | localStorage | 24 hours | Parent Window | | Supported Languages | localStorage | 24 hours | Parent Window | | Current Language | localStorage | Session | Parent Window | | JWT Token | Configurable* | Session | Parent/Child Window |
Note: JWT token storage is configurable via OmniFrameOptions.jqStorage (default: sessionStorage). All other caching is managed by the parent window using localStorage. Child windows request data without worrying about cache management.
🔒 Security Features
Trusted Origin Validation
The parent window can restrict which origins are allowed to communicate:
const trustedOrigins = [
'https://app1.company.com',
'https://app2.company.com'
];
const parentApi = OmniParentApi.initialize(
serviceProxy,
bridgeManager,
trustedOrigins
);When a request comes from an untrusted origin:
{
"error": "Untrusted origin",
"message": "Origin https://malicious-site.com is not in the trusted origins list"
}Security Best Practices
- Always specify trusted origins in production environments
- Use HTTPS for all communication
- Validate data received from child windows
- Implement proper authentication in your REST service
- Use Content Security Policy headers
🎯 Heartbeat System
The client automatically:
- Monitors user interactions (keyboard, mouse events)
- Sends heartbeat signals every 10 seconds when active
- Handles cross-window communication
- Manages session lifecycle
🔐 JWT Expiry Management
The SDK provides comprehensive JWT expiry detection and management with support for both synchronous and asynchronous callbacks.
Configuration
JWT check configuration via OmniFrameOptions:
| Option | Default | Description | |--------|---------|-------------| | jwtCheckInterval | 30 seconds | JWT expiry check interval | | jwtWarningTime | 5 minutes | How early to warn before expiry | | jwtStorage | sessionStorage | Storage location for JWT token | | jwtStorageKey | 'omni-token' | Storage key for JWT token | | isDebug | false | Enable debug logging |
Parent Window - JWT Monitoring
The parent window's JWT manager is created at initialization and monitors token expiry automatically.
const parentApi = OmniParentApi.getInstance();
// Start JWT monitoring with callback
parentApi.startJwtMonitoring((data) => {
if (data.expired) {
// JWT has expired (e.g., after sleep/wakeup)
console.log('JWT has expired, need to re-login');
// Clean up local data, redirect to login page, etc.
} else {
// JWT is about to expire
const minutesLeft = Math.floor(data.remainingTime! / 60000);
console.log(`JWT will expire in ${minutesLeft} minutes`);
// Try to refresh token here
}
});
// Stop JWT monitoring
parentApi.stopJwtMonitoring();Child Window - Cross-Origin JWT Management
In cross-origin scenarios, the child window can independently monitor its locally stored JWT token.
// Only needed in cross-origin iframes
clientApi.startJwtMonitoring(async (data) => {
if (data.expired) {
// Token expired, request new token from parent
const newToken = await clientApi.refreshJwtToken();
if (newToken) {
await clientApi.updateJwtToken(newToken);
} else {
// Cannot get new token, redirect to login
window.location.href = '/login';
}
} else if (data.remainingTime && data.remainingTime < 5 * 60 * 1000) {
// Token expiring soon (less than 5 minutes)
showWarning('Session will expire soon, please save your work');
}
});
// Stop monitoring
clientApi.stopJwtMonitoring();Async Callback Support
Supports asynchronous callback functions. The system won't trigger repeated warnings while the user is handling an async operation.
parentApi.startJwtMonitoring(async (data) => {
if (!data.expired) {
// Show confirmation dialog
const shouldRefresh = await showConfirmDialog({
title: 'Session Expiring',
message: `Your session will expire in ${Math.floor(data.remainingTime! / 60000)} minutes. Refresh now?`
});
if (shouldRefresh) {
await refreshToken();
}
}
});Features
- Automatic Detection: Automatically checks JWT status at configured intervals
- Expiry Warning: Triggers callback before JWT expires (only once)
- Expired Handling: When JWT is expired, triggers callback and automatically broadcasts logoff (parent window)
- Async Support: Supports async callback functions, won't trigger repeated warnings during async operations
- Prevent Duplication: Uses state flags to prevent duplicate warnings, avoiding multiple dialogs
- Cross-Origin Support: Child windows can independently manage JWT in cross-origin scenarios
🛠️ Development
# Clean build directory
npm run clean
# Build the library
npm run build
# Watch mode for development
npm run dev
# Type checking
npm run typecheck
# Publish to npm
npm run publish:public📄 TypeScript Support
Full TypeScript definitions included. The library exports:
import OmniClientApi, {
OmniParentApi,
Permissions,
Constants,
BaseRestServiceProxy
} from '@omni-gate/frame-bridge';
// Access constants
const { BRIDGE_MESSAGES, BROADCAST_MESSAGES } = Constants;🌐 Internationalization (i18n)
The SDK provides built-in internationalization support for user-facing error messages through the i18nRes resource object.
Error Message Resources
Error messages in BaseRestServiceProxy use i18n resources for displaying localized messages to end users. The available error message keys are:
| Key | Default Message | Usage |
|-----|----------------|-------|
| UnknownErrorCode | "Unknown error code." | Shown when an error code is not found in the error table |
| NetworkError | "An unknown network error occurred." | Shown for network-related errors |
| NoErrorsTable | "The error information table is not loaded." | Shown when error messages are not loaded |
Error Message Display
When API errors occur, the BaseRestServiceProxy automatically:
- Retrieves the error code from the API response
- Looks up the corresponding error message from the loaded error table
- Falls back to i18n resource messages if the error code is not found
- Displays the formatted error message via
Toast.show()
Example Error Display:
MY-APP-1001: User does not existOr if error code is not found:
MY-APP-9999: Unknown error code.i18n Resources Location:
import i18nRes from '@omni-gate/frame-bridge/src/i18nRes/i18nRes';Note: Only user-facing error messages (displayed via Toast.show()) use i18n. Developer console logs and inter-window communication messages remain in English for debugging purposes.
🔧 REST Service Proxy
BaseRestServiceProxy
Abstract base class for managing RestService instances with automatic JWT token injection, language support, and error handling.
Features:
- Automatic JWT token injection from storage
- Automatic language header injection
- Unified error handling with internationalized error messages
- Request interception for custom headers
Usage Example:
import { BaseRestServiceProxy } from '@omni-gate/frame-bridge';
import RestService from '@ticatec/restful_service_api';
// Create a custom service proxy by extending the base class
class MyAppServiceProxy extends BaseRestServiceProxy {
protected getPrefix(): string {
return 'MY-APP';
}
protected getAppCode(): string {
return 'my-application';
}
protected async retrieveErrorMessage(): Promise<any> {
// Load error messages for the application
const appCode = this.getAppCode();
const response = await fetch(`/error-messages/${appCode}`);
return await response.json();
}
constructor() {
super(
(errorHandler, preInterceptor) => {
return new RestService('https://api.example.com', errorHandler, preInterceptor);
},
{
jwtStorage: sessionStorage,
jwtStorageKey: 'omni-token'
}
);
// Initialize to load error messages
this.initialize();
}
}
// Create proxy instance (error messages are loaded automatically)
const proxy = new MyAppServiceProxy();
// Use the RestService
const users = await proxy.service.get('/api/users');Automatic Request Headers:
Authorization: Bearer <token>- JWT token from storage (if available)x-language: <lang>- Language setting from storage (if available)
Abstract Methods (must be implemented):
getPrefix(): string- Returns error message prefix (e.g., 'MY-APP')getAppCode(): string- Returns application code for loading error messagesretrieveErrorMessage(): Promise<any>- Loads error messages (called byinitialize())
Important:
- Call
initialize()to load error messages before usingservice - Requires global
Toast.show()method for error display - Requires JWT token and language to be stored in storage
🛠️ Utility Tools
omniFrameSdk
Utility functions for frame detection and origin checking.
isSameOrigin(): boolean
Check if the current window is in the same origin as the parent window. Returns true if:
- The window is not in an iframe (window === window.top), OR
- The window is in an iframe and has the same origin as the parent window
Returns false if the window is in a cross-origin iframe.
import omniFrameSdk from '@omni-gate/frame-bridge';
if (omniFrameSdk.isSameOrigin()) {
// Safe to access parent window properties directly
console.log(window.parent.location.href);
} else {
// Need to use postMessage for cross-origin communication
console.log('Cross-origin iframe detected');
}Use Cases:
- Determine if direct parent window access is safe
- Choose between direct property access and postMessage communication
- Implement security checks before accessing parent window
🤝 Dependencies
Peer Dependencies
@ticatec/iframe-message-bridge- Cross-window communication@ticatec/restful_service_api- REST API client
Dev Dependencies
typescript- TypeScript compiler
📋 Requirements
- TypeScript 5.0+
- ES2017+ target environment
- Browser with sessionStorage/localStorage support
- Modern browser with postMessage support
📞 Support
For issues and questions, please refer to the OmniGate platform documentation or contact the development team.
📜 License
MIT
