Now.js Framework Documentation

Now.js Framework Documentation

AuthErrorHandler - การจัดการข้อผิดพลาดสำหรับการตรวจสอบสิทธิ์

TH 31 Oct 2025 01:34

AuthErrorHandler - การจัดการข้อผิดพลาดสำหรับการตรวจสอบสิทธิ์

เอกสารฉบับนี้อธิบาย AuthErrorHandler ซึ่งเป็นระบบจัดการข้อผิดพลาดสำหรับกระบวนการตรวจสอบสิทธิ์ของ Now.js Framework

📋 สารบัญ

  1. ภาพรวม
  2. การติดตั้งและนำเข้า
  3. การเริ่มต้นใช้งาน
  4. ประเภท Error
  5. การดำเนินการ Error
  6. การจัดการ Error แบบกำหนดเอง
  7. เหตุการณ์ Error
  8. ตรรกะการลองใหม่
  9. การกู้คืนจาก Error
  10. ตัวอย่างการใช้งาน
  11. เอกสารอ้างอิง API
  12. แนวทางปฏิบัติที่ดี
  13. ข้อผิดพลาดที่พบบ่อย

ภาพรวม

AuthErrorHandler จัดการข้อผิดพลาด และข้อยกเว้น ที่เกิดขึ้นในกระบวนการตรวจสอบสิทธิ์ พร้อมส่งต่อการตอบสนองและกระบวนการกู้คืนอย่างเหมาะสม

ฟีเจอร์หลัก

  • การจัดหมวดหมู่ข้อผิดพลาด: แยกประเภทข้อผิดพลาดอย่างชัดเจน
  • การกำหนดการทำงาน: กำหนดลำดับการตอบสนองสำหรับแต่ละข้อผิดพลาดได้เอง
  • ตัวจัดการแบบกำหนดเอง: รองรับการต่อยอดด้วยตัวจัดการข้อผิดพลาดเพิ่มเติม
  • ตรรกะการลองใหม่: ลองใหม่อัตโนมัติสำหรับข้อผิดพลาดที่รองรับ
  • กลไกการกู้คืน: มีกระบวนการกู้คืนเมื่อเกิดข้อผิดพลาดร้ายแรง
  • ระบบเหตุการณ์: กระจายเหตุการณ์สำหรับการติดตามและบันทึก
  • การแจ้งเตือนผู้ใช้: สื่อสารสถานะข้อผิดพลาดให้ผู้ใช้ทราบทันที
  • การบันทึกข้อผิดพลาด: เก็บรายละเอียดข้อผิดพลาดเพื่อใช้ดีบักและวิเคราะห์
  • การลดทอนฟีเจอร์อย่างสุภาพ: ลดฟังก์ชันที่ไม่สำคัญเพื่อให้ระบบทำงานต่อได้
  • ขอบเขตข้อผิดพลาด: จำกัดไม่ให้ข้อผิดพลาดลุกลามกระทบส่วนอื่น

เมื่อไหร่ควรใช้ AuthErrorHandler

ใช้ AuthErrorHandler เมื่อ:

  • ต้องการระบบจัดการข้อผิดพลาดในการตรวจสอบสิทธิ์แบบครบวงจร
  • ต้องการตรรกะการจัดการข้อผิดพลาดที่ปรับแต่งเองได้
  • ต้องการติดตามและบันทึกข้อผิดพลาดอย่างเป็นระบบ
  • ต้องการตรรกะการลองใหม่สำหรับข้อผิดพลาดที่เกิดซ้ำได้
  • ต้องการแจ้งเตือนผู้ใช้ทันทีเมื่อเกิดข้อผิดพลาดสำคัญ

ไม่ควรใช้เมื่อ:

  • ต้องการจัดการข้อผิดพลาดด้วยโค้ดแบบแมนนวลทั้งหมด
  • แอปพลิเคชันไม่ได้ใช้ระบบตรวจสอบสิทธิ์ของ Now.js

การติดตั้งและนำเข้า

AuthErrorHandler โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:

// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.AuthErrorHandler); // อ็อบเจ็กต์ AuthErrorHandler ที่ผูกไว้กับ window

การเริ่มต้นใช้งาน

การตั้งค่าพื้นฐาน

// AuthErrorHandler ทำงานร่วมกับ AuthManager โดยอัตโนมัติ
// ไม่จำเป็นต้องมีขั้นตอนเริ่มต้นแยกต่างหาก

await AuthManager.init({
  enabled: true,
  endpoints: {
    login: '/api/auth/login',
    verify: '/api/auth/verify'
  },

  // การกำหนดค่าการจัดการข้อผิดพลาด
  errorHandling: {
    // จำนวนครั้งสูงสุดในการลองใหม่
    maxRetries: 3,

    // ระยะเวลาหน่วงก่อนลองใหม่ (มิลลิวินาที)
    retryDelay: 1000,

    // แสดงการแจ้งเตือนให้ผู้ใช้
    showNotifications: true,

    // บันทึกข้อผิดพลาดลงคอนโซล
    logErrors: true
  }
});

console.log('AuthErrorHandler เริ่มทำงานร่วมกับ AuthManager แล้ว');

Error Handling Flow

การดำเนินการล้มเหลว
     ↓
┌────────────────────┐
│  จำแนกข้อผิดพลาด │
│  - เครือข่ายหรือไม่?  │
│  - การตรวจสอบสิทธิ์หรือไม่? │
│  - การอนุญาตหรือไม่? │
│  - การตรวจสอบข้อมูลหรือไม่? │
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ เลือกการดำเนินการ │
│  - ลองใหม่หรือไม่? │
│  - เปลี่ยนเส้นทางหรือไม่? │
│  - แจ้งเตือนหรือไม่? │
│  - บันทึกหรือไม่? │
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ ปฏิบัติตามแผนที่เลือก │
│  - ลองทำงานซ้ำ      │
│  - เปลี่ยนเส้นทาง   │
│  - แสดงข้อความ      │
│  - บันทึกข้อผิดพลาด │
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ ส่งเหตุการณ์ข้อผิดพลาด │
│  - เรียกตัวจัดการแบบกำหนดเอง │
│  - บันทึกเพื่อติดตาม │
│  - เก็บ log เพิ่มเติม │
└────────────────────┘

