# JavaScript / TypeScript 示例

## 安装依赖

```bash
# Node.js 18+ 内置 fetch，无需额外依赖
# 如需使用 axios:
npm install axios
```

## 配置

```javascript
const EASEBG_API_KEY = process.env.EASEBG_API_KEY || 'your_api_key_here';
const EASEBG_API_BASE = process.env.EASEBG_API_BASE || 'https://api.easebg.com/api/v1';

const headers = {
  'X-API-Key': EASEBG_API_KEY,
  'Content-Type': 'application/json',
};
```

## 1. 创建抠图任务

```javascript
async function createTask(imageUrl, options = {}) {
  const response = await fetch(`${EASEBG_API_BASE}/open/developer/tasks`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      imageUrl,
      modelVersion: options.modelVersion || 'easebg-2.0',
      callbackUrl: options.callbackUrl,
      metadata: options.metadata,
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`API Error ${response.status}: ${error.error?.message || 'Unknown error'}`);
  }

  return response.json();
}

// 使用示例
try {
  const result = await createTask('https://example.com/photo.jpg', {
    callbackUrl: 'https://your-app.com/webhooks/easebg',
  });
  console.log('Task created:', result.data.id);
} catch (err) {
  console.error('Failed to create task:', err.message);
}
```

## 2. 查询任务状态

```javascript
async function getTask(taskId) {
  const response = await fetch(`${EASEBG_API_BASE}/open/developer/tasks/${taskId}`, {
    headers,
  });

  if (!response.ok) {
    throw new Error(`Failed to get task: ${response.statusText}`);
  }

  return response.json();
}

// 轮询任务状态
async function waitForTask(taskId, intervalMs = 2000, maxAttempts = 60) {
  for (let i = 0; i < maxAttempts; i++) {
    const { data } = await getTask(taskId);

    if (data.status === 'succeeded') {
      return data;
    }

    if (data.status === 'failed') {
      throw new Error(`Task failed: ${data.errorMessage}`);
    }

    await new Promise(resolve => setTimeout(resolve, intervalMs));
  }

  throw new Error('Task timed out');
}
```

## 3. SSE 实时任务流（浏览器 / App）

对于 App/Web 前台，可通过 SSE（Server-Sent Events）实时获取任务进度，无需轮询。

> **认证方式说明**：浏览器 `EventSource` API 不支持自定义 Header，因此 SSE 连接使用 `?token=` query 参数传递 JWT，而非 `Authorization` Header 或 `withCredentials`。
>
> **替代方案**：如 SSE 不可用，可轮询 `GET /app/tasks/images/{taskId}/events` 获取事件流水，或使用上方「查询任务状态」中的 `waitForTask` 轮询函数。

```javascript
// SSE 连接（使用 token query 参数认证）
function subscribeTaskStream(taskId, accessToken, callbacks = {}) {
  const url = `${EASEBG_API_BASE}/app/tasks/images/${taskId}/stream?token=${encodeURIComponent(accessToken)}`;

  const eventSource = new EventSource(url);

  // 任务进度事件
  eventSource.addEventListener('progress', (e) => {
    const data = JSON.parse(e.data);
    console.log('进度:', data.progress + '%');
    callbacks.onProgress?.(data);
  });

  // 任务完成事件
  eventSource.addEventListener('completed', (e) => {
    const data = JSON.parse(e.data);
    console.log('完成:', data.resultUrl);
    callbacks.onCompleted?.(data);
    eventSource.close();
  });

  // 任务失败事件
  eventSource.addEventListener('failed', (e) => {
    const data = JSON.parse(e.data);
    console.error('失败:', data.errorMessage);
    callbacks.onFailed?.(data);
    eventSource.close();
  });

  // 连接错误
  eventSource.onerror = (e) => {
    console.error('SSE 连接错误');
    callbacks.onError?.(e);
    eventSource.close();
  };

  // 返回关闭函数
  return () => eventSource.close();
}

// 使用示例
const unsubscribe = subscribeTaskStream(
  '550e8400-e29b-41d4-a716-446655440000',
  accessToken,
  {
    onProgress: (data) => updateProgressBar(data.progress),
    onCompleted: (data) => showResult(data.resultUrl),
    onFailed: (data) => showError(data.errorMessage),
  }
);

// 需要时手动取消订阅
// unsubscribe();
```

<details>
<summary>Node.js 中使用 SSE（需 eventsource polyfill）</summary>

```bash
npm install eventsource
```

```javascript
const EventSource = require('eventsource');

// Node.js 中同样使用 token query 参数认证
const es = new EventSource(
  `${EASEBG_API_BASE}/app/tasks/images/${taskId}/stream?token=${accessToken}`
);

es.addEventListener('completed', (e) => {
  console.log('完成:', JSON.parse(e.data));
  es.close();
});
```

