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-depsRoot 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 initTypeScript 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:
- 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- 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.
- 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:
- 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 };
});- 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:
- 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- Verify manifest routes:
{
"slug": "my-plugin",
"endpoints": [
{
"path": "/api/process-payment",
"method": "POST"
}
]
}- 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);- 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 nginxWebhooks 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:
- 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-
Signature verification rejects valid webhooks: See "Signature Verification Fails" above.
-
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 minuteCommon Causes:
- 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);
}
});
});- 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
});- 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:
- 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]
);- 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;
});- 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:
- Use dotenv:
// Load at app entry point
import 'dotenv/config';
// Or explicitly
import dotenv from 'dotenv';
dotenv.config();- 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"- 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 --verboseCommon 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:
- 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- 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 startUse 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
- Check logs for error messages and stack traces
- Verify configuration with
npx @jiffoo/plugin-sdk validate - Test in isolation to rule out external dependencies
- 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
- Documentation: docs.jiffoo.com
- GitHub Issues: github.com/jiffoo/plugin-sdk/issues
- Discord Community: discord.gg/jiffoo
- Email Support: [email protected]
Next Steps
- Review Plugin SDK Reference
- Explore Advanced Examples
- Join the Developer Community