vue-api-kit 1.5.2 → 1.7.0

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/README.md CHANGED
@@ -251,7 +251,8 @@ const api = createApiClient({
251
251
  headers: {
252
252
  'Authorization': 'Bearer token'
253
253
  },
254
- withCredentials: true,
254
+ withCredentials: true, // Enable cookies
255
+ withXSRFToken: true, // Enable automatic XSRF token handling
255
256
 
256
257
  // CSRF Token Protection
257
258
  csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Auto-refresh CSRF token on 403/419 errors
@@ -458,20 +459,26 @@ The client includes built-in CSRF token protection, perfect for Laravel Sanctum
458
459
 
459
460
  ### How it works
460
461
 
461
- When you set `csrfRefreshEndpoint`, the client will:
462
- 1. Automatically detect CSRF errors (403 or 419 status codes)
463
- 2. Call the CSRF refresh endpoint to get a new token
464
- 3. Retry the original request with the fresh token
465
- 4. Prevent infinite loops and race conditions
462
+ **Automatic XSRF Token Handling:**
463
+ 1. Set `withCredentials: true` to enable cookie-based authentication
464
+ 2. Set `withXSRFToken: true` to enable automatic XSRF token handling
465
+ 3. Axios automatically reads `XSRF-TOKEN` cookie and sends it as `X-XSRF-TOKEN` header
466
+ 4. This satisfies Laravel Sanctum's CSRF protection requirements
467
+
468
+ **Automatic CSRF Refresh:**
469
+ 1. Detects CSRF errors (403 or 419 status codes)
470
+ 2. Calls the CSRF refresh endpoint to get a new token
471
+ 3. Retries the original request automatically with the fresh token
472
+ 4. Prevents infinite loops and race conditions
466
473
 
467
474
  ### Configuration
468
475
 
469
476
  ```typescript
470
477
  const api = createApiClient({
471
478
  baseURL: 'https://api.example.com',
472
- withCredentials: true, // Required for CSRF cookies
479
+ withCredentials: true, // Enable cookies for authentication
480
+ withXSRFToken: true, // Enable automatic XSRF token handling
473
481
  csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Laravel Sanctum endpoint
474
-
475
482
  queries: { /* ... */ },
476
483
  mutations: { /* ... */ }
477
484
  });
@@ -486,10 +493,9 @@ import { z } from 'zod';
486
493
 
487
494
  export const api = createApiClient({
488
495
  baseURL: 'https://api.example.com',
489
- withCredentials: true, // Send cookies with requests
490
- csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Laravel's CSRF endpoint
491
-
492
- mutations: {
496
+ withCredentials: true, // Enables cookies
497
+ withXSRFToken: true, // Enables automatic XSRF-TOKEN header
498
+ csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Laravel's CSRF endpoint mutations: {
493
499
  login: {
494
500
  method: 'POST',
495
501
  path: '/login',
@@ -519,12 +525,27 @@ export const api = createApiClient({
519
525
 
520
526
  ### Benefits
521
527
 
528
+ - ✅ **Separate Options**: `withCredentials` and `withXSRFToken` can be configured independently
529
+ - ✅ **Built-in XSRF Support**: Axios `withXSRFToken` handles token automatically
522
530
  - ✅ **Automatic Recovery**: No manual token refresh needed
523
531
  - ✅ **Seamless UX**: Users don't experience authentication errors
524
532
  - ✅ **Race Condition Safe**: Multiple simultaneous requests share the same refresh
525
533
  - ✅ **Infinite Loop Prevention**: Won't retry the CSRF endpoint itself
526
534
  - ✅ **Laravel Sanctum Compatible**: Works perfectly with Laravel's SPA authentication
527
535
 
536
+ ### Important Notes
537
+
538
+ 1. **Two separate options**:
539
+ - `withCredentials: true` - Enables sending cookies with requests
540
+ - `withXSRFToken: true` - Enables automatic XSRF token header handling
541
+ 2. **Cookie Domain**: Ensure your API sets cookies with the correct domain (e.g., `.localhost` for local development)
542
+ 3. **CORS Configuration**: Your Laravel backend must allow credentials:
543
+ ```php
544
+ // config/cors.php
545
+ 'supports_credentials' => true,
546
+ 'allowed_origins' => ['http://localhost:5173'],
547
+ ```
548
+
528
549
 
529
550
 
530
551
  ## 📝 License
@@ -83,6 +83,7 @@ export interface ApiClientOptions<Q extends Record<string, ApiQuery> = Record<st
83
83
  baseURL: string;
84
84
  headers?: Record<string, string>;
85
85
  withCredentials?: boolean;
86
+ withXSRFToken?: boolean;
86
87
  csrfRefreshEndpoint?: string;
87
88
  queries?: Q;
88
89
  mutations?: M;
package/dist/index.js CHANGED
@@ -1,44 +1,45 @@
1
- import { ZodError as L } from "zod";
1
+ import { ZodError as S } from "zod";
2
2
  import * as U from "zod";
3
- import x, { AxiosError as S } from "axios";
4
- import { AxiosError as K } from "axios";
5
- import { nextTick as F, ref as m, onMounted as N, watch as T, onBeforeUnmount as Z } from "vue";
6
- import { debounce as $ } from "lodash-es";
7
- function z(s) {
8
- const h = x.create({
9
- baseURL: s.baseURL,
3
+ import L, { AxiosError as F } from "axios";
4
+ import { AxiosError as H } from "axios";
5
+ import { nextTick as T, ref as m, onMounted as k, watch as x, onBeforeUnmount as N } from "vue";
6
+ import { debounce as Z } from "lodash-es";
7
+ function z(r) {
8
+ const h = L.create({
9
+ baseURL: r.baseURL,
10
10
  headers: {
11
11
  "Content-Type": "application/json",
12
12
  Accept: "application/json",
13
- ...s.headers
13
+ ...r.headers
14
14
  },
15
- withCredentials: s.withCredentials ?? !1
15
+ withCredentials: r.withCredentials ?? !1,
16
+ withXSRFToken: r.withXSRFToken ?? !1
16
17
  });
17
18
  let b = !1, q = null;
18
- s.onBeforeRequest && h.interceptors.request.use(
19
+ r.onBeforeRequest && h.interceptors.request.use(
19
20
  async (e) => {
20
21
  try {
21
- return await s.onBeforeRequest(e) || e;
22
- } catch (r) {
23
- return Promise.reject(r);
22
+ return await r.onBeforeRequest(e) || e;
23
+ } catch (s) {
24
+ return Promise.reject(s);
24
25
  }
25
26
  },
26
27
  (e) => Promise.reject(e)
27
- ), s.onStartRequest && h.interceptors.request.use(
28
+ ), r.onStartRequest && h.interceptors.request.use(
28
29
  async (e) => {
29
30
  try {
30
- return await s.onStartRequest(), e;
31
- } catch (r) {
32
- return Promise.reject(r);
31
+ return await r.onStartRequest(), e;
32
+ } catch (s) {
33
+ return Promise.reject(s);
33
34
  }
34
35
  },
35
36
  (e) => Promise.reject(e)
36
- ), s.onFinishRequest && h.interceptors.response.use(
37
- (e) => (s.onFinishRequest(), e),
38
- (e) => (s.onFinishRequest(), Promise.reject(e))
37
+ ), r.onFinishRequest && h.interceptors.response.use(
38
+ (e) => (r.onFinishRequest(), e),
39
+ (e) => (r.onFinishRequest(), Promise.reject(e))
39
40
  ), h.interceptors.request.use((e) => {
40
41
  if (!e.url) return e;
41
- const r = (a) => {
42
+ const s = (a) => {
42
43
  if (a)
43
44
  for (const [t, v] of Object.entries(a)) {
44
45
  const l = `{${t}}`;
@@ -48,19 +49,19 @@ function z(s) {
48
49
  ), delete a[t]);
49
50
  }
50
51
  };
51
- return e.method !== "get" && e.data?.params && r(e.data.params), r(e.params), e;
52
- }), s.csrfRefreshEndpoint && h.interceptors.response.use(
52
+ return e.method !== "get" && e.data?.params && s(e.data.params), s(e.params), e;
53
+ }), r.csrfRefreshEndpoint && h.interceptors.response.use(
53
54
  (e) => e,
54
55
  async (e) => {
55
- const r = e.config;
56
- if (r?.url === s.csrfRefreshEndpoint)
56
+ const s = e.config;
57
+ if (s?.url === r.csrfRefreshEndpoint)
57
58
  return Promise.reject(e);
58
- if (e.response && (e.response.status === 403 || e.response.status === 419) && !r?._retry) {
59
- r._retry = !0;
59
+ if (e.response && (e.response.status === 403 || e.response.status === 419) && !s?._retry) {
60
+ s._retry = !0;
60
61
  try {
61
- return b && q ? await q : (b = !0, q = h.get(s.csrfRefreshEndpoint).then(() => {
62
+ return b && q ? await q : (b = !0, q = h.get(r.csrfRefreshEndpoint).then(() => {
62
63
  b = !1, q = null;
63
- }), await q), h.request(r);
64
+ }), await q), h.request(s);
64
65
  } catch (a) {
65
66
  return b = !1, q = null, Promise.reject(a);
66
67
  }
@@ -69,93 +70,93 @@ function z(s) {
69
70
  }
70
71
  ), h.interceptors.response.use(
71
72
  (e) => e,
72
- (e) => (F(() => {
73
+ (e) => (T(() => {
73
74
  e.code;
74
75
  }), Promise.reject(e))
75
76
  );
76
- const C = s.queries ?? {}, M = {};
77
+ const C = r.queries ?? {}, M = {};
77
78
  for (const e in C) {
78
- const r = C[e];
79
- r && (M[e] = (a) => {
79
+ const s = C[e];
80
+ s && (M[e] = (a) => {
80
81
  let t;
81
82
  a && typeof a == "object" && ("loadOnMount" in a || "debounce" in a || "onResult" in a || "onError" in a || "data" in a ? t = a : t = { params: a });
82
- const v = m(), l = m(), E = m(), g = m(!1), y = m(!1), P = m(!0);
83
+ const v = m(), l = m(), R = m(), g = m(!1), y = m(!1), P = m(!0);
83
84
  let j = new AbortController();
84
85
  const u = () => {
85
86
  j?.abort(), j = new AbortController();
86
87
  }, c = async () => {
87
88
  g.value && u(), g.value = !0, l.value = void 0;
88
89
  try {
89
- r.params && t?.params && r.params.parse(t.params);
90
+ s.params && t?.params && s.params.parse(t.params);
90
91
  let o = t?.data;
91
- r.data && o && r.data.parse(o);
92
+ s.data && o && s.data.parse(o);
92
93
  const f = {
93
- method: r.method ?? "GET",
94
- url: r.path,
94
+ method: s.method ?? "GET",
95
+ url: s.path,
95
96
  params: t?.params,
96
97
  signal: j.signal
97
98
  };
98
- r.method === "POST" && o && (f.data = o);
99
- const n = await h.request(f), i = r.response ? r.response.parse(n.data) : n.data;
99
+ s.method === "POST" && o && (f.data = o);
100
+ const n = await h.request(f), i = s.response ? s.response.parse(n.data) : n.data;
100
101
  v.value = i, t?.onResult?.(i);
101
102
  } catch (o) {
102
- if (o instanceof S) {
103
+ if (o instanceof F) {
103
104
  if (o.code !== "ERR_CANCELED") {
104
105
  const f = o.response?.data?.message || o.message || "An error occurred", n = o.response?.status, i = o.code, p = o.response?.data;
105
- l.value = f, t?.onError?.(o), s.onErrorRequest?.({ message: f, status: n, code: i, data: p });
106
+ l.value = f, t?.onError?.(o), r.onErrorRequest?.({ message: f, status: n, code: i, data: p });
106
107
  }
107
- } else if (o instanceof L) {
108
- E.value = o.issues || [];
109
- const n = `Validation error: ${E.value.map(
108
+ } else if (o instanceof S) {
109
+ R.value = o.issues || [];
110
+ const n = `Validation error: ${R.value.map(
110
111
  (i) => `${i.path.join(".")}: ${i.message}`
111
112
  ).join(", ")}`;
112
- l.value = n, t?.onError?.(o), t?.onZodError?.(E.value), s.onErrorRequest?.({ message: n, code: "VALIDATION_ERROR" }), s.onZodError && s.onZodError(E.value);
113
+ l.value = n, t?.onError?.(o), t?.onZodError?.(R.value), r.onErrorRequest?.({ message: n, code: "VALIDATION_ERROR" }), r.onZodError && r.onZodError(R.value);
113
114
  } else {
114
115
  const f = o.message || "An error occurred";
115
- l.value = f, t?.onError?.(f), s.onErrorRequest?.({ message: f });
116
+ l.value = f, t?.onError?.(f), r.onErrorRequest?.({ message: f });
116
117
  }
117
118
  } finally {
118
119
  g.value = !1, y.value = !0;
119
120
  }
120
- }, R = t?.debounce ? $(c, t.debounce) : c;
121
+ }, E = t?.debounce ? Z(c, t.debounce) : c;
121
122
  let d = null;
