Jiffoo Docs

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