ประเภท Error

AuthErrorHandler รองรับ error types หลักๆ:

1. NETWORK_ERROR

การเชื่อมต่อเครือข่ายล้มเหลว

// เกิดขึ้นเมื่อ:
// - ไม่มีการเชื่อมต่ออินเทอร์เน็ต
// - ไม่สามารถเข้าถึงเซิร์ฟเวอร์ได้
// - คำขอหมดเวลา
// - ถูกปฏิเสธโดยนโยบาย CORS

// การดำเนินการเริ่มต้น: ลองใหม่ด้วยการหน่วงแบบยกกำลัง
{
  type: 'NETWORK_ERROR',
  message: 'Network connection failed',
  retryable: true,
  action: 'retry'
}

2. UNAUTHORIZED

ยังไม่ได้รับการตรวจสอบสิทธิ์หรือโทเค็นหมดอายุ

// เกิดขึ้นเมื่อ:
// - ไม่มีโทเค็นสำหรับพิสูจน์ตัวตน
// - โทเค็นหมดอายุ
// - โทเค็นไม่ถูกต้อง
// - โทเค็นถูกเพิกถอน

// การดำเนินการเริ่มต้น: เปลี่ยนเส้นทางไปหน้าลงชื่อเข้าใช้
{
  type: 'UNAUTHORIZED',
  message: 'Authentication required',
  retryable: false,
  action: 'redirect',
  target: '/login'
}

3. FORBIDDEN

ตรวจสอบสิทธิ์แล้วแต่ไม่มีสิทธิ์เข้าถึง

// เกิดขึ้นเมื่อ:
// - ไม่มีบทบาทที่ต้องการ
// - ไม่มีสิทธิ์ที่กำหนดไว้
// - การเข้าถึงถูกปฏิเสธโดยนโยบายความปลอดภัย

// การดำเนินการเริ่มต้น: แสดงหน้าแจ้งข้อผิดพลาด
{
  type: 'FORBIDDEN',
  message: 'Access denied',
  retryable: false,
  action: 'render',
  target: '/403'
}

4. VALIDATION_ERROR

ข้อมูลไม่ผ่านการตรวจสอบความถูกต้อง

// เกิดขึ้นเมื่อ:
// - ข้อมูลประจำตัวไม่ถูกต้อง
// - ขาดฟิลด์ที่จำเป็น
// - รูปแบบข้อมูลไม่ตรงตามที่กำหนด

// การดำเนินการเริ่มต้น: แสดงรายละเอียดข้อผิดพลาดให้ผู้ใช้ทราบ
{
  type: 'VALIDATION_ERROR',
  message: 'Invalid input',
  errors: {
    email: 'Invalid email format',
    password: 'Password too short'
  },
  retryable: true,
  action: 'notify'
}

5. TOKEN_EXPIRED

โทเค็นหมดอายุ

// เกิดขึ้นเมื่อ:
// - โทเค็นสำหรับเข้าถึง หมดอายุ
// - โทเค็นสำหรับรีเฟรช หมดอายุ

// การดำเนินการเริ่มต้น: พยายามรีเฟรชโทเค็น แล้วค่อยเปลี่ยนเส้นทางถ้าจำเป็น
{
  type: 'TOKEN_EXPIRED',
  message: 'Token expired',
  retryable: true,
  action: 'refresh',
  fallback: 'redirect',
  target: '/login'
}

6. TOKEN_REFRESH_FAILED

การรีเฟรชโทเค็นล้มเหลว

// เกิดขึ้นเมื่อ:
// - โทเค็นสำหรับรีเฟรชไม่ถูกต้อง
// - ปลายทางสำหรับรีเฟรชตอบกลับข้อผิดพลาด
// - ไม่มีโทเค็นสำหรับรีเฟรช

// การดำเนินการเริ่มต้น: เปลี่ยนเส้นทางไปหน้าลงชื่อเข้าใช้ใหม่
{
  type: 'TOKEN_REFRESH_FAILED',
  message: 'Failed to refresh token',
  retryable: false,
  action: 'redirect',
  target: '/login'
}

7. SESSION_EXPIRED

เซสชันหมดอายุ

// เกิดขึ้นเมื่อ:
// - เซสชันหมดเวลา
// - เซสชันถูกยกเลิกด้วยเหตุผลด้านความปลอดภัย
// - มีการออกจากระบบจากอุปกรณ์อื่น

// การดำเนินการเริ่มต้น: เปลี่ยนเส้นทางไปหน้าลงชื่อเข้าใช้ พร้อมแจ้งผู้ใช้
{
  type: 'SESSION_EXPIRED',
  message: 'Your session has expired',
  retryable: false,
  action: 'redirect',
  target: '/login',
  notify: true
}

8. RATE_LIMIT_EXCEEDED

ส่งคำขอเกินขีดจำกัด

// เกิดขึ้นเมื่อ:
// - มีคำขอต่อเนื่องมากเกินไป
// - เกินขีดจำกัดที่เซิร์ฟเวอร์ตั้งไว้

// การดำเนินการเริ่มต้น: หน่วงเวลาตามที่ระบบแนะนำก่อนลองใหม่อีกครั้ง
{
  type: 'RATE_LIMIT_EXCEEDED',
  message: 'Too many requests',
  retryable: true,
  action: 'retry',
  retryAfter: 60000,  // 1 นาที
  notify: true
}