122
- return (t?.params || t?.data) && (N(() => {
123
- d && d(), d = T(
123
+ return (t?.params || t?.data) && (k(() => {
124
+ d && d(), d = x(
124
125
  () => JSON.stringify({ params: t.params, data: t.data }),
125
126
  () => {
126
- R();
127
+ E();
127
128
  },
128
129
  { immediate: !1 }
129
130
  );
130
- }), Z(() => {
131
+ }), N(() => {
131
132
  d && d(), j?.abort();
132
- })), (t?.loadOnMount === void 0 || t.loadOnMount) && !y.value && (P.value ? (P.value = !1, c()) : R()), { result: v, errorMessage: l, zodErrors: E, isLoading: g, isDone: y, refetch: c };
133
+ })), (t?.loadOnMount === void 0 || t.loadOnMount) && !y.value && (P.value ? (P.value = !1, c()) : E()), { result: v, errorMessage: l, zodErrors: R, isLoading: g, isDone: y, refetch: c };
133
134
  });
134
135
  }
135
- const D = s.mutations ?? {}, w = {};
136
- for (const e in D) {
137
- const r = D[e];
138
- r && (w[e] = (a) => {
139
- const t = m(), v = m(), l = m(), E = m(!1), g = m(!1), y = m(0);
140
- return { result: t, errorMessage: v, zodErrors: l, isLoading: E, isDone: g, uploadProgress: y, mutate: async (j) => {
141
- if (!E.value) {
142
- E.value = !0, v.value = void 0, y.value = 0;
136
+ const w = r.mutations ?? {}, D = {};
137
+ for (const e in w) {
138
+ const s = w[e];
139
+ s && (D[e] = (a) => {
140
+ const t = m(), v = m(), l = m(), R = m(!1), g = m(!1), y = m(0);
141
+ return { result: t, errorMessage: v, zodErrors: l, isLoading: R, isDone: g, uploadProgress: y, mutate: async (j) => {
142
+ if (!R.value) {
143
+ R.value = !0, v.value = void 0, y.value = 0;
143
144
  try {
144
145
  const { data: u = {}, params: c } = j ?? {};
145
- let R = u ?? {}, d = {};
146
- if (r.isMultipart) {
146
+ let E = u ?? {}, d = {};
147
+ if (s.isMultipart) {
147
148
  const n = new FormData();
148
149
  for (const [i, p] of Object.entries(u))
149
150
  p instanceof File || p instanceof Blob ? n.append(i, p) : Array.isArray(p) ? p.forEach((A) => {
150
151
  A instanceof File || A instanceof Blob ? n.append(i, A) : n.append(i, JSON.stringify(A));
151
152
  }) : typeof p == "object" && p !== null ? n.append(i, JSON.stringify(p)) : n.append(i, String(p));
152
- R = n, d["Content-Type"] = "multipart/form-data";
153
- } else r.data && r.data.parse(u);
154
- r.params && c && r.params.parse(c);
153
+ E = n, d["Content-Type"] = "multipart/form-data";
154
+ } else s.data && s.data.parse(u);
155
+ s.params && c && s.params.parse(c);
155
156
  const o = await h.request({
156
- method: r.method,
157
- url: r.path,
158
- data: R,
157
+ method: s.method,
158
+ url: s.path,
159
+ data: E,
159
160
  params: c,
160
161
  headers: d,
161
162
  onUploadProgress: (n) => {
@@ -164,24 +165,24 @@ function z(s) {
164
165
  y.value = i, a?.onUploadProgress?.(i);
165
166
  }
166
167
  }
167
- }), f = r.response ? r.response.parse(o.data) : o.data;
168
+ }), f = s.response ? s.response.parse(o.data) : o.data;
168
169
  t.value = f, a?.onResult?.(f);
169
170
  } catch (u) {
170
- if (u instanceof S) {
171
- const c = u.response?.data?.message || u.message || "An error occurred", R = u.response?.status, d = u.code;
172
- v.value = c, a?.onError?.(u), s.onErrorRequest?.({ message: c, status: R, code: d });
173
- } else if (u instanceof L) {
171
+ if (u instanceof F) {
172
+ const c = u.response?.data?.message || u.message || "An error occurred", E = u.response?.status, d = u.code;
173
+ v.value = c, a?.onError?.(u), r.onErrorRequest?.({ message: c, status: E, code: d });
174
+ } else if (u instanceof S) {
174
175
  l.value = u.issues || [];
175
- const R = `Validation error: ${l.value.map(
176
+ const E = `Validation error: ${l.value.map(
176
177
  (d) => `${d.path.join(".")}: ${d.message}`
177
178
  ).join(", ")}`;
178
- v.value = R, a?.onError?.(u), a?.onZodError?.(l.value), s.onErrorRequest?.({ message: R, code: "VALIDATION_ERROR" }), s.onZodError && s.onZodError(l.value);
179
+ v.value = E, a?.onError?.(u), a?.onZodError?.(l.value), r.onErrorRequest?.({ message: E, code: "VALIDATION_ERROR" }), r.onZodError && r.onZodError(l.value);
179
180
  } else {
180
181
  const c = u.message || "An error occurred";
181
- v.value = c, a?.onError?.(u), s.onErrorRequest?.({ message: c });
182
+ v.value = c, a?.onError?.(u), r.onErrorRequest?.({ message: c });
182
183
  }
183
184
  } finally {
184
- E.value = !1, g.value = !0;
185
+ R.value = !1, g.value = !0;
185
186
  }
186
187
  }
187
188
  } };
@@ -189,23 +190,23 @@ function z(s) {
189
190
  }
190
191
  return {
191
192
  query: M,
192
- mutation: w
193
+ mutation: D
193
194
  };
194
195
  }
195
- function V(s) {
196
- return s;
196
+ function V(r) {
197
+ return r;
197
198
  }
198
- function J(s) {
199
- return s;
199
+ function J(r) {
200
+ return r;
200
201
  }
201
- function Q(...s) {
202
- return Object.assign({}, ...s);
202
+ function Q(...r) {
203
+ return Object.assign({}, ...r);
203
204
  }
204
- function W(...s) {
205
- return Object.assign({}, ...s);
205
+ function W(...r) {
206
+ return Object.assign({}, ...r);
206
207
  }
207
208
  export {
208
- K as AxiosError,
209
+ H as AxiosError,
209
210
  z as createApiClient,
210
211
  J as defineMutation,
211
212
  V as defineQuery,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vue-api-kit",
3
3
  "type": "module",
4
- "version": "1.5.2",
4
+ "version": "1.7.0",
5
5
  "description": "A powerful and flexible API client for Vue 3 applications, built with TypeScript and Zod for type-safe API interactions.",
6
6
  "keywords": [
7
7
  "vue3",