Jiffoo Docs
SDK Documentation

Troubleshooting Guide

Common issues and solutions for Jiffoo Mall plugin development

Plugin SDK Troubleshooting

This guide covers common issues, debugging techniques, and solutions for plugin development.

Installation & Setup Issues

Plugin SDK Installation Fails

Symptom: npm install @jiffoo/plugin-sdk fails with dependency errors.

Solution:

# Clear npm cache
npm cache clean --force

# Remove node_modules and lock file
rm -rf node_modules package-lock.json

# Reinstall with specific Node version
nvm use 18
npm install

# If still failing, try with legacy peer deps
npm install --legacy-peer-deps

Root Cause: Incompatible Node.js version or conflicting peer dependencies.

CLI Commands Not Found

Symptom: npx @jiffoo/plugin-sdk init returns "command not found".

Solution:

# Verify installation
npm list @jiffoo/plugin-sdk

# Reinstall globally
npm install -g @jiffoo/plugin-sdk

# Or use full npx path
npx --package=@jiffoo/plugin-sdk plugin-sdk init

TypeScript Compilation Errors

Symptom: Type errors when importing SDK modules.

Solution:

Update your tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": true,
    "resolveJsonModule": true
  }
}

Authentication & Security

Signature Verification Fails

Symptom: Requests from the platform are rejected with "Invalid signature" error.

Diagnosis:

import { verifySignature } from '@jiffoo/plugin-sdk';

// Add detailed logging
const verifyMiddleware = (req, res, next) => {
  const secret = process.env.PLUGIN_SECRET;
  const timestamp = req.headers['x-platform-timestamp'];
  const signature = req.headers['x-platform-signature'];

  console.log('Verification details:', {
    method: req.method,
    path: req.path,
    timestamp,
    signature,
    secretLength: secret?.length,
    bodyKeys: Object.keys(req.body),
  });

  const isValid = verifySignature(
    secret,
    req.method,
    req.path,
    req.body,
    timestamp,
    signature
  );

  console.log('Signature valid:', isValid);

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  next();
};

Common Causes:

  1. Clock Skew: Server time is out of sync
// Check timestamp skew
const timestamp = parseInt(req.headers['x-platform-timestamp']);
const now = Date.now();
const skew = Math.abs(now - timestamp);

if (skew > 300000) {
  // 5 minutes
  console.error('Clock skew detected:', {
    serverTime: new Date(now).toISOString(),
    requestTime: new Date(timestamp).toISOString(),
    skewMs: skew,
  });
}

Solution: Sync server time with NTP

# Ubuntu/Debian
sudo apt-get install ntp
sudo systemctl restart ntp

# macOS
sudo sntp -sS time.apple.com
  1. Wrong Secret: Using incorrect or expired secret
// Verify secret format
console.log('Secret format:', {
  length: process.env.PLUGIN_SECRET?.length,
  prefix: process.env.PLUGIN_SECRET?.substring(0, 10),
});

Solution: Get fresh secret from plugin settings in the platform dashboard.

  1. Body Parsing Issues: Request body modified before verification
// Preserve raw body for signature verification
app.use(
  express.json({
    verify: (req, res, buf) => {
      req.rawBody = buf.toString('utf8');
    },
  })
);

// Use raw body in verification
const isValid = verifySignature(
  secret,
  req.method,
  req.path,
  req.rawBody, // Use raw body, not parsed
  timestamp,
  signature
);

Unauthorized API Requests

Symptom: Plugin cannot make requests to platform API (401/403 errors).

Solution:

import { createClient } from '@jiffoo/plugin-sdk';

// Ensure correct credentials
const client = createClient({
  baseUrl: process.env.PLATFORM_API_URL || 'https://api.jiffoo.com',
  installationId: ctx.installationId,
  secret: process.env.PLUGIN_SECRET,
});

// Add request logging
client.interceptors.request.use((config) => {
  console.log('API Request:', {
    method: config.method,
    url: config.url,
    hasAuth: !!config.headers['Authorization'],
  });
  return config;
});

// Add error logging
client.interceptors.response.use(
  (response) => response,
  (error) => {
    console.error('API Error:', {
      status: error.response?.status,
      message: error.response?.data?.message,
      url: error.config?.url,
    });
    throw error;
  }
);

Plugin Lifecycle Issues

Installation Handler Fails

Symptom: Plugin installation hangs or returns error.

Debugging:

plugin.onInstall(async (ctx: PluginContext) => {
  const { tenantId, installationId, config } = ctx;

  try {
    ctx.logger.info('Starting installation', {
      tenantId,
      installationId,
      configKeys: Object.keys(config),
    });

    // Validate configuration
    const requiredFields = ['apiKey', 'webhookUrl'];
    for (const field of requiredFields) {
      if (!config[field]) {
        ctx.logger.error('Missing required config', { field });
        throw new Error(`Missing required configuration: ${field}`);
      }
    }

    // Test external service connection
    ctx.logger.info('Testing external service connection');
    const externalService = initializeService(config);
    await externalService.testConnection();

    ctx.logger.info('Installation successful');
    return { success: true };
  } catch (error) {
    ctx.logger.error('Installation failed', {
      error: error.message,
      stack: error.stack,
    });

    // Return detailed error to platform
    return {
      success: false,
      error: error.message,
      details: {
        step: 'installation',
        timestamp: new Date().toISOString(),
      },
    };
  }
});

Common Issues:

  1. Timeout: Installation takes too long
// Use background job for long-running tasks
plugin.onInstall(async (ctx) => {
  // Quick synchronous setup
  await quickSetup(ctx);

  // Queue background job for heavy work
  await queue.add('complete-installation', {
    tenantId: ctx.tenantId,
    installationId: ctx.installationId,
  });

  return { success: true, backgroundJobQueued: true };
});
  1. External Service Unavailable: Third-party API is down
// Implement retry with exponential backoff
async function installWithRetry(ctx, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await performInstallation(ctx);
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      const delay = Math.pow(2, i) * 1000;
      ctx.logger.warn(`Installation attempt ${i + 1} failed, retrying in ${delay}ms`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}

Plugin Not Receiving Requests

Symptom: Platform shows plugin as healthy, but endpoints return 404.

Checklist:

  1. Verify plugin is running:
# Check health endpoint
curl http://localhost:3000/health

# Check process
ps aux | grep node

# Check logs
tail -f logs/plugin.log
  1. Verify manifest routes:
{
  "slug": "my-plugin",
  "endpoints": [
    {
      "path": "/api/process-payment",
      "method": "POST"
    }
  ]
}
  1. Check URL construction:
// Correct: Relative path
plugin.handle('POST', '/api/process-payment', handler);

// Incorrect: Absolute URL
plugin.handle('POST', 'http://localhost:3000/api/process-payment', handler);
  1. Verify network accessibility:
# Test from external host
curl -X POST https://your-plugin-url.com/api/process-payment \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

# Check firewall rules
sudo ufw status

# Check reverse proxy config (nginx)
sudo nginx -t
sudo systemctl status nginx

Webhooks Not Being Received

Symptom: Platform webhooks are not triggering plugin handlers.

Debugging:

plugin.handle('POST', '/webhook', async (ctx, req) => {
  // Log all webhook requests
  ctx.logger.info('Webhook received', {
    headers: req.headers,
    bodyKeys: Object.keys(req.body),
    timestamp: new Date().toISOString(),
  });

  try {
    // Process webhook
    const result = await processWebhook(req.body);

    ctx.logger.info('Webhook processed', { result });
    return { received: true };
  } catch (error) {
    ctx.logger.error('Webhook processing failed', {
      error: error.message,
      stack: error.stack,
    });

    // Return 200 to prevent retries for invalid data
    if (error.code === 'INVALID_DATA') {
      return { received: false, error: 'Invalid data' };
    }

    // Return 500 to trigger retry
    throw error;
  }
});

Common Issues:

  1. Webhook URL not accessible: Platform cannot reach your endpoint
# Test webhook URL accessibility
curl -X POST https://your-webhook-url.com/webhook \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

# Check DNS resolution
nslookup your-webhook-url.com

# Check SSL certificate
openssl s_client -connect your-webhook-url.com:443
  1. Signature verification rejects valid webhooks: See "Signature Verification Fails" above.

  2. Webhook handler throws error: Platform will retry failed webhooks

// Implement idempotency
plugin.handle('POST', '/webhook', async (ctx, req) => {
  const eventId = req.headers['x-webhook-id'];

  // Check if already processed
  const processed = await cache.get(`webhook:${eventId}`);
  if (processed) {
    ctx.logger.info('Webhook already processed', { eventId });
    return { received: true, duplicate: true };
  }

  // Process webhook
  await processWebhook(req.body);

  // Mark as processed (store for 24 hours)
  await cache.set(`webhook:${eventId}`, '1', 86400);

  return { received: true };
});

Performance Issues

High Memory Usage

Symptom: Plugin consumes excessive memory, leading to crashes.

Diagnosis:

// Monitor memory usage
setInterval(() => {
  const usage = process.memoryUsage();
  console.log('Memory usage:', {
    rss: `${Math.round(usage.rss / 1024 / 1024)} MB`,
    heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)} MB`,
    heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)} MB`,
    external: `${Math.round(usage.external / 1024 / 1024)} MB`,
  });
}, 60000); // Every minute

