api.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // API_BASE now uses relative path for proxy, or falls back to direct API URL
  2. export const API_BASE = process.env.NEXT_PUBLIC_WATCH_FINISHED_API || "/api";
  3. function buildUrl(path: string, params?: any) {
  4. // If API_BASE starts with http, use it as absolute URL
  5. // Otherwise, use it as relative path (for proxy)
  6. const base = API_BASE.startsWith("http") ? API_BASE : "";
  7. const fullPath = API_BASE.startsWith("http") ? path : `${API_BASE}${path}`;
  8. const url = base
  9. ? new URL(fullPath, base)
  10. : new URL(fullPath, window.location.origin);
  11. if (params && typeof params === "object") {
  12. Object.entries(params).forEach(([k, v]) => {
  13. if (v !== undefined && v !== null) url.searchParams.append(k, String(v));
  14. });
  15. }
  16. return url.toString();
  17. }
  18. export async function get<T = any>(path: string, params?: any): Promise<T> {
  19. const controller = new AbortController();
  20. const timeoutId = setTimeout(() => controller.abort(), 90000); // 90 second timeout
  21. try {
  22. const res = await fetch(buildUrl(path, params), {
  23. method: "GET",
  24. credentials: "same-origin",
  25. headers: { Accept: "application/json" },
  26. signal: controller.signal
  27. });
  28. clearTimeout(timeoutId);
  29. if (!res.ok) throw new Error(await res.text());
  30. return res.json();
  31. } catch (error) {
  32. clearTimeout(timeoutId);
  33. if (error instanceof Error && error.name === "AbortError") {
  34. throw new Error("Request timeout");
  35. }
  36. throw error;
  37. }
  38. }
  39. export async function post<T = any>(path: string, data?: any): Promise<T> {
  40. const controller = new AbortController();
  41. const timeoutId = setTimeout(() => controller.abort(), 90000); // 90 second timeout
  42. try {
  43. const res = await fetch(buildUrl(path), {
  44. method: "POST",
  45. credentials: "same-origin",
  46. headers: {
  47. "Content-Type": "application/json",
  48. Accept: "application/json"
  49. },
  50. body: data ? JSON.stringify(data) : undefined,
  51. signal: controller.signal
  52. });
  53. clearTimeout(timeoutId);
  54. if (!res.ok) throw new Error(await res.text());
  55. return res.json();
  56. } catch (error) {
  57. clearTimeout(timeoutId);
  58. if (error instanceof Error && error.name === "AbortError") {
  59. throw new Error("Request timeout");
  60. }
  61. throw error;
  62. }
  63. }
  64. export async function put<T = any>(path: string, data?: any): Promise<T> {
  65. const controller = new AbortController();
  66. const timeoutId = setTimeout(() => controller.abort(), 90000); // 90 second timeout
  67. try {
  68. const res = await fetch(buildUrl(path), {
  69. method: "PUT",
  70. credentials: "same-origin",
  71. headers: {
  72. "Content-Type": "application/json",
  73. Accept: "application/json"
  74. },
  75. body: data ? JSON.stringify(data) : undefined,
  76. signal: controller.signal
  77. });
  78. clearTimeout(timeoutId);
  79. if (!res.ok) throw new Error(await res.text());
  80. return res.json();
  81. } catch (error) {
  82. clearTimeout(timeoutId);
  83. if (error instanceof Error && error.name === "AbortError") {
  84. throw new Error("Request timeout");
  85. }
  86. throw error;
  87. }
  88. }
  89. export async function del<T = any>(path: string, params?: any): Promise<T> {
  90. const controller = new AbortController();
  91. const timeoutId = setTimeout(() => controller.abort(), 90000); // 90 second timeout
  92. try {
  93. const res = await fetch(buildUrl(path, params), {
  94. method: "DELETE",
  95. credentials: "same-origin",
  96. headers: { Accept: "application/json" },
  97. signal: controller.signal
  98. });
  99. clearTimeout(timeoutId);
  100. if (!res.ok) throw new Error(await res.text());
  101. // Handle empty response body (common for DELETE operations)
  102. const contentLength = res.headers.get("content-length");
  103. const contentType = res.headers.get("content-type");
  104. if (
  105. contentLength === "0" ||
  106. res.status === 204 ||
  107. !contentType?.includes("application/json")
  108. ) {
  109. return {} as T;
  110. }
  111. const text = await res.text();
  112. if (!text.trim()) {
  113. return {} as T;
  114. }
  115. return JSON.parse(text);
  116. } catch (error) {
  117. clearTimeout(timeoutId);
  118. if (error instanceof Error && error.name === "AbortError") {
  119. throw new Error("Request timeout");
  120. }
  121. throw error;
  122. }
  123. }