@djangocfg/ext-support 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -300,6 +300,8 @@ interface HttpRequest {
300
300
  params?: Record<string, any>;
301
301
  /** FormData for file uploads (multipart/form-data) */
302
302
  formData?: FormData;
303
+ /** Binary data for octet-stream uploads */
304
+ binaryBody?: Blob | ArrayBuffer;
303
305
  }
304
306
  interface HttpResponse<T = any> {
305
307
  data: T;
@@ -552,11 +554,13 @@ declare class APIClient {
552
554
  private httpClient;
553
555
  private logger;
554
556
  private retryConfig;
557
+ private tokenGetter;
555
558
  ext_support_support: ExtSupportSupport;
556
559
  constructor(baseUrl: string, options?: {
557
560
  httpClient?: HttpClientAdapter;
558
561
  loggerConfig?: Partial<LoggerConfig>;
559
562
  retryConfig?: RetryConfig;
563
+ tokenGetter?: () => string | null;
560
564
  });
561
565
  /**
562
566
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -564,6 +568,15 @@ declare class APIClient {
564
568
  * Returns null if cookie doesn't exist (JWT-only auth).
565
569
  */
566
570
  getCsrfToken(): string | null;
571
+ /**
572
+ * Get the base URL for building streaming/download URLs.
573
+ */
574
+ getBaseUrl(): string;
575
+ /**
576
+ * Get JWT token for URL authentication (used in streaming endpoints).
577
+ * Returns null if no token getter is configured or no token is available.
578
+ */
579
+ getToken(): string | null;
567
580
  /**
568
581
  * Make HTTP request with Django CSRF and session handling.
569
582
  * Automatically retries on network errors and 5xx server errors.
@@ -572,6 +585,7 @@ declare class APIClient {
572
585
  params?: Record<string, any>;
573
586
  body?: any;
574
587
  formData?: FormData;
588
+ binaryBody?: Blob | ArrayBuffer;
575
589
  headers?: Record<string, string>;
576
590
  }): Promise<T>;
577
591
  /**
@@ -1102,30 +1116,38 @@ declare namespace index {
1102
1116
  }
1103
1117
 
1104
1118
  /**
1105
- * Global API Instance - Singleton configuration
1119
+ * Global API Instance - Singleton configuration with auto-configuration support
1106
1120
  *
1107
- * This module provides a global API instance that can be configured once
1108
- * and used throughout your application.
1121
+ * This module provides a global API instance that auto-configures from
1122
+ * environment variables or can be configured manually.
1109
1123
  *
1110
- * Usage:
1124
+ * AUTO-CONFIGURATION (recommended):
1125
+ * Set one of these environment variables and the API will auto-configure:
1126
+ * - NEXT_PUBLIC_API_URL (Next.js)
1127
+ * - VITE_API_URL (Vite)
1128
+ * - REACT_APP_API_URL (Create React App)
1129
+ * - API_URL (generic)
1130
+ *
1131
+ * Then just use fetchers and hooks directly:
1132
+ * ```typescript
1133
+ * import { getUsers } from './_utils/fetchers'
1134
+ * const users = await getUsers({ page: 1 })
1135
+ * ```
1136
+ *
1137
+ * MANUAL CONFIGURATION:
1111
1138
  * ```typescript
1112
- * // Configure once (e.g., in your app entry point)
1113
1139
  * import { configureAPI } from './api-instance'
1114
1140
  *
1115
1141
  * configureAPI({
1116
1142
  * baseUrl: 'https://api.example.com',
1117
1143
  * token: 'your-jwt-token'
1118
1144
  * })
1119
- *
1120
- * // Then use fetchers and hooks anywhere without configuration
1121
- * import { getUsers } from './fetchers'
1122
- * const users = await getUsers({ page: 1 })
1123
1145
  * ```
1124
1146
  *
1125
1147
  * For SSR or multiple instances:
1126
1148
  * ```typescript
1127
1149
  * import { API } from './index'
1128
- * import { getUsers } from './fetchers'
1150
+ * import { getUsers } from './_utils/fetchers'
1129
1151
  *
1130
1152
  * const api = new API('https://api.example.com')
1131
1153
  * const users = await getUsers({ page: 1 }, api)
@@ -1134,11 +1156,12 @@ declare namespace index {
1134
1156
 
1135
1157
  /**
1136
1158
  * Get the global API instance
1137
- * @throws Error if API is not configured
1159
+ * Auto-configures from environment variables on first call if not manually configured.
1160
+ * @throws Error if API is not configured and no env variable is set
1138
1161
  */
1139
1162
  declare function getAPIInstance(): API;
1140
1163
  /**
1141
- * Check if API is configured
1164
+ * Check if API is configured (or can be auto-configured)
1142
1165
  */
1143
1166
  declare function isAPIConfigured(): boolean;
1144
1167
  /**
package/dist/index.d.ts CHANGED
@@ -300,6 +300,8 @@ interface HttpRequest {
300
300
  params?: Record<string, any>;
301
301
  /** FormData for file uploads (multipart/form-data) */
302
302
  formData?: FormData;
303
+ /** Binary data for octet-stream uploads */
304
+ binaryBody?: Blob | ArrayBuffer;
303
305
  }
304
306
  interface HttpResponse<T = any> {
305
307
  data: T;
@@ -552,11 +554,13 @@ declare class APIClient {
552
554
  private httpClient;
553
555
  private logger;
554
556
  private retryConfig;
557
+ private tokenGetter;
555
558
  ext_support_support: ExtSupportSupport;
556
559
  constructor(baseUrl: string, options?: {
557
560
  httpClient?: HttpClientAdapter;
558
561
  loggerConfig?: Partial<LoggerConfig>;
559
562
  retryConfig?: RetryConfig;
563
+ tokenGetter?: () => string | null;
560
564
  });
561
565
  /**
562
566
  * Get CSRF token from cookies (for SessionAuthentication).
@@ -564,6 +568,15 @@ declare class APIClient {
564
568
  * Returns null if cookie doesn't exist (JWT-only auth).
565
569
  */
566
570
  getCsrfToken(): string | null;
571
+ /**
572
+ * Get the base URL for building streaming/download URLs.
573
+ */
574
+ getBaseUrl(): string;
575
+ /**
576
+ * Get JWT token for URL authentication (used in streaming endpoints).
577
+ * Returns null if no token getter is configured or no token is available.
578
+ */
579
+ getToken(): string | null;
567
580
  /**
568
581
  * Make HTTP request with Django CSRF and session handling.
569
582
  * Automatically retries on network errors and 5xx server errors.
@@ -572,6 +585,7 @@ declare class APIClient {
572
585
  params?: Record<string, any>;
573
586
  body?: any;
574
587
  formData?: FormData;
588
+ binaryBody?: Blob | ArrayBuffer;
575
589
  headers?: Record<string, string>;
576
590
  }): Promise<T>;
577
591
  /**
@@ -1102,30 +1116,38 @@ declare namespace index {
1102
1116
  }
1103
1117
 
1104
1118
  /**
1105
- * Global API Instance - Singleton configuration
1119
+ * Global API Instance - Singleton configuration with auto-configuration support
1106
1120
  *
1107
- * This module provides a global API instance that can be configured once
1108
- * and used throughout your application.
1121
+ * This module provides a global API instance that auto-configures from
1122
+ * environment variables or can be configured manually.
1109
1123
  *
1110
- * Usage:
1124
+ * AUTO-CONFIGURATION (recommended):
1125
+ * Set one of these environment variables and the API will auto-configure:
1126
+ * - NEXT_PUBLIC_API_URL (Next.js)
1127
+ * - VITE_API_URL (Vite)
1128
+ * - REACT_APP_API_URL (Create React App)
1129
+ * - API_URL (generic)
1130
+ *
1131
+ * Then just use fetchers and hooks directly:
1132
+ * ```typescript
1133
+ * import { getUsers } from './_utils/fetchers'
1134
+ * const users = await getUsers({ page: 1 })
1135
+ * ```
1136
+ *
1137
+ * MANUAL CONFIGURATION:
1111
1138
  * ```typescript
1112
- * // Configure once (e.g., in your app entry point)
1113
1139
  * import { configureAPI } from './api-instance'
1114
1140
  *
1115
1141
  * configureAPI({
1116
1142
  * baseUrl: 'https://api.example.com',
1117
1143
  * token: 'your-jwt-token'
1118
1144
  * })
1119
- *
1120
- * // Then use fetchers and hooks anywhere without configuration
1121
- * import { getUsers } from './fetchers'
1122
- * const users = await getUsers({ page: 1 })
1123
1145
  * ```
1124
1146
  *
1125
1147
  * For SSR or multiple instances:
1126
1148
  * ```typescript
1127
1149
  * import { API } from './index'
1128
- * import { getUsers } from './fetchers'
1150
+ * import { getUsers } from './_utils/fetchers'
1129
1151
  *
1130
1152
  * const api = new API('https://api.example.com')
1131
1153
  * const users = await getUsers({ page: 1 }, api)
@@ -1134,11 +1156,12 @@ declare namespace index {
1134
1156
 
1135
1157
  /**
1136
1158
  * Get the global API instance
1137
- * @throws Error if API is not configured
1159
+ * Auto-configures from environment variables on first call if not manually configured.
1160
+ * @throws Error if API is not configured and no env variable is set
1138
1161
  */
1139
1162
  declare function getAPIInstance(): API;
1140
1163
  /**
1141
- * Check if API is configured
1164
+ * Check if API is configured (or can be auto-configured)
1142
1165
  */
1143
1166
  declare function isAPIConfigured(): boolean;
1144
1167
  /**
package/dist/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { createConsola, consola } from 'consola';
2
2
  import pRetry, { AbortError } from 'p-retry';
3
3
  import { z } from 'zod';
4
- import { createExtensionAPI } from '@djangocfg/ext-base/api';
4
+ import { initializeExtensionAPI, createExtensionAPI } from '@djangocfg/ext-base/api';
5
5
  import { ArrowLeft, LifeBuoy, Plus, MessageSquare, Loader2, Send, Headphones, User, Clock } from 'lucide-react';
6
6
  import React7, { createContext, useState, useCallback, useEffect, useContext, useRef } from 'react';
7
- import { Button, ResizablePanelGroup, ResizablePanel, ResizableHandle, Skeleton, ScrollArea, useToast, Textarea, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Avatar, AvatarImage, AvatarFallback, Card, CardContent, Badge } from '@djangocfg/ui-nextjs';
7
+ import { Button, ResizablePanelGroup, ResizablePanel, ResizableHandle, Skeleton, ScrollArea, Textarea, useToast as useToast$1, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Avatar, AvatarImage, AvatarFallback, Card, CardContent, Badge } from '@djangocfg/ui-core';
8
8
  import useSWR, { useSWRConfig } from 'swr';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import moment2 from 'moment';
11
11
  import { cn } from '@djangocfg/ui-core/lib';
12
12
  import { useAuth } from '@djangocfg/api/auth';
13
13
  import useSWRInfinite from 'swr/infinite';
14
+ import { useToast } from '@djangocfg/ui-core/hooks';
14
15
  import { useForm } from 'react-hook-form';
15
16
  import { zodResolver } from '@hookform/resolvers/zod';
16
17
  import { createExtensionConfig } from '@djangocfg/ext-base';
@@ -152,7 +153,7 @@ var models_exports = {};
152
153
  // src/api/generated/ext_support/http.ts
153
154
  var FetchAdapter = class {
154
155
  async request(request) {
155
- const { method, url, headers, body, params, formData } = request;
156
+ const { method, url, headers, body, params, formData, binaryBody } = request;
156
157
  let finalUrl = url;
157
158
  if (params) {
158
159
  const searchParams = new URLSearchParams();
@@ -170,6 +171,9 @@ var FetchAdapter = class {
170
171
  let requestBody;
171
172
  if (formData) {
172
173
  requestBody = formData;
174
+ } else if (binaryBody) {
175
+ finalHeaders["Content-Type"] = "application/octet-stream";
176
+ requestBody = binaryBody;
173
177
  } else if (body) {
174
178
  finalHeaders["Content-Type"] = "application/json";
175
179
  requestBody = JSON.stringify(body);
@@ -494,11 +498,13 @@ var APIClient = class {
494
498
  httpClient;
495
499
  logger = null;
496
500
  retryConfig = null;
501
+ tokenGetter = null;
497
502
  // Sub-clients
498
503
  ext_support_support;
499
504
  constructor(baseUrl, options) {
500
505
  this.baseUrl = baseUrl.replace(/\/$/, "");
501
506
  this.httpClient = options?.httpClient || new FetchAdapter();
507
+ this.tokenGetter = options?.tokenGetter || null;
502
508
  if (options?.loggerConfig !== void 0) {
503
509
  this.logger = new APILogger(options.loggerConfig);
504
510
  }
@@ -521,6 +527,19 @@ var APIClient = class {
521
527
  }
522
528
  return null;
523
529
  }
530
+ /**
531
+ * Get the base URL for building streaming/download URLs.
532
+ */
533
+ getBaseUrl() {
534
+ return this.baseUrl;
535
+ }
536
+ /**
537
+ * Get JWT token for URL authentication (used in streaming endpoints).
538
+ * Returns null if no token getter is configured or no token is available.
539
+ */
540
+ getToken() {
541
+ return this.tokenGetter ? this.tokenGetter() : null;
542
+ }
524
543
  /**
525
544
  * Make HTTP request with Django CSRF and session handling.
526
545
  * Automatically retries on network errors and 5xx server errors.
@@ -551,7 +570,7 @@ var APIClient = class {
551
570
  const headers = {
552
571
  ...options?.headers || {}
553
572
  };
554
- if (!options?.formData && !headers["Content-Type"]) {
573
+ if (!options?.formData && !options?.binaryBody && !headers["Content-Type"]) {
555
574
  headers["Content-Type"] = "application/json";
556
575
  }
557
576
  if (this.logger) {
@@ -570,7 +589,8 @@ var APIClient = class {
570
589
  headers,
571
590
  params: options?.params,
572
591
  body: options?.body,
573
- formData: options?.formData
592
+ formData: options?.formData,
593
+ binaryBody: options?.binaryBody
574
594
  });
575
595
  const duration = Date.now() - startTime;
576
596
  if (response.status >= 400) {
@@ -984,15 +1004,28 @@ __export(fetchers_exports, {
984
1004
 
985
1005
  // src/api/generated/ext_support/api-instance.ts
986
1006
  var globalAPI = null;
1007
+ var autoConfigAttempted = false;
1008
+ function tryAutoConfigureFromEnv() {
1009
+ if (autoConfigAttempted) return;
1010
+ autoConfigAttempted = true;
1011
+ if (globalAPI) return;
1012
+ if (typeof process === "undefined" || !process.env) return;
1013
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || process.env.VITE_API_URL || process.env.REACT_APP_API_URL || process.env.API_URL;
1014
+ if (baseUrl) {
1015
+ globalAPI = new API(baseUrl);
1016
+ }
1017
+ }
987
1018
  function getAPIInstance() {
1019
+ tryAutoConfigureFromEnv();
988
1020
  if (!globalAPI) {
989
1021
  throw new Error(
990
- 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })'
1022
+ 'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\nExample:\n import { configureAPI } from "./api-instance"\n configureAPI({ baseUrl: "https://api.example.com" })\n\nOr set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
991
1023
  );
992
1024
  }
993
1025
  return globalAPI;
994
1026
  }
995
1027
  function isAPIConfigured() {
1028
+ tryAutoConfigureFromEnv();
996
1029
  return globalAPI !== null;
997
1030
  }
998
1031
  function configureAPI(config) {
@@ -1475,7 +1508,8 @@ var API = class {
1475
1508
  this._loadTokensFromStorage();
1476
1509
  this._client = new APIClient(this.baseUrl, {
1477
1510
  retryConfig: this.options?.retryConfig,
1478
- loggerConfig: this.options?.loggerConfig
1511
+ loggerConfig: this.options?.loggerConfig,
1512
+ tokenGetter: () => this.getToken()
1479
1513
  });
1480
1514
  this._injectAuthHeader();
1481
1515
  this.ext_support_support = this._client.ext_support_support;
@@ -1487,7 +1521,8 @@ var API = class {
1487
1521
  _reinitClients() {
1488
1522
  this._client = new APIClient(this.baseUrl, {
1489
1523
  retryConfig: this.options?.retryConfig,
1490
- loggerConfig: this.options?.loggerConfig
1524
+ loggerConfig: this.options?.loggerConfig,
1525
+ tokenGetter: () => this.getToken()
1491
1526
  });
1492
1527
  this._injectAuthHeader();
1493
1528
  this.ext_support_support = this._client.ext_support_support;
@@ -1578,6 +1613,7 @@ var API = class {
1578
1613
  return "./schema.json";
1579
1614
  }
1580
1615
  };
1616
+ initializeExtensionAPI(configureAPI);
1581
1617
  var apiSupport = createExtensionAPI(API);
1582
1618
  function useSupportTicketsList(params, client) {
1583
1619
  return useSWR(
@@ -2444,7 +2480,7 @@ var createTicketSchema = z.object({
2444
2480
  });
2445
2481
  var CreateTicketDialog = () => {
2446
2482
  const { uiState, createTicket, closeCreateDialog } = useSupportLayoutContext();
2447
- const { toast } = useToast();
2483
+ const { toast } = useToast$1();
2448
2484
  const [isSubmitting, setIsSubmitting] = React7.useState(false);
2449
2485
  const form = useForm({
2450
2486
  resolver: zodResolver(createTicketSchema),
@@ -2458,17 +2494,10 @@ var CreateTicketDialog = () => {
2458
2494
  try {
2459
2495
  await createTicket(data);
2460
2496
  form.reset();
2461
- toast({
2462
- title: "Success",
2463
- description: "Support ticket created successfully"
2464
- });
2497
+ toast.success("Support ticket created successfully");
2465
2498
  } catch (error) {
2466
2499
  supportLogger.error("Failed to create ticket:", error);
2467
- toast({
2468
- title: "Error",
2469
- description: "Failed to create ticket. Please try again.",
2470
- variant: "destructive"
2471
- });
2500
+ toast.error("Failed to create ticket. Please try again.");
2472
2501
  } finally {
2473
2502
  setIsSubmitting(false);
2474
2503
  }
@@ -2617,7 +2646,7 @@ var SupportLayout = () => {
2617
2646
  // package.json
2618
2647
  var package_default = {
2619
2648
  name: "@djangocfg/ext-support",
2620
- version: "1.0.8",
2649
+ version: "1.0.10",
2621
2650
  description: "Support ticket system extension for DjangoCFG",
2622
2651
  keywords: [
2623
2652
  "django",
@@ -2679,7 +2708,6 @@ var package_default = {
2679
2708
  "@djangocfg/api": "workspace:*",
2680
2709
  "@djangocfg/ext-base": "workspace:*",
2681
2710
  "@djangocfg/ui-core": "workspace:*",
2682
- "@djangocfg/ui-nextjs": "workspace:*",
2683
2711
  consola: "^3.4.2",
2684
2712
  "lucide-react": "^0.545.0",
2685
2713
  moment: "^2.30.1",
@@ -2693,6 +2721,7 @@ var package_default = {
2693
2721
  devDependencies: {
2694
2722
  "@djangocfg/api": "workspace:*",
2695
2723
  "@djangocfg/ext-base": "workspace:*",
2724
+ "@djangocfg/ui-core": "workspace:*",
2696
2725
  "@djangocfg/typescript-config": "workspace:*",
2697
2726
  "@types/node": "^24.7.2",
2698
2727
  "@types/react": "^19.0.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ext-support",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Support ticket system extension for DjangoCFG",
5
5
  "keywords": [
6
6
  "django",
@@ -59,10 +59,9 @@
59
59
  "check": "tsc --noEmit"
60
60
  },
61
61
  "peerDependencies": {
62
- "@djangocfg/api": "^2.1.91",
62
+ "@djangocfg/api": "^2.1.107",
63
63
  "@djangocfg/ext-base": "^1.0.8",
64
- "@djangocfg/ui-core": "^2.1.91",
65
- "@djangocfg/ui-nextjs": "^2.1.91",
64
+ "@djangocfg/ui-core": "^2.1.107",
66
65
  "consola": "^3.4.2",
67
66
  "lucide-react": "^0.545.0",
68
67
  "moment": "^2.30.1",
@@ -74,9 +73,10 @@
74
73
  "zod": "^4.1.13"
75
74
  },
76
75
  "devDependencies": {
77
- "@djangocfg/api": "^2.1.91",
76
+ "@djangocfg/api": "^2.1.107",
78
77
  "@djangocfg/ext-base": "^1.0.8",
79
- "@djangocfg/typescript-config": "^2.1.91",
78
+ "@djangocfg/ui-core": "^2.1.107",
79
+ "@djangocfg/typescript-config": "^2.1.107",
80
80
  "@types/node": "^24.7.2",
81
81
  "@types/react": "^19.0.0",
82
82
  "consola": "^3.4.2",
@@ -69,12 +69,5 @@ openapi_client = OpenAPIClientConfig(
69
69
  )
70
70
  ```
71
71
 
72
- **Copy to Next.js** (if `nextjs_admin` configured):
73
- ```python
74
- nextjs_admin = NextJsAdminConfig(
75
- project_path="../frontend/apps/...",
76
- api_output_path="app/_lib/api/generated",
77
- )
78
- ```
79
-
80
72
  @see https://djangocfg.com/docs/features/api-generation
73
+
@@ -1,29 +1,37 @@
1
1
  // Auto-generated by DjangoCFG - see CLAUDE.md
2
2
  /**
3
- * Global API Instance - Singleton configuration
3
+ * Global API Instance - Singleton configuration with auto-configuration support
4
4
  *
5
- * This module provides a global API instance that can be configured once
6
- * and used throughout your application.
5
+ * This module provides a global API instance that auto-configures from
6
+ * environment variables or can be configured manually.
7
7
  *
8
- * Usage:
8
+ * AUTO-CONFIGURATION (recommended):
9
+ * Set one of these environment variables and the API will auto-configure:
10
+ * - NEXT_PUBLIC_API_URL (Next.js)
11
+ * - VITE_API_URL (Vite)
12
+ * - REACT_APP_API_URL (Create React App)
13
+ * - API_URL (generic)
14
+ *
15
+ * Then just use fetchers and hooks directly:
16
+ * ```typescript
17
+ * import { getUsers } from './_utils/fetchers'
18
+ * const users = await getUsers({ page: 1 })
19
+ * ```
20
+ *
21
+ * MANUAL CONFIGURATION:
9
22
  * ```typescript
10
- * // Configure once (e.g., in your app entry point)
11
23
  * import { configureAPI } from './api-instance'
12
24
  *
13
25
  * configureAPI({
14
26
  * baseUrl: 'https://api.example.com',
15
27
  * token: 'your-jwt-token'
16
28
  * })
17
- *
18
- * // Then use fetchers and hooks anywhere without configuration
19
- * import { getUsers } from './fetchers'
20
- * const users = await getUsers({ page: 1 })
21
29
  * ```
22
30
  *
23
31
  * For SSR or multiple instances:
24
32
  * ```typescript
25
33
  * import { API } from './index'
26
- * import { getUsers } from './fetchers'
34
+ * import { getUsers } from './_utils/fetchers'
27
35
  *
28
36
  * const api = new API('https://api.example.com')
29
37
  * const users = await getUsers({ page: 1 }, api)
@@ -33,27 +41,67 @@
33
41
  import { API, type APIOptions } from './index'
34
42
 
35
43
  let globalAPI: API | null = null
44
+ let autoConfigAttempted = false
45
+
46
+ /**
47
+ * Auto-configure from environment variable if available (Next.js pattern)
48
+ * This allows hooks and fetchers to work without explicit configureAPI() call
49
+ *
50
+ * Supported environment variables:
51
+ * - NEXT_PUBLIC_API_URL (Next.js)
52
+ * - VITE_API_URL (Vite)
53
+ * - REACT_APP_API_URL (Create React App)
54
+ * - API_URL (generic)
55
+ */
56
+ function tryAutoConfigureFromEnv(): void {
57
+ // Only attempt once
58
+ if (autoConfigAttempted) return
59
+ autoConfigAttempted = true
60
+
61
+ // Skip if already configured
62
+ if (globalAPI) return
63
+
64
+ // Skip if process is not available (pure browser without bundler)
65
+ if (typeof process === 'undefined' || !process.env) return
66
+
67
+ // Try different environment variable patterns
68
+ const baseUrl =
69
+ process.env.NEXT_PUBLIC_API_URL ||
70
+ process.env.VITE_API_URL ||
71
+ process.env.REACT_APP_API_URL ||
72
+ process.env.API_URL
73
+
74
+ if (baseUrl) {
75
+ globalAPI = new API(baseUrl)
76
+ }
77
+ }
36
78
 
37
79
  /**
38
80
  * Get the global API instance
39
- * @throws Error if API is not configured
81
+ * Auto-configures from environment variables on first call if not manually configured.
82
+ * @throws Error if API is not configured and no env variable is set
40
83
  */
41
84
  export function getAPIInstance(): API {
85
+ // Try auto-configuration on first access (lazy initialization)
86
+ tryAutoConfigureFromEnv()
87
+
42
88
  if (!globalAPI) {
43
89
  throw new Error(
44
90
  'API not configured. Call configureAPI() with your base URL before using fetchers or hooks.\n\n' +
45
91
  'Example:\n' +
46
92
  ' import { configureAPI } from "./api-instance"\n' +
47
- ' configureAPI({ baseUrl: "https://api.example.com" })'
93
+ ' configureAPI({ baseUrl: "https://api.example.com" })\n\n' +
94
+ 'Or set environment variable: NEXT_PUBLIC_API_URL, VITE_API_URL, or REACT_APP_API_URL'
48
95
  )
49
96
  }
50
97
  return globalAPI
51
98
  }
52
99
 
53
100
  /**
54
- * Check if API is configured
101
+ * Check if API is configured (or can be auto-configured)
55
102
  */
56
103
  export function isAPIConfigured(): boolean {
104
+ tryAutoConfigureFromEnv()
57
105
  return globalAPI !== null
58
106
  }
59
107
 
@@ -25,6 +25,7 @@ export class APIClient {
25
25
  private httpClient: HttpClientAdapter;
26
26
  private logger: APILogger | null = null;
27
27
  private retryConfig: RetryConfig | null = null;
28
+ private tokenGetter: (() => string | null) | null = null;
28
29
 
29
30
  // Sub-clients
30
31
  public ext_support_support: ExtSupportSupport;
@@ -35,10 +36,12 @@ export class APIClient {
35
36
  httpClient?: HttpClientAdapter;
36
37
  loggerConfig?: Partial<LoggerConfig>;
37
38
  retryConfig?: RetryConfig;
39
+ tokenGetter?: () => string | null;
38
40
  }
39
41
  ) {
40
42
  this.baseUrl = baseUrl.replace(/\/$/, '');
41
43
  this.httpClient = options?.httpClient || new FetchAdapter();
44
+ this.tokenGetter = options?.tokenGetter || null;
42
45
 
43
46
  // Initialize logger if config provided
44
47
  if (options?.loggerConfig !== undefined) {
@@ -69,6 +72,21 @@ export class APIClient {
69
72
  return null;
70
73
  }
71
74
 
75
+ /**
76
+ * Get the base URL for building streaming/download URLs.
77
+ */
78
+ getBaseUrl(): string {
79
+ return this.baseUrl;
80
+ }
81
+
82
+ /**
83
+ * Get JWT token for URL authentication (used in streaming endpoints).
84
+ * Returns null if no token getter is configured or no token is available.
85
+ */
86
+ getToken(): string | null {
87
+ return this.tokenGetter ? this.tokenGetter() : null;
88
+ }
89
+
72
90
  /**
73
91
  * Make HTTP request with Django CSRF and session handling.
74
92
  * Automatically retries on network errors and 5xx server errors.
@@ -80,6 +98,7 @@ export class APIClient {
80
98
  params?: Record<string, any>;
81
99
  body?: any;
82
100
  formData?: FormData;
101
+ binaryBody?: Blob | ArrayBuffer;
83
102
  headers?: Record<string, string>;
84
103
  }
85
104
  ): Promise<T> {
@@ -116,6 +135,7 @@ export class APIClient {
116
135
  params?: Record<string, any>;
117
136
  body?: any;
118
137
  formData?: FormData;
138
+ binaryBody?: Blob | ArrayBuffer;
119
139
  headers?: Record<string, string>;
120
140
  }
121
141
  ): Promise<T> {
@@ -129,8 +149,8 @@ export class APIClient {
129
149
  ...(options?.headers || {})
130
150
  };
131
151
 
132
- // Don't set Content-Type for FormData (browser will set it with boundary)
133
- if (!options?.formData && !headers['Content-Type']) {
152
+ // Don't set Content-Type for FormData/binaryBody (browser will set it with boundary)
153
+ if (!options?.formData && !options?.binaryBody && !headers['Content-Type']) {
134
154
  headers['Content-Type'] = 'application/json';
135
155
  }
136
156
 
@@ -157,6 +177,7 @@ export class APIClient {
157
177
  params: options?.params,
158
178
  body: options?.body,
159
179
  formData: options?.formData,
180
+ binaryBody: options?.binaryBody,
160
181
  });
161
182
 
162
183
  const duration = Date.now() - startTime;