9. SERVER_ERROR

ข้อผิดพลาดภายในเซิร์ฟเวอร์

// เกิดขึ้นเมื่อ:
// - เซิร์ฟเวอร์ส่งสถานะ 500
// - มีข้อยกเว้นเกิดขึ้นในฝั่งเซิร์ฟเวอร์
// - ระบบฐานข้อมูลตอบสนองผิดพลาด

// การดำเนินการเริ่มต้น: ลองใหม่จำนวนจำกัดพร้อมการหน่วงเวลา
{
  type: 'SERVER_ERROR',
  message: 'Server error occurred',
  retryable: true,
  action: 'retry',
  maxRetries: 3
}

10. CSRF_ERROR

โทเค็น CSRF ไม่ถูกต้อง

// เกิดขึ้นเมื่อ:
// - ไม่มีโทเค็น CSRF แนบมากับคำขอ
// - โทเค็น CSRF ไม่ตรงกับที่ระบบคาดหวัง
// - โทเค็น CSRF หมดอายุ

// การดำเนินการเริ่มต้น: รีเฟรชโทเค็น CSRF แล้วลองทำงานใหม่อีกครั้ง
{
  type: 'CSRF_ERROR',
  message: 'CSRF validation failed',
  retryable: true,
  action: 'refresh_csrf',
  fallback: 'reload'
}

11. CUSTOM_ERROR

ข้อผิดพลาดที่นิยามเอง

// เกิดจากตรรกะของแอปพลิเคชันที่กำหนดขึ้นมาเอง
{
  type: 'CUSTOM_ERROR',
  code: 'SUBSCRIPTION_REQUIRED',
  message: 'ต้องมีการสมัครสมาชิกที่ยังใช้งานอยู่',
  retryable: false,
  action: 'custom',
  handler: 'handleSubscriptionError'
}

การดำเนินการ Error

1. การลองใหม่

// ลองทำงานที่ล้มเหลวอีกครั้งโดยอัตโนมัติ
{
  action: 'retry',
  maxRetries: 3,
  retryDelay: 1000,
  backoff: 'exponential'  // linear (เส้นตรง), exponential (ยกกำลัง), fixed (คงที่)
}

// ลองใหม่ด้วยการหน่วงแบบยกกำลัง
// ครั้งที่ 1: หน่วง 1 วินาที
// ครั้งที่ 2: หน่วง 2 วินาที
// ครั้งที่ 3: หน่วง 4 วินาที

2. การเปลี่ยนเส้นทาง

// เปลี่ยนเส้นทางไปยัง route อื่น
{
  action: 'redirect',
  target: '/login',
  reason: 'Authentication required',
  storeIntendedRoute: true  // เก็บเส้นทางปัจจุบันไว้ชั่วคราว
}

// หลังจากลงชื่อเข้าใช้ เปลี่ยนเส้นทางกลับไปยังหน้าที่ต้องการ

3. การแสดงผล

// แสดงหน้าข้อผิดพลาด
{
  action: 'render',
  target: '/403',
  context: {
    error: 'Access denied',
    requiredRole: 'admin'
  }
}

4. การแจ้งเตือน

// แสดงการแจ้งเตือนให้ผู้ใช้
{
  action: 'notify',
  notification: {
    type: 'error',
    title: 'Login Failed',
    message: 'Invalid credentials',
    duration: 5000
  }
}

5. การบล็อก

// บล็อกการนำทาง อยู่ในหน้าปัจจุบัน
{
  action: 'block',
  reason: 'Unsaved changes',
  confirm: true  // แสดงกล่องยืนยันก่อนดำเนินการต่อ
}

6. การรีเฟรช

// รีเฟรชโทเค็นชุดปัจจุบัน
{
  action: 'refresh',
  type: 'token',
  fallback: {
    action: 'redirect',
    target: '/login'
  }
}

7. การออกจากระบบ

// บังคับออกจากระบบ
{
  action: 'logout',
  reason: 'Session invalidated',
  redirect: '/login',
  notify: true
}

8. การดำเนินการแบบกำหนดเอง

// ดำเนินการ handler แบบกำหนดเอง
{
  action: 'custom',
  handler: async (error, context) => {
    console.log('Custom error handler:', error);

    // ตรรกะแบบกำหนดเอง
    if (error.code === 'PAYMENT_REQUIRED') {
      await showPaymentModal();
    }

    return { handled: true };
  }
}

การจัดการ Error แบบกำหนดเอง

1. ตัวจัดการ Error ทั่วไป

// ตั้งค่าตัวจัดการข้อผิดพลาดระดับทั่วทั้งระบบ
AuthErrorHandler.setGlobalHandler(async (error, context) => {
  console.log('Global error handler:', error.type);

  // บันทึกไปยัง external service
  await logErrorToService(error);

  // การแจ้งเตือนแบบกำหนดเอง
  if (error.type === 'UNAUTHORIZED') {
    showCustomLoginModal();
    return { handled: true };
  }

  // ให้ default handler ดำเนินการต่อ
  return { handled: false };
});

2. ตัวจัดการสำหรับ Error แต่ละประเภท

// ลงทะเบียนตัวจัดการสำหรับข้อผิดพลาดแต่ละประเภท
AuthErrorHandler.registerHandler('VALIDATION_ERROR', async (error) => {
  console.log('Validation errors:', error.errors);

  // แสดงข้อความข้อผิดพลาดในฟอร์ม
  Object.keys(error.errors).forEach(field => {
    showFieldError(field, error.errors[field]);
  });

  return { handled: true };
});

// Handler สำหรับ rate limiting
AuthErrorHandler.registerHandler('RATE_LIMIT_EXCEEDED', async (error) => {
  const retryAfter = error.retryAfter || 60000;
  const minutes = Math.ceil(retryAfter / 60000);

  showNotification(
    `Too many attempts. Please try again in ${minutes} minute(s).`,
    'warning'
  );

  return { handled: true };
});

