CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vkontakte--vk-bridge

Bridge library for VK Mini Apps to communicate with VK clients across iOS, Android, and Web platforms

Pending
Overview
Eval results
Files

middleware.mddocs/

Middleware System

Redux-style middleware system for intercepting and processing VK Bridge communications, enabling logging, data transformation, error handling, and custom request/response processing.

Capabilities

Apply Middleware

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');

Built-in Middleware Examples

Logging Middleware

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);

Error Handling Middleware

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);

Data Transformation Middleware

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);

Analytics Middleware

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);

Multiple Middleware Composition

/**
 * 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');

Conditional Middleware

// 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;
};

Advanced Middleware Patterns

Middleware with State

// 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);

Async Middleware

// 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

docs

advertising-monetization.md

application-lifecycle.md

authentication.md

core-bridge.md

device-features.md

geolocation.md

index.md

launch-parameters.md

middleware.md

payments-commerce.md

qr-barcode-scanning.md

social-features.md

storage-data.md

ui-display.md

user-data.md

tile.json