Bridge library for VK Mini Apps to communicate with VK clients across iOS, Android, and Web platforms
—
Redux-style middleware system for intercepting and processing VK Bridge communications, enabling logging, data transformation, error handling, and custom request/response processing.
Create an enhanced VK Bridge instance with middleware applied to the send method.
/**
* Apply middleware to VK Bridge instance
* Creates enhanced bridge with middleware chain applied to send method
* @param middlewares - Array of middleware functions to apply
* @returns Function that takes bridge and returns enhanced bridge
*/
function applyMiddleware(
...middlewares: Array<Middleware | undefined | null>
): (bridge: VKBridge) => VKBridge;
type Middleware<S extends VKBridgeSend = VKBridgeSend> = (
api: MiddlewareAPI<S>
) => (next: S) => S;
interface MiddlewareAPI<S extends VKBridgeSend = VKBridgeSend> {
/** Send function for making bridge calls within middleware */
send: S;
/** Subscribe function for event listening within middleware */
subscribe(listener: VKBridgeSubscribeHandler): void;
}
type VKBridgeSend = <K extends AnyRequestMethodName>(
method: K,
props?: RequestProps<K> & RequestIdProp
) => Promise<K extends AnyReceiveMethodName ? ReceiveData<K> : void>;Usage Examples:
import bridge, { applyMiddleware } from "@vkontakte/vk-bridge";
// Create logger middleware
const loggerMiddleware = ({ send, subscribe }) => (next) => async (method, props) => {
console.log('→', method, props);
const startTime = Date.now();
try {
const result = await next(method, props);
const duration = Date.now() - startTime;
console.log('✓', method, `${duration}ms`, result);
return result;
} catch (error) {
const duration = Date.now() - startTime;
console.error('✗', method, `${duration}ms`, error);
throw error;
}
};
// Apply middleware and create enhanced bridge
const enhancedBridge = applyMiddleware(loggerMiddleware)(bridge);
// Use enhanced bridge normally
await enhancedBridge.send('VKWebAppInit');
await enhancedBridge.send('VKWebAppGetUserInfo');interface LoggerMiddleware {
/** Log all requests and responses with timing */
(api: MiddlewareAPI): (next: VKBridgeSend) => VKBridgeSend;
}Usage Examples:
// Basic logger
const basicLogger = ({ send, subscribe }) => (next) => async (method, props) => {
console.log(`Sending: ${method}`, props);
const result = await next(method, props);
console.log(`Received: ${method}`, result);
return result;
};
// Advanced logger with timing and error tracking
const advancedLogger = ({ send, subscribe }) => (next) => async (method, props) => {
const requestId = props?.request_id || Math.random().toString(36).substr(2, 9);
const startTime = performance.now();
console.group(`🔄 ${method} [${requestId}]`);
console.log('Parameters:', props);
try {
const result = await next(method, props);
const duration = performance.now() - startTime;
console.log('✅ Success:', result);
console.log(`⏱️ Duration: ${duration.toFixed(2)}ms`);
console.groupEnd();
return result;
} catch (error) {
const duration = performance.now() - startTime;
console.error('❌ Error:', error);
console.log(`⏱️ Duration: ${duration.toFixed(2)}ms`);
console.groupEnd();
throw error;
}
};
const loggedBridge = applyMiddleware(advancedLogger)(bridge);interface ErrorHandlerMiddleware {
/** Handle and transform errors before they reach the application */
(api: MiddlewareAPI): (next: VKBridgeSend) => VKBridgeSend;
}Usage Examples:
// Retry middleware for network errors
const retryMiddleware = ({ send, subscribe }) => (next) => async (method, props) => {
const maxRetries = 3;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await next(method, props);
} catch (error) {
lastError = error;
// Only retry on network/client errors
if (error.error_type === 'client_error' && attempt < maxRetries) {
console.warn(`Retry ${attempt}/${maxRetries} for ${method}`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
continue;
}
throw error;
}
}
throw lastError;
};
// Global error handler
const errorHandlerMiddleware = ({ send, subscribe }) => (next) => async (method, props) => {
try {
return await next(method, props);
} catch (error) {
// Log error for analytics
console.error('Bridge error:', { method, props, error });
// Transform specific errors
if (error.error_type === 'auth_error' && error.error_data.error_code === 4) {
throw new Error('User cancelled authorization');
}
if (error.error_type === 'client_error' && error.error_data.error_code === 1) {
throw new Error('Method not supported on this platform');
}
// Re-throw original error
throw error;
}
};
const robustBridge = applyMiddleware(retryMiddleware, errorHandlerMiddleware)(bridge);interface DataTransformMiddleware {
/** Transform request/response data */
(api: MiddlewareAPI): (next: VKBridgeSend) => VKBridgeSend;
}Usage Examples:
// Request/response transformer
const transformerMiddleware = ({ send, subscribe }) => (next) => async (method, props) => {
// Transform outgoing requests
let transformedProps = props;
if (method === 'VKWebAppGetAuthToken' && props) {
// Ensure scope is always a string
transformedProps = {
...props,
scope: Array.isArray(props.scope) ? props.scope.join(',') : props.scope
};
}
const result = await next(method, transformedProps);
// Transform incoming responses
if (method === 'VKWebAppGetUserInfo' && result) {
return {
...result,
full_name: `${result.first_name} ${result.last_name}`,
avatar: result.photo_200 || result.photo_100
};
}
return result;
};
// Data normalization middleware
const normalizerMiddleware = ({ send, subscribe }) => (next) => async (method, props) => {
const result = await next(method, props);
// Normalize timestamps to Date objects
if (result && typeof result === 'object') {
const normalizedResult = { ...result };
// Convert known timestamp fields
const timestampFields = ['vk_ts', 'timestamp', 'date', 'created', 'updated'];
timestampFields.forEach(field => {
if (field in normalizedResult && typeof normalizedResult[field] === 'number') {
normalizedResult[`${field}_date`] = new Date(normalizedResult[field] * 1000);
}
});
return normalizedResult;
}
return result;
};
const transformedBridge = applyMiddleware(transformerMiddleware, normalizerMiddleware)(bridge);interface AnalyticsMiddleware {
/** Track bridge usage for analytics */
(api: MiddlewareAPI): (next: VKBridgeSend) => VKBridgeSend;
}Usage Examples:
// Usage tracking middleware
const analyticsMiddleware = ({ send, subscribe }) => (next) => async (method, props) => {
const startTime = Date.now();
try {
const result = await next(method, props);
const duration = Date.now() - startTime;
// Track successful usage
trackEvent('bridge_method_success', {
method,
duration,
platform: bridge.isWebView() ? 'webview' : 'web',
timestamp: Date.now()
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
// Track errors
trackEvent('bridge_method_error', {
method,
duration,
error_type: error.error_type,
error_code: error.error_data?.error_code,
platform: bridge.isWebView() ? 'webview' : 'web',
timestamp: Date.now()
});
throw error;
}
};
// Performance monitoring
const performanceMiddleware = ({ send, subscribe }) => (next) => async (method, props) => {
const startTime = performance.now();
try {
const result = await next(method, props);
const duration = performance.now() - startTime;
// Log slow operations
if (duration > 1000) {
console.warn(`Slow bridge operation: ${method} took ${duration.toFixed(2)}ms`);
}
// Send to monitoring service
if (window.performance && window.performance.mark) {
window.performance.mark(`bridge-${method}-start`);
window.performance.mark(`bridge-${method}-end`);
window.performance.measure(`bridge-${method}`, `bridge-${method}-start`, `bridge-${method}-end`);
}
return result;
} catch (error) {
const duration = performance.now() - startTime;
console.error(`Bridge error in ${method} after ${duration.toFixed(2)}ms:`, error);
throw error;
}
};
const monitoredBridge = applyMiddleware(analyticsMiddleware, performanceMiddleware)(bridge);/**
* Compose multiple middleware functions
* Middleware is applied in order: first middleware wraps the second, etc.
*/
function composeMiddleware(...middlewares: Middleware[]): (bridge: VKBridge) => VKBridge;Usage Examples:
// Create comprehensive middleware stack
const middlewareStack = [
// First: Log all requests (outermost)
loggerMiddleware,
// Second: Handle authentication errors
authErrorHandlerMiddleware,
// Third: Retry failed requests
retryMiddleware,
// Fourth: Transform data
transformerMiddleware,
// Fifth: Track analytics (innermost)
analyticsMiddleware
];
// Apply all middleware
const fullBridge = applyMiddleware(...middlewareStack)(bridge);
// Alternative: Step by step application
const step1 = applyMiddleware(loggerMiddleware)(bridge);
const step2 = applyMiddleware(authErrorHandlerMiddleware)(step1);
const step3 = applyMiddleware(retryMiddleware)(step2);
const fullBridge2 = applyMiddleware(transformerMiddleware, analyticsMiddleware)(step3);
// Use the enhanced bridge
await fullBridge.send('VKWebAppInit');
await fullBridge.send('VKWebAppGetUserInfo');// Development vs production middleware
const createBridge = (isDevelopment: boolean) => {
const middlewares = [];
if (isDevelopment) {
middlewares.push(advancedLogger);
middlewares.push(performanceMiddleware);
} else {
middlewares.push(basicLogger);
middlewares.push(analyticsMiddleware);
}
// Always apply error handling
middlewares.push(errorHandlerMiddleware);
return applyMiddleware(...middlewares)(bridge);
};
const developmentBridge = createBridge(true);
const productionBridge = createBridge(false);
// Feature-based middleware
const createFeatureBridge = (features: { analytics?: boolean; retry?: boolean; logging?: boolean }) => {
const middlewares = [];
if (features.logging) {
middlewares.push(loggerMiddleware);
}
if (features.retry) {
middlewares.push(retryMiddleware);
}
if (features.analytics) {
middlewares.push(analyticsMiddleware);
}
return middlewares.length > 0 ? applyMiddleware(...middlewares)(bridge) : bridge;
};// Middleware with internal state
const createCacheMiddleware = (ttl: number = 60000) => {
const cache = new Map();
return ({ send, subscribe }) => (next) => async (method, props) => {
// Only cache certain methods
const cacheableMethods = ['VKWebAppGetUserInfo', 'VKWebAppGetConfig'];
if (!cacheableMethods.includes(method)) {
return next(method, props);
}
const cacheKey = `${method}:${JSON.stringify(props)}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) {
console.log('Cache hit:', method);
return cached.data;
}
const result = await next(method, props);
cache.set(cacheKey, { data: result, timestamp: Date.now() });
return result;
};
};
const cachedBridge = applyMiddleware(createCacheMiddleware(30000))(bridge);// Middleware with async initialization
const createAuthMiddleware = async (authConfig) => {
const authToken = await getStoredAuthToken();
return ({ send, subscribe }) => (next) => async (method, props) => {
// Auto-inject auth token for API calls
if (method === 'VKWebAppCallAPIMethod' && authToken) {
const enhancedProps = {
...props,
params: {
...props.params,
access_token: authToken
}
};
return next(method, enhancedProps);
}
return next(method, props);
};
};
// Async bridge creation
async function createAuthenticatedBridge() {
const authMiddleware = await createAuthMiddleware(authConfig);
return applyMiddleware(authMiddleware)(bridge);
}
const authenticatedBridge = await createAuthenticatedBridge();Install with Tessl CLI
npx tessl i tessl/npm-vkontakte--vk-bridge