3. การจัดการ Error ตาม Context

// จัดการข้อผิดพลาดตามบริบท
AuthErrorHandler.setContextHandler(async (error, context) => {
  console.log('Context:', context);

  // การจัดการที่แตกต่างกันสำหรับแต่ละการดำเนินการ
  if (context.operation === 'login') {
    if (error.type === 'VALIDATION_ERROR') {
      highlightInvalidFields(error.errors);
      return { handled: true };
    }
  }

  if (context.operation === 'refresh_token') {
    if (error.type === 'TOKEN_REFRESH_FAILED') {
      // บังคับออกจากระบบเมื่อ refresh ล้มเหลว
      await AuthManager.logout();
      return { handled: true };
    }
  }

  return { handled: false };
});

4. ตัวจัดการ Error แบบลูกโซ่

// หลาย handlers ที่ทำงานต่อเนื่อง
AuthErrorHandler.chainHandlers([
  // 1. บันทึกข้อผิดพลาดทั้งหมด
  async (error) => {
    await logError(error);
    return { handled: false };  // ดำเนินการต่อในลูกโซ่
  },

  // 2. ติดตามใน analytics
  async (error) => {
    trackErrorInAnalytics(error);
    return { handled: false };  // ดำเนินการต่อในลูกโซ่
  },

  // 3. การจัดการแบบกำหนดเอง
  async (error) => {
    if (error.type === 'NETWORK_ERROR') {
      showOfflineMode();
      return { handled: true };  // หยุดลูกโซ่
    }
    return { handled: false };
  },

  // 4. Default handler (เฉพาะถ้ายังไม่ได้จัดการ)
  async (error) => {
    showGenericErrorMessage(error);
    return { handled: true };
  }
]);

เหตุการณ์ Error

เหตุการณ์ที่ใช้ได้

// รับฟังเหตุการณ์เกี่ยวกับข้อผิดพลาด

// 1. ข้อผิดพลาดทุกประเภท
document.addEventListener('auth:error', (e) => {
  const { error, context } = e.detail;
  console.log('Auth error:', error.type);
});

// 2. ข้อผิดพลาดแบบเฉพาะเจาะจง
document.addEventListener('auth:error:unauthorized', (e) => {
  console.log('User unauthorized');
});

document.addEventListener('auth:error:forbidden', (e) => {
  console.log('Access forbidden');
});

// 3. ข้อผิดพลาดด้านเครือข่าย
document.addEventListener('auth:error:network', (e) => {
  console.log('Network error');
  showOfflineIndicator();
});

// 4. ข้อผิดพลาดจากการตรวจสอบข้อมูล
document.addEventListener('auth:error:validation', (e) => {
  const { errors } = e.detail;
  console.log('Validation errors:', errors);
});

// 5. ข้อผิดพลาดเกี่ยวกับโทเค็น
document.addEventListener('auth:error:token', (e) => {
  console.log('Token error');
});

// 6. ข้อผิดพลาดของเซสชัน
document.addEventListener('auth:error:session', (e) => {
  console.log('Session error');
});

// 7. ข้อผิดพลาดจากการจำกัดอัตรา (Rate Limit)
document.addEventListener('auth:error:ratelimit', (e) => {
  const { retryAfter } = e.detail;
  console.log(`Rate limited. Retry after ${retryAfter}ms`);
});

// 8. ข้อผิดพลาดจากเซิร์ฟเวอร์
document.addEventListener('auth:error:server', (e) => {
  console.log('Server error');
});

// 9. การกู้คืนหลังเกิดข้อผิดพลาด
document.addEventListener('auth:error:recovered', (e) => {
  console.log('Error recovered');
});

// 10. การลองใหม่เมื่อมีข้อผิดพลาด
document.addEventListener('auth:error:retry', (e) => {
  const { attempt, maxRetries } = e.detail;
  console.log(`Retry attempt ${attempt}/${maxRetries}`);
});

ตรรกะการลองใหม่

1. Automatic Retry (การลองใหม่อัตโนมัติ)

// กำหนดค่าพฤติกรรมการลองใหม่
await AuthManager.init({
  errorHandling: {
    maxRetries: 3,
    retryDelay: 1000,
    retryBackoff: 'exponential',  // linear, exponential, fixed

  // รายการข้อผิดพลาดที่ต้องการให้ลองใหม่
    retryableErrors: [
      'NETWORK_ERROR',
      'SERVER_ERROR',
      'TOKEN_EXPIRED',
      'RATE_LIMIT_EXCEEDED'
    ]
  }
});

2. กลยุทธ์การลองใหม่

การเพิ่มแบบยกกำลัง

// ความหน่วงเพิ่มขึ้นแบบยกกำลัง
// ครั้งที่ 1: 1 วินาที
// ครั้งที่ 2: 2 วินาที
// ครั้งที่ 3: 4 วินาที
// ครั้งที่ 4: 8 วินาที

{
  retryBackoff: 'exponential',
  retryDelay: 1000,
  maxRetries: 4
}

การเพิ่มแบบเส้นตรง

// ความหน่วงเพิ่มขึ้นแบบเส้นตรง
// ครั้งที่ 1: 1 วินาที
// ครั้งที่ 2: 2 วินาที
// ครั้งที่ 3: 3 วินาที
// ครั้งที่ 4: 4 วินาที

{
  retryBackoff: 'linear',
  retryDelay: 1000,
  maxRetries: 4
}

คงที่

// ความหน่วงเท่ากันทุกครั้ง
// ครั้งที่ 1: 1 วินาที
// ครั้งที่ 2: 1 วินาที
// ครั้งที่ 3: 1 วินาที