Common Causes:

  1. Memory Leaks: Event listeners or intervals not cleaned up
// Bad: Leak
plugin.handle('GET', '/api/data', async (ctx) => {
  setInterval(() => {
    // This interval is never cleared!
    fetchData();
  }, 1000);
});

// Good: Cleanup
const intervals = new Map();

plugin.handle('GET', '/api/data', async (ctx) => {
  const intervalId = setInterval(() => {
    fetchData();
  }, 1000);

  intervals.set(ctx.tenantId, intervalId);

  // Clear on disconnect
  ctx.on('disconnect', () => {
    const id = intervals.get(ctx.tenantId);
    if (id) {
      clearInterval(id);
      intervals.delete(ctx.tenantId);
    }
  });
});
  1. Large Data in Memory: Caching too much data
// Bad: Unlimited cache
const cache = {};

async function getData(id) {
  if (!cache[id]) {
    cache[id] = await fetchData(id);
  }
  return cache[id];
}

// Good: Use proper cache with TTL and size limit
import { createCache } from '@jiffoo/plugin-sdk';

const cache = createCache({
  type: 'memory',
  ttl: 3600,
  max: 1000, // Max 1000 entries
});
  1. Streaming Large Files: Loading entire file into memory
// Bad: Load entire file
const data = await fs.readFile('large-file.csv', 'utf8');
processData(data);

// Good: Stream processing
const stream = fs.createReadStream('large-file.csv');
stream.pipe(csvParser()).pipe(processor());

Slow Response Times

Symptom: API endpoints take too long to respond.

Diagnosis:

// Add timing middleware
plugin.use(async (ctx, req, next) => {
  const start = Date.now();

  await next();

  const duration = Date.now() - start;
  ctx.logger.info('Request completed', {
    method: req.method,
    path: req.path,
    duration: `${duration}ms`,
    statusCode: ctx.response?.status,
  });

  // Alert on slow requests
  if (duration > 1000) {
    ctx.logger.warn('Slow request detected', {
      duration,
      path: req.path,
    });
  }
});

Solutions:

  1. Database Query Optimization:
// Bad: N+1 queries
const orders = await db.query('SELECT * FROM orders WHERE tenant_id = $1', [
  tenantId,
]);
for (const order of orders.rows) {
  const items = await db.query('SELECT * FROM items WHERE order_id = $1', [
    order.id,
  ]);
  order.items = items.rows;
}

// Good: Single query with join
const orders = await db.query(
  `SELECT
     o.*,
     json_agg(i.*) as items
   FROM orders o
   LEFT JOIN items i ON i.order_id = o.id
   WHERE o.tenant_id = $1
   GROUP BY o.id`,
  [tenantId]
);
  1. Add Caching:
import { createCache } from '@jiffoo/plugin-sdk';

const cache = createCache({ type: 'redis', ttl: 300 });

plugin.handle('GET', '/api/tenant-config', async (ctx) => {
  const cacheKey = `config:${ctx.tenantId}`;

  // Try cache first
  const cached = await cache.get(cacheKey);
  if (cached) {
    return cached;
  }

  // Fetch from database
  const config = await fetchConfig(ctx.tenantId);

  // Store in cache
  await cache.set(cacheKey, config);

  return config;
});
  1. Use Background Jobs:
// Bad: Synchronous heavy processing
plugin.handle('POST', '/api/import', async (ctx, req) => {
  const data = req.body.data; // Large dataset
  await processAllData(data); // Takes 30 seconds
  return { success: true };
});

// Good: Queue background job
plugin.handle('POST', '/api/import', async (ctx, req) => {
  const jobId = await queue.add('import-data', {
    tenantId: ctx.tenantId,
    data: req.body.data,
  });

  return {
    success: true,
    jobId,
    status: 'processing',
  };
});

Database Connection Issues

Symptom: "Too many connections" or "Connection timeout" errors.

Solution:

import { createDatabase } from '@jiffoo/plugin-sdk';

// Configure connection pool
const db = createDatabase({
  type: 'postgres',
  url: process.env.DATABASE_URL,
  pool: {
    min: 2,
    max: 10,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000,
  },
});

// Add connection error handling
db.on('error', (err) => {
  console.error('Database error:', err);
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  await db.close();
  process.exit(0);
});

Configuration Issues

Environment Variables Not Loading

Symptom: process.env.VARIABLE is undefined.

Solution:

  1. Use dotenv:
// Load at app entry point
import 'dotenv/config';

// Or explicitly
import dotenv from 'dotenv';
dotenv.config();
  1. Check .env file location:
