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