{
  retryBackoff: 'fixed',
  retryDelay: 1000,
  maxRetries: 3
}

3. ตรรกะการลองใหม่แบบกำหนดเอง

// ตัดสินใจการลองใหม่แบบกำหนดเอง
AuthErrorHandler.setRetryDecider(async (error, attempt, context) => {
  console.log(`Retry decision for ${error.type}, attempt ${attempt}`);

  // ตรรกะแบบกำหนดเอง
  if (error.type === 'NETWORK_ERROR') {
    // ตรวจสอบว่าออนไลน์หรือไม่
    if (!navigator.onLine) {
      return { retry: false, reason: 'Offline' };
    }

  // ลองใหม่สูงสุด 5 ครั้งสำหรับข้อผิดพลาดด้านเครือข่าย
    if (attempt < 5) {
      return {
        retry: true,
        delay: attempt * 2000  // ความหน่วงแบบกำหนดเอง
      };
    }
  }

  if (error.type === 'RATE_LIMIT_EXCEEDED') {
    // ใช้ retry-after header จากเซิร์ฟเวอร์
    return {
      retry: true,
      delay: error.retryAfter || 60000
    };
  }

  // พฤติกรรมเริ่มต้น
  return { retry: false };
});

4. การลองใหม่พร้อม Circuit Breaker

// ป้องกันการลองใหม่มากเกินไป
const circuitBreaker = {
  failures: 0,
  threshold: 5,
  timeout: 60000,
  state: 'CLOSED'  // CLOSED, OPEN, HALF_OPEN
};

AuthErrorHandler.setRetryDecider(async (error, attempt) => {
  // ตรวจสอบสถานะ circuit breaker
  if (circuitBreaker.state === 'OPEN') {
    const now = Date.now();
    const timeSinceOpen = now - circuitBreaker.openedAt;

    if (timeSinceOpen >= circuitBreaker.timeout) {
      // ลอง half-open
      circuitBreaker.state = 'HALF_OPEN';
    } else {
      // ยังเปิดอยู่ ไม่ลองใหม่
      return {
        retry: false,
        reason: 'Circuit breaker open'
      };
    }
  }

  // ตรรกะการลองใหม่
  if (error.type === 'SERVER_ERROR') {
    circuitBreaker.failures++;

    if (circuitBreaker.failures >= circuitBreaker.threshold) {
      // เปิด circuit
      circuitBreaker.state = 'OPEN';
      circuitBreaker.openedAt = Date.now();
      return { retry: false, reason: 'Circuit breaker opened' };
    }

    return { retry: true, delay: 1000 };
  }

  return { retry: false };
});

// รีเซ็ตเมื่อสำเร็จ
document.addEventListener('auth:success', () => {
  if (circuitBreaker.state === 'HALF_OPEN') {
    circuitBreaker.state = 'CLOSED';
    circuitBreaker.failures = 0;
  }
});

การกู้คืนจาก Error

1. การกู้คืนอัตโนมัติ

// AuthErrorHandler พยายามกู้คืนโดยอัตโนมัติ

// สำหรับ TOKEN_EXPIRED:
// 1. พยายาม refresh token
// 2. ถ้า refresh ล้มเหลว เปลี่ยนเส้นทางไปหน้า login

// สำหรับข้อผิดพลาดด้านเครือข่าย (NETWORK_ERROR):
// 1. ลองใหม่ด้วย backoff
// 2. แสดงตัวบ่งชี้ออฟไลน์
// 3. จัดคิว operations สำหรับเมื่อออนไลน์

// สำหรับ SESSION_EXPIRED:
// 1. ล้างข้อมูล session
// 2. เก็บ route ที่ต้องการ
// 3. เปลี่ยนเส้นทางไปหน้า login

2. กลยุทธ์การกู้คืนแบบกำหนดเอง

// ลงทะเบียนกลยุทธ์การกู้คืน
AuthErrorHandler.registerRecovery('TOKEN_EXPIRED', async (error, context) => {
  console.log('Recovering from token expiration');

  try {
    // พยายาม refresh token
    await AuthManager.refreshToken();

    // ลองทำ operation เดิมอีกครั้ง
    if (context.operation) {
      await context.operation.retry();
    }

    return { recovered: true };
  } catch (refreshError) {
    // Refresh ล้มเหลว เปลี่ยนเส้นทางไปหน้า login
    await Router.navigate('/login');
    return { recovered: false };
  }
});

3. การลดทอนฟีเจอร์อย่างสง่างาม

// ลดทอนฟีเจอร์แทนที่จะล้มเหลวทั้งหมด
AuthErrorHandler.registerRecovery('NETWORK_ERROR', async (error) => {
  console.log('Network error - enabling offline mode');

  // เปิดใช้งาน offline mode
  app.offlineMode = true;

  // แสดงตัวบ่งชี้ออฟไลน์
  showOfflineBanner();

  // ใช้ข้อมูลที่แคชไว้
  const cachedData = await loadFromCache();
  if (cachedData) {
    renderWithCachedData(cachedData);
    return { recovered: true };
  }

  return { recovered: false };
});

// กู้คืนเมื่อออนไลน์
window.addEventListener('online', async () => {
  if (app.offlineMode) {
    app.offlineMode = false;
    hideOfflineBanner();

    // ซิงค์การเปลี่ยนแปลงออฟไลน์
    await syncOfflineChanges();

    // โหลดหน้าปัจจุบันใหม่
    await Router.reload();
  }
});

ตัวอย่างการใช้งาน

1. การตั้งค่าการจัดการ Error แบบสมบูรณ์