# .env should be in project root
ls -la .env

# Verify contents (be careful not to expose secrets)
cat .env | grep -v "SECRET"
  1. Verify build includes env:
// package.json
{
  "scripts": {
    "build": "tsc",
    "start": "node -r dotenv/config dist/index.js"
  }
}

Invalid Configuration Schema

Symptom: Configuration UI doesn't render or validation fails.

Debugging:

# Validate manifest
npx @jiffoo/plugin-sdk validate --manifest ./manifest.json

# Check for schema errors
npx @jiffoo/plugin-sdk validate --verbose

Common Errors:

// Bad: Invalid type
{
  "apiKey": {
    "type": "password", // Should be "secret"
    "required": true
  }
}

// Good: Correct type
{
  "apiKey": {
    "type": "secret",
    "label": "API Key",
    "required": true,
    "description": "Your API key from the provider"
  }
}

Testing Issues

Mock Context Not Working

Symptom: Tests fail with "Cannot read property of undefined".

Solution:

import { createMockContext } from '@jiffoo/plugin-sdk/testing';

describe('Plugin Handler', () => {
  it('should process payment', async () => {
    // Create complete mock context
    const ctx = createMockContext({
      tenantId: 'test-tenant',
      installationId: 'test-installation',
      config: {
        apiKey: 'test-api-key',
        environment: 'test',
      },
      headers: {
        'user-agent': 'test',
      },
    });

    // Mock logger
    ctx.logger = {
      info: jest.fn(),
      error: jest.fn(),
      warn: jest.fn(),
      debug: jest.fn(),
    };

    // Mock client
    ctx.client = {
      get: jest.fn().mockResolvedValue({ data: {} }),
      post: jest.fn().mockResolvedValue({ data: {} }),
    };

    const result = await handler(ctx, {
      body: { amount: 1000 },
    });

    expect(result.success).toBe(true);
  });
});

Integration Tests Fail in CI

Symptom: Tests pass locally but fail in CI/CD pipeline.

Common Causes:

  1. Environment differences:
# .github/workflows/test.yml
name: Test
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - run: npm ci
      - run: npm test
        env:
          DATABASE_URL: postgresql://postgres:postgres@postgres:5432/test
          REDIS_URL: redis://redis:6379
  1. Timing issues:
// Bad: Hardcoded delay
await new Promise((resolve) => setTimeout(resolve, 1000));

// Good: Wait for condition
await waitFor(() => jobCompleted, { timeout: 5000 });

Debugging Tools

Enable Debug Logging

# Development
DEBUG=plugin:* npm run dev

# Production with structured logging
LOG_LEVEL=debug npm start

Use Plugin SDK Logger

import { createLogger } from '@jiffoo/plugin-sdk';

const logger = createLogger({
  name: 'my-plugin',
  level: process.env.LOG_LEVEL || 'info',
  prettyPrint: process.env.NODE_ENV !== 'production',
});

logger.debug('Debug information', { data: complexObject });
logger.info('Operation completed', { duration: '120ms' });
logger.warn('Deprecation warning', { feature: 'oldMethod' });
logger.error('Operation failed', { error: error.message, stack: error.stack });

Request/Response Logging

// Log all requests
plugin.use(async (ctx, req, next) => {
  const requestId = req.headers['x-request-id'] || generateId();

  ctx.logger.child({ requestId }).info('Request received', {
    method: req.method,
    path: req.path,
    headers: req.headers,
    body: req.body,
  });

  try {
    await next();

    ctx.logger.child({ requestId }).info('Response sent', {
      status: ctx.response?.status,
      duration: ctx.duration,
    });
  } catch (error) {
    ctx.logger.child({ requestId }).error('Request failed', {
      error: error.message,
      stack: error.stack,
    });
    throw error;
  }
});

Getting Help

Before Asking for Help

  1. Check logs for error messages and stack traces
  2. Verify configuration with npx @jiffoo/plugin-sdk validate
  3. Test in isolation to rule out external dependencies
  4. Search documentation and existing issues

Providing Information

When seeking help, include:

// Plugin information
const diagnostics = {
  plugin: {
    name: 'my-plugin',
    version: '1.0.0',
    sdkVersion: require('@jiffoo/plugin-sdk/package.json').version,
  },
  environment: {
    nodeVersion: process.version,
    platform: process.platform,
    env: process.env.NODE_ENV,
  },
  error: {
    message: error.message,
    stack: error.stack,
    code: error.code,
  },
  context: {
    timestamp: new Date().toISOString(),
    tenantId: ctx.tenantId,
    installationId: ctx.installationId,
  },
};

console.log(JSON.stringify(diagnostics, null, 2));

Support Channels

Next Steps

On this page