</details>

## 4. 获取任务结果

```javascript
async function downloadResult(taskId) {
  const { data } = await getTask(taskId);

  if (data.status !== 'succeeded' || !data.resultUrl) {
    throw new Error('Task not completed or no result available');
  }

  const imageResponse = await fetch(data.resultUrl);
  const blob = await imageResponse.blob();

  return blob;
}
```

## 5. 取消任务

```javascript
async function cancelTask(taskId) {
  const response = await fetch(`${EASEBG_API_BASE}/open/developer/tasks/${taskId}`, {
    method: 'DELETE',
    headers,
  });

  if (response.status === 204) {
    console.log('Task canceled successfully');
    return true;
  }

  if (response.status === 409) {
    throw new Error('Task cannot be canceled in current state');
  }

  throw new Error(`Cancel failed: ${response.statusText}`);
}
```

## 6. 创建批量任务

```javascript
async function createBatchTask(imageUrls, options = {}) {
  const response = await fetch(`${EASEBG_API_BASE}/open/developer/batch-tasks`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      imageUrls,
      modelVersion: options.modelVersion || 'easebg-2.0',
      callbackUrl: options.callbackUrl,
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Batch task failed: ${error.error?.message}`);
  }

  return response.json();
}

// 使用示例
const batchResult = await createBatchTask([
  'https://example.com/photo1.jpg',
  'https://example.com/photo2.jpg',
  'https://example.com/photo3.jpg',
], {
  callbackUrl: 'https://your-app.com/webhooks/easebg',
});
```

## 7. Webhook 签名验证

```javascript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const computedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  // 使用时间安全比较防止时序攻击
  return crypto.timingSafeEqual(
    Buffer.from(computedSignature, 'hex'),
    Buffer.from(signature, 'hex'),
  );
}

// Express 中间件示例
const express = require('express');
const app = express();

app.post('/webhooks/easebg', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-easebg-signature'];
  const secret = process.env.EASEBG_WEBHOOK_SECRET;

  if (!verifyWebhookSignature(req.body, signature, secret)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body);
  console.log('Received webhook:', event.event, event.taskId);

  // 处理事件
  switch (event.event) {
    case 'task.succeeded':
      console.log('Task succeeded:', event.resultUrl);
      break;
    case 'task.failed':
      console.log('Task failed:', event.errorMessage);
      break;
  }

  res.status(200).json({ received: true });
});
```

## 8. 错误处理

```javascript
class EaseBGApiError extends Error {
  constructor(statusCode, code, message, details) {
    super(message);
    this.name = 'EaseBGApiError';
    this.statusCode = statusCode;
    this.code = code;
    this.details = details;
  }
}

async function apiRequest(path, options = {}) {
  const response = await fetch(`${EASEBG_API_BASE}${path}`, {
    ...options,
    headers: { ...headers, ...options.headers },
  });

  const body = await response.json();

  if (!response.ok) {
    throw new EaseBGApiError(
      response.status,
      body.error?.code || 'UNKNOWN',
      body.error?.message || 'Request failed',
      body.error?.details,
    );
  }

  return body;
}

// 使用示例
try {
  const result = await apiRequest('/open/developer/tasks', {
    method: 'POST',
    body: JSON.stringify({ imageUrl: 'https://example.com/photo.jpg' }),
  });
} catch (err) {
  if (err instanceof EaseBGApiError) {
    switch (err.code) {
      case 'INSUFFICIENT_CREDITS':
        console.error('积分不足，请充值');
        break;
      case 'RATE_LIMIT_EXCEEDED':
        console.error('请求频率超限，请稍后重试');
        break;
      case 'INVALID_API_KEY':
        console.error('API Key 无效');
        break;
      default:
        console.error(`API 错误 [${err.code}]: ${err.message}`);
    }
  } else {
    console.error('网络错误:', err.message);
  }
}
```

## 9. 分页查询

```javascript
async function listTasks(params = {}) {
  const query = new URLSearchParams({
    page: String(params.page || 1),
    pageSize: String(params.pageSize || 20),
    ...(params.status && { status: params.status }),
    ...(params.startDate && { startDate: params.startDate }),
    ...(params.endDate && { endDate: params.endDate }),
  });

  const response = await fetch(
    `${EASEBG_API_BASE}/open/developer/tasks?${query}`,
    { headers },
  );

  return response.json();
}

// 自动翻页获取所有任务
async function getAllTasks(status) {
  const allTasks = [];
  let page = 1;
  let totalPages = 1;

  while (page <= totalPages) {
    const { data, meta } = await listTasks({ page, status });
    allTasks.push(...data);
    totalPages = meta.totalPages;
    page++;
  }

  return allTasks;
}
```