// เริ่มต้นด้วยการจัดการข้อผิดพลาดแบบครอบคลุม
await AuthManager.init({
  enabled: true,
  endpoints: {
    login: '/api/auth/login',
    logout: '/api/auth/logout',
    refresh: '/api/auth/refresh',
    verify: '/api/auth/verify'
  },

  errorHandling: {
    // การกำหนดค่าการลองใหม่
    maxRetries: 3,
    retryDelay: 1000,
    retryBackoff: 'exponential',

    // การกำหนดค่าการแจ้งเตือน
    showNotifications: true,
    notificationDuration: 5000,

    // การบันทึก
    logErrors: true,
    logLevel: 'error',

  // ประเภทข้อผิดพลาดที่ต้องการให้ลองใหม่
    retryableErrors: [
      'NETWORK_ERROR',
      'SERVER_ERROR',
      'TOKEN_EXPIRED',
      'RATE_LIMIT_EXCEEDED'
    ]
  }
});

// รับฟังข้อผิดพลาดทั้งหมด
document.addEventListener('auth:error', (e) => {
  const { error, context } = e.detail;

  // บันทึกไปยัง analytics
  logErrorToAnalytics({
    type: error.type,
    message: error.message,
    context: context.operation,
    timestamp: new Date().toISOString()
  });
});

// จัดการข้อผิดพลาดด้านเครือข่าย
document.addEventListener('auth:error:network', () => {
  showOfflineIndicator();
});

// จัดการกรณีผู้ใช้ไม่ได้รับอนุญาต
document.addEventListener('auth:error:unauthorized', () => {
  // ล้างสถานะ auth ที่แคชไว้
  localStorage.removeItem('authState');
});

console.log('ตั้งค่าการจัดการข้อผิดพลาดเรียบร้อยแล้ว');

2. การแสดง Validation Error แบบกำหนดเอง

// ลงทะเบียนตัวจัดการสำหรับข้อผิดพลาดจากการตรวจสอบข้อมูล
AuthErrorHandler.registerHandler('VALIDATION_ERROR', async (error) => {
  console.log('ข้อผิดพลาดจากการตรวจสอบข้อมูล:', error.errors);

  // ล้างข้อความข้อผิดพลาดก่อนหน้า
  document.querySelectorAll('.error-message').forEach(el => {
    el.remove();
  });

  document.querySelectorAll('.is-invalid').forEach(el => {
    el.classList.remove('is-invalid');
  });

  // แสดงข้อความข้อผิดพลาดชุดใหม่
  Object.keys(error.errors).forEach(field => {
    const input = document.querySelector(`[name="${field}"]`);
    if (input) {
      // ทำเครื่องหมาย input ว่าไม่ถูกต้อง
      input.classList.add('is-invalid');

      // สร้างข้อความข้อผิดพลาด
      const errorDiv = document.createElement('div');
      errorDiv.className = 'error-message text-danger';
      errorDiv.textContent = error.errors[field];

      // แทรกหลัง input
      input.parentNode.insertBefore(errorDiv, input.nextSibling);
    }
  });

  // โฟกัสฟิลด์แรกที่ไม่ถูกต้อง
  const firstInvalid = document.querySelector('.is-invalid');
  if (firstInvalid) {
    firstInvalid.focus();
  }

  return { handled: true };
});

// ตัวอย่าง login พร้อม validation
async function handleLogin(e) {
  e.preventDefault();

  try {
    await AuthManager.login({
      email: emailInput.value,
      password: passwordInput.value
    });

    // สำเร็จ - เปลี่ยนเส้นทาง
    await Router.navigate('/dashboard');
  } catch (error) {
    // AuthErrorHandler จัดการข้อผิดพลาดจากการตรวจสอบข้อมูลให้อัตโนมัติ
    console.log('Login failed:', error.type);
  }
}

3. Rate Limiting พร้อม User Feedback

// จัดการข้อผิดพลาดจากการจำกัดอัตราการส่งคำขอ
AuthErrorHandler.registerHandler('RATE_LIMIT_EXCEEDED', async (error) => {
  const retryAfter = error.retryAfter || 60000;
  const minutes = Math.ceil(retryAfter / 60000);

  // แสดง countdown modal
  const modal = createModal({
    title: 'Too Many Attempts',
    message: `You've made too many login attempts. Please wait ${minutes} minute(s).`,
    countdown: retryAfter,
    cancelable: false
  });

  // เริ่ม countdown
  let remaining = retryAfter;
  const interval = setInterval(() => {
    remaining -= 1000;

    if (remaining <= 0) {
      clearInterval(interval);
      modal.close();
      return;
    }

    const secs = Math.ceil(remaining / 1000);
    modal.updateMessage(`Please wait ${secs} seconds...`);
  }, 1000);

  return { handled: true };
});

// ป้องกันการส่งฟอร์มระหว่าง rate limit
document.addEventListener('auth:error:ratelimit', (e) => {
  const { retryAfter } = e.detail;

  // ปิดการใช้งานฟอร์ม login
  const loginForm = document.getElementById('loginForm');
  const submitBtn = loginForm.querySelector('[type="submit"]');

  submitBtn.disabled = true;
  submitBtn.textContent = 'Please wait...';

  // เปิดใช้งานอีกครั้งหลังจากหน่วงเวลา
  setTimeout(() => {
    submitBtn.disabled = false;
    submitBtn.textContent = 'Login';
  }, retryAfter);
});

4. Offline Mode พร้อมคิว

// คิวการดำเนินการสำหรับโหมดออฟไลน์
const operationQueue = [];

// จัดการข้อผิดพลาดด้านเครือข่าย
AuthErrorHandler.registerHandler('NETWORK_ERROR', async (error, context) => {
  console.log('Network error - queueing operation');

  // จัดคิวการดำเนินการ
  if (context.operation) {
    operationQueue.push({
      operation: context.operation,
      timestamp: Date.now(),
      error: error
    });
  }

  // แสดงโหมดออฟไลน์
  showOfflineMode();

  return { handled: true };
});

// ประมวลผลคิวเมื่อออนไลน์
window.addEventListener('online', async () => {
  console.log('Back online - processing queue');
  hideOfflineMode();

  // ประมวลผลงานในคิว
  while (operationQueue.length > 0) {
    const item = operationQueue.shift();

    try {
      await item.operation.retry();
      console.log('Operation completed:', item.operation.type);
    } catch (error) {
      console.error('Operation failed:', error);

  // จัดคิวใหม่หากยังเป็นข้อผิดพลาดด้านเครือข่าย
      if (error.type === 'NETWORK_ERROR') {
        operationQueue.push(item);
        break;  // หยุดประมวลผล ยังออฟไลน์อยู่
      }
    }
  }

  // แสดงการแจ้งเตือน
  if (operationQueue.length === 0) {
    showNotification('All operations completed', 'success');
  } else {
    showNotification(`${operationQueue.length} operations pending`, 'warning');
  }
});

// ล้างงานเก่าที่อยู่ในคิว (เก่ากว่า 1 ชั่วโมง)
setInterval(() => {
  const now = Date.now();
  const maxAge = 60 * 60 * 1000;  // 1 ชั่วโมง

  const filtered = operationQueue.filter(item => {
    return (now - item.timestamp) < maxAge;
  });

  const removed = operationQueue.length - filtered.length;
  if (removed > 0) {
    console.log(`Removed ${removed} old operations from queue`);
    operationQueue.length = 0;
    operationQueue.push(...filtered);
  }
}, 5 * 60 * 1000);  // ตรวจสอบทุก 5 นาที

5. Token Refresh พร้อม Fallback

// จัดการ token หมดอายุ
AuthErrorHandler.registerHandler('TOKEN_EXPIRED', async (error, context) => {
  console.log('Token expired - attempting refresh');

  try {
    // พยายาม refresh token
    await AuthManager.refreshToken();

    console.log('Token refreshed successfully');

    // ลองทำ operation เดิมอีกครั้ง
    if (context.operation) {
      return await context.operation.retry();
    }

    return { handled: true };
  } catch (refreshError) {
    console.error('Token refresh failed:', refreshError);

    // เก็บ route ที่ต้องการ
    const currentRoute = Router.getCurrentRoute();
    if (currentRoute && !currentRoute.metadata?.public) {
      AuthGuard.storeIntendedRoute(currentRoute, Router);
    }

    // เปลี่ยนเส้นทางไปหน้า login
    await Router.navigate('/login');

    // แสดงการแจ้งเตือน
    showNotification(
      'Your session has expired. Please login again.',
      'warning'
    );

    return { handled: true };
  }
});

// จัดการการ refresh ล้มเหลว
document.addEventListener('auth:error:token_refresh_failed', async () => {
  // บังคับออกจากระบบ
  await AuthManager.logout();

  // ล้างข้อมูล auth ทั้งหมด
  localStorage.clear();
  sessionStorage.clear();

  // เปลี่ยนเส้นทางไปหน้า login
  await Router.navigate('/login');
});

6. การติดตาม Error และ Analytics

// ติดตามข้อผิดพลาดทั้งหมดในระบบวิเคราะห์ (Analytics)
document.addEventListener('auth:error', async (e) => {
  const { error, context } = e.detail;

  // เตรียมข้อมูลข้อผิดพลาด
  const errorData = {
    type: error.type,
    message: error.message,
    context: {
      operation: context.operation?.type,
      route: Router.getCurrentRoute()?.path,
      user: AuthManager.getUser()?.id
    },
    timestamp: new Date().toISOString(),
    userAgent: navigator.userAgent,
    url: window.location.href
  };

  // ส่งไปยัง analytics
  try {
    await fetch('/api/analytics/error', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorData)
    });
  } catch (err) {
    console.error('Failed to log error:', err);
  }

  // บันทึกใน console ในโหมด development
  if (isDevelopment) {
    console.group('Auth Error');
    console.error('Type:', error.type);
    console.error('Message:', error.message);
    console.error('Context:', context);
    console.groupEnd();
  }
});

// ติดตามการกู้คืนหลังเกิดข้อผิดพลาด
document.addEventListener('auth:error:recovered', (e) => {
  const { error, recoveryMethod } = e.detail;

  trackEvent('auth_error_recovered', {
    error_type: error.type,
    recovery_method: recoveryMethod
  });
});

// ติดตามการลองใหม่
document.addEventListener('auth:error:retry', (e) => {
  const { error, attempt, maxRetries } = e.detail;

  trackEvent('auth_error_retry', {
    error_type: error.type,
    attempt: attempt,
    max_retries: maxRetries
  });
});

เอกสารอ้างอิง API

เมธอด

handleError(error, context)

จัดการ error ด้วย actions ที่กำหนดไว้

พารามิเตอร์:

  • error (Object/Error) - Error object
  • context (Object) - Error context

คืนค่า: Promise<Object> - ผลลัพธ์การจัดการ

ตัวอย่าง:

try {
  await AuthManager.login(credentials);
} catch (error) {
  await AuthErrorHandler.handleError(error, {
    operation: 'login',
    route: Router.getCurrentRoute()
  });
}

registerHandler(errorType, handler)

ลงทะเบียน handler สำหรับ error ประเภทเฉพาะ

พารามิเตอร์:

  • errorType (string) - ประเภท Error
  • handler (Function) - ฟังก์ชัน Handler

คืนค่า: void

ตัวอย่าง:

AuthErrorHandler.registerHandler('VALIDATION_ERROR', async (error) => {
  displayValidationErrors(error.errors);
  return { handled: true };
});

setGlobalHandler(handler)

ตั้งค่าตัวจัดการข้อผิดพลาดระดับทั่วทั้งระบบ ซึ่งถูกเรียกสำหรับข้อผิดพลาดทุกประเภท

พารามิเตอร์:

  • handler (Function) - ฟังก์ชัน Global handler

คืนค่า: void

ตัวอย่าง:

AuthErrorHandler.setGlobalHandler(async (error, context) => {
  await logErrorToService(error);
  return { handled: false };  // ดำเนินการต่อไปยัง specific handlers
});

setRetryDecider(decider)

ตั้งค่าตรรกะการตัดสินใจลองใหม่แบบกำหนดเอง

พารามิเตอร์:

  • decider (Function) - ฟังก์ชันตัดสินใจการลองใหม่

คืนค่า: void

ตัวอย่าง:

AuthErrorHandler.setRetryDecider(async (error, attempt) => {
  if (error.type === 'NETWORK_ERROR' && attempt < 5) {
    return { retry: true, delay: attempt * 1000 };
  }
  return { retry: false };
});

registerRecovery(errorType, strategy)

ลงทะเบียนกลยุทธ์การกู้คืนสำหรับประเภท error

พารามิเตอร์:

  • errorType (string) - ประเภท Error
  • strategy (Function) - ฟังก์ชันกลยุทธ์การกู้คืน

คืนค่า: void

ตัวอย่าง:

AuthErrorHandler.registerRecovery('TOKEN_EXPIRED', async (error, context) => {
  await AuthManager.refreshToken();
  return { recovered: true };
});

clearHandlers()

ล้าง error handlers ที่ลงทะเบียนไว้ทั้งหมด

คืนค่า: void

ตัวอย่าง:

AuthErrorHandler.clearHandlers();

แนวทางปฏิบัติที่ดี

1. จัดการข้อผิดพลาดอย่างสง่างาม

// ✅ ดี - จัดการข้อผิดพลาดอย่างสง่างาม
try {
  await AuthManager.login(credentials);
} catch (error) {
  // ข้อผิดพลาดถูกจัดการโดย AuthErrorHandler โดยอัตโนมัติ
  console.log('Login failed, error handled');
}

// ❌ ไม่ดี - ไม่มีการจัดการข้อผิดพลาด
await AuthManager.login(credentials);  // ข้อผิดพลาดไม่ถูกจัดการ!

2. ให้ข้อมูลย้อนกลับแก่ผู้ใช้

// ✅ ดี - ให้ข้อมูลย้อนกลับที่ชัดเจนแก่ผู้ใช้
document.addEventListener('auth:error:validation', (e) => {
  showValidationErrors(e.detail.errors);
  showNotification('Please fix the errors and try again', 'error');
});

// ❌ ไม่ดี - ล้มเหลวแบบเงียบๆ
// ผู้ใช้ไม่รู้ว่าเกิดอะไรขึ้น

3. บันทึกข้อผิดพลาดอย่างเหมาะสม

// ✅ ดี - บันทึกข้อผิดพลาดแบบมีโครงสร้าง
document.addEventListener('auth:error', (e) => {
  const { error, context } = e.detail;

  logger.error({
    type: error.type,
    message: error.message,
    context: context,
    timestamp: new Date().toISOString()
  });
});

// ❌ ไม่ดี - ไม่มีการบันทึก
// ไม่สามารถ debug ปัญหาใน production ได้

4. ใช้การลองใหม่ที่มีขีดจำกัด

// ✅ ดี - ลองใหม่ด้วยการหน่วงแบบยกกำลัง และมีขีดจำกัด
{
  maxRetries: 3,
  retryBackoff: 'exponential',
  retryDelay: 1000
}

// ❌ ไม่ดี - ลองใหม่ไม่จำกัดจำนวนครั้ง
{
  maxRetries: Infinity  // ไม่มีวันหยุดพยายาม!
}

ข้อผิดพลาดที่พบบ่อย

1. ไม่จัดการข้อผิดพลาดทุกประเภท

// ❌ ไม่ดี - จัดการเฉพาะกรณีไม่ได้รับอนุญาต
document.addEventListener('auth:error:unauthorized', () => {
  redirectToLogin();
});
// แล้วข้อผิดพลาดด้านเครือข่ายล่ะ? แล้วข้อผิดพลาดจากการตรวจสอบข้อมูลล่ะ?

// ✅ ดี - จัดการข้อผิดพลาดทุกประเภท
document.addEventListener('auth:error', (e) => {
  const { error } = e.detail;

  switch (error.type) {
    case 'UNAUTHORIZED':
      redirectToLogin();
      break;
    case 'NETWORK_ERROR':
      showOfflineMode();
      break;
    case 'VALIDATION_ERROR':
      showValidationErrors(error.errors);
      break;
    default:
      showGenericError(error.message);
  }
});

2. กลืนข้อผิดพลาด

// ❌ ไม่ดี - ข้อผิดพลาดหายไป
try {
  await AuthManager.login(credentials);
} catch (error) {
  // ไม่ทำอะไร - ข้อผิดพลาดสูญหาย
}

// ✅ ดี - อย่างน้อยก็บันทึกข้อผิดพลาด
try {
  await AuthManager.login(credentials);
} catch (error) {
  console.error('Login failed:', error);
  // ส่งต่อให้ AuthErrorHandler จัดการต่อ
  throw error;
}

3. ลองใหม่โดยไม่มี Backoff

// ❌ ไม่ดี - ลองใหม่ทันทีตลอดเวลา
{
  retryBackoff: 'fixed',
  retryDelay: 0,
  maxRetries: 999
}
// นี่มันทำลายเซิร์ฟเวอร์ชัดๆ!

// ✅ ดี - ใช้การหน่วงแบบยกกำลัง ที่มีขีดจำกัด
{
  retryBackoff: 'exponential',
  retryDelay: 1000,
  maxRetries: 3
}

เอกสารที่เกี่ยวข้อง