Skip to content

Commit c435cc3

Browse files
committed
make http client aware of the csrf token
1 parent a68b3be commit c435cc3

File tree

9 files changed

+154
-130
lines changed

9 files changed

+154
-130
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tailflow/laravel-orion",
3-
"version": "2.1.2",
3+
"version": "3.0.0",
44
"description": "Typescript SDK for Laravel Orion",
55
"keywords": [
66
"laravel orion",

src/drivers/default/builders/queryBuilder.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class QueryBuilder<
5252
}
5353

5454
public async get(limit: number = 15, page: number = 1): Promise<Array<M>> {
55-
const response = await this.httpClient.request(
55+
const response = await this.httpClient.request<{data: Array<AllAttributes & Relations>}>(
5656
'',
5757
HttpMethod.GET,
5858
this.prepareQueryParams({ limit, page })
@@ -64,7 +64,7 @@ export class QueryBuilder<
6464
}
6565

6666
public async search(limit: number = 15, page: number = 1): Promise<Array<M>> {
67-
const response = await this.httpClient.request(
67+
const response = await this.httpClient.request<{data: Array<AllAttributes & Relations>}>(
6868
'/search',
6969
HttpMethod.POST,
7070
this.prepareQueryParams({ limit, page }),
@@ -82,7 +82,7 @@ export class QueryBuilder<
8282
}
8383

8484
public async find(key: Key): Promise<M> {
85-
const response = await this.httpClient.request(
85+
const response = await this.httpClient.request<{data: AllAttributes & Relations}>(
8686
`/${key}`,
8787
HttpMethod.GET,
8888
this.prepareQueryParams()
@@ -92,29 +92,29 @@ export class QueryBuilder<
9292
}
9393

9494
public async store(attributes: Attributes): Promise<M> {
95-
const response = await this.httpClient.request(
95+
const response = await this.httpClient.request<{data: AllAttributes & Relations}>(
9696
'',
9797
HttpMethod.POST,
9898
this.prepareQueryParams(),
99-
attributes
99+
attributes as Record<string, unknown>
100100
);
101101

102102
return this.hydrate(response.data.data, response);
103103
}
104104

105105
public async update(key: Key, attributes: Attributes): Promise<M> {
106-
const response = await this.httpClient.request(
106+
const response = await this.httpClient.request<{data: AllAttributes & Relations}>(
107107
`/${key}`,
108108
HttpMethod.PATCH,
109109
this.prepareQueryParams(),
110-
attributes
110+
attributes as Record<string, unknown>
111111
);
112112

113113
return this.hydrate(response.data.data, response);
114114
}
115115

116116
public async destroy(key: Key, force: boolean = false): Promise<M> {
117-
const response = await this.httpClient.request(
117+
const response = await this.httpClient.request<{data: AllAttributes & Relations}>(
118118
`/${key}`,
119119
HttpMethod.DELETE,
120120
this.prepareQueryParams({ force })
@@ -124,7 +124,7 @@ export class QueryBuilder<
124124
}
125125

126126
public async restore(key: Key): Promise<M> {
127-
const response = await this.httpClient.request(
127+
const response = await this.httpClient.request<{data: AllAttributes & Relations}>(
128128
`/${key}/restore`,
129129
HttpMethod.POST,
130130
this.prepareQueryParams()

src/drivers/default/builders/relationQueryBuilder.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class RelationQueryBuilder<
2323
Relations,
2424
Key
2525
>,
26-
parent: Model<any>
26+
parent: Model
2727
) {
2828
super(relationConstructor);
2929

src/drivers/default/relations/belongsToMany.ts

+36-26
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { Model } from '../../../model';
2-
import { RelationQueryBuilder } from '../builders/relationQueryBuilder';
3-
import { HttpMethod } from '../enums/httpMethod';
4-
import { AttachResult } from '../results/attachResult';
5-
import { ExtractModelAttributesType } from '../../../types/extractModelAttributesType';
6-
import { DetachResult } from '../results/detachResult';
7-
import { SyncResult } from '../results/syncResult';
8-
import { ToggleResult } from '../results/toggleResult';
9-
import { UpdatePivotResult } from '../results/updatePivotResult';
10-
import { ExtractModelPersistedAttributesType } from '../../../types/extractModelPersistedAttributesType';
11-
import { ExtractModelRelationsType } from '../../../types/extractModelRelationsType';
1+
import {Model} from '../../../model';
2+
import {RelationQueryBuilder} from '../builders/relationQueryBuilder';
3+
import {HttpMethod} from '../enums/httpMethod';
4+
import {AttachResult} from '../results/attachResult';
5+
import {ExtractModelAttributesType} from '../../../types/extractModelAttributesType';
6+
import {DetachResult} from '../results/detachResult';
7+
import {SyncResult} from '../results/syncResult';
8+
import {ToggleResult} from '../results/toggleResult';
9+
import {UpdatePivotResult} from '../results/updatePivotResult';
10+
import {
11+
ExtractModelPersistedAttributesType
12+
} from '../../../types/extractModelPersistedAttributesType';
13+
import {ExtractModelRelationsType} from '../../../types/extractModelRelationsType';
1214

1315
export class BelongsToMany<
1416
Relation extends Model,
@@ -21,10 +23,10 @@ export class BelongsToMany<
2123
keys: Array<number | string>,
2224
duplicates: boolean = false
2325
): Promise<AttachResult> {
24-
const response = await this.httpClient.request(
26+
const response = await this.httpClient.request<{ attached: Array<number | string> }>(
2527
`/attach`,
2628
HttpMethod.POST,
27-
{ duplicates: duplicates ? 1 : 0 },
29+
{duplicates: duplicates ? 1 : 0},
2830
{
2931
resources: keys,
3032
}
@@ -37,37 +39,39 @@ export class BelongsToMany<
3739
resources: Record<string, Pivot>,
3840
duplicates: boolean = false
3941
): Promise<AttachResult> {
40-
const response = await this.httpClient.request(
42+
const response = await this.httpClient.request<{ attached: Array<number | string> }>(
4143
`/attach`,
4244
HttpMethod.POST,
43-
{ duplicates: duplicates ? 1 : 0 },
44-
{ resources }
45+
{duplicates: duplicates ? 1 : 0},
46+
{resources}
4547
);
4648

4749
return new AttachResult(response.data.attached);
4850
}
4951

5052
public async detach(keys: Array<number | string>): Promise<DetachResult> {
51-
const response = await this.httpClient.request(`/detach`, HttpMethod.DELETE, null, {
53+
const response = await this.httpClient.request<{ detached: Array<number | string> }>(`/detach`, HttpMethod.DELETE, null, {
5254
resources: keys,
5355
});
5456

5557
return new DetachResult(response.data.detached);
5658
}
5759

5860
public async detachWithFields(resources: Record<string, Pivot>): Promise<DetachResult> {
59-
const response = await this.httpClient.request(`/detach`, HttpMethod.DELETE, null, {
61+
const response = await this.httpClient.request<{ detached: Array<number | string> }>(`/detach`, HttpMethod.DELETE, null, {
6062
resources,
6163
});
6264

6365
return new DetachResult(response.data.detached);
6466
}
6567

6668
public async sync(keys: Array<number | string>, detaching: boolean = true): Promise<SyncResult> {
67-
const response = await this.httpClient.request(
69+
const response = await this.httpClient.request<
70+
{ attached: Array<number | string>, updated: Array<number | string>, detached: Array<number | string> }
71+
>(
6872
`/sync`,
6973
HttpMethod.PATCH,
70-
{ detaching: detaching ? 1 : 0 },
74+
{detaching: detaching ? 1 : 0},
7175
{
7276
resources: keys,
7377
}
@@ -80,34 +84,40 @@ export class BelongsToMany<
8084
resources: Record<string, Pivot>,
8185
detaching: boolean = true
8286
): Promise<SyncResult> {
83-
const response = await this.httpClient.request(
87+
const response = await this.httpClient.request<
88+
{ attached: Array<number | string>, updated: Array<number | string>, detached: Array<number | string> }
89+
>(
8490
`/sync`,
8591
HttpMethod.PATCH,
86-
{ detaching: detaching ? 1 : 0 },
87-
{ resources }
92+
{detaching: detaching ? 1 : 0},
93+
{resources}
8894
);
8995

9096
return new SyncResult(response.data.attached, response.data.updated, response.data.detached);
9197
}
9298

9399
public async toggle(keys: Array<number | string>): Promise<ToggleResult> {
94-
const response = await this.httpClient.request(`/toggle`, HttpMethod.PATCH, null, {
100+
const response = await this.httpClient.request<
101+
{ attached: Array<number | string>, detached: Array<number | string> }
102+
>(`/toggle`, HttpMethod.PATCH, null, {
95103
resources: keys,
96104
});
97105

98106
return new ToggleResult(response.data.attached, response.data.detached);
99107
}
100108

101109
public async toggleWithFields(resources: Record<string, Pivot>): Promise<ToggleResult> {
102-
const response = await this.httpClient.request(`/toggle`, HttpMethod.PATCH, null, {
110+
const response = await this.httpClient.request<
111+
{ attached: Array<number | string>, detached: Array<number | string> }
112+
>(`/toggle`, HttpMethod.PATCH, null, {
103113
resources,
104114
});
105115

106116
return new ToggleResult(response.data.attached, response.data.detached);
107117
}
108118

109119
public async updatePivot(key: number | string, pivot: Pivot): Promise<UpdatePivotResult> {
110-
const response = await this.httpClient.request(`/${key}/pivot`, HttpMethod.PATCH, null, {
120+
const response = await this.httpClient.request<{ updated: Array<string | number> }>(`/${key}/pivot`, HttpMethod.PATCH, null, {
111121
pivot,
112122
});
113123

src/drivers/default/relations/hasMany.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class HasMany<
1212
Relations = ExtractModelRelationsType<Relation>
1313
> extends RelationQueryBuilder<Relation, Attributes, PersistedAttributes, Relations> {
1414
public async associate(key: string | number): Promise<Relation> {
15-
const response = await this.httpClient.request(
15+
const response = await this.httpClient.request<{data: Attributes & PersistedAttributes & Relations}>(
1616
`/associate`,
1717
HttpMethod.POST,
1818
this.prepareQueryParams(),
@@ -25,7 +25,7 @@ export class HasMany<
2525
}
2626

2727
public async dissociate(key: string | number): Promise<Relation> {
28-
const response = await this.httpClient.request(
28+
const response = await this.httpClient.request<{data: Attributes & PersistedAttributes & Relations}>(
2929
`/${key}/dissociate`,
3030
HttpMethod.DELETE,
3131
this.prepareQueryParams()

src/httpClient.ts

+78-11
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
import { HttpMethod } from './drivers/default/enums/httpMethod';
2-
import { Orion } from './orion';
3-
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
1+
import {HttpMethod} from './drivers/default/enums/httpMethod';
2+
import {Orion} from './orion';
3+
import {AxiosInstance, AxiosRequestConfig, AxiosResponse} from 'axios';
4+
import {AuthDriver} from "./drivers/default/enums/authDriver";
45

56
export class HttpClient {
6-
protected baseUrl: string;
7-
protected client: AxiosInstance;
7+
protected static csrfTokenFetched = false;
88

9-
constructor(baseUrl: string, client: AxiosInstance) {
9+
constructor(protected baseUrl: string, protected client: AxiosInstance, protected authDriver: AuthDriver) {
1010
this.baseUrl = baseUrl;
1111
this.client = client;
12+
this.authDriver = authDriver;
1213
}
1314

14-
public async request(
15+
public async request<Response extends Record<string, unknown>>(
1516
url: string,
1617
method: HttpMethod,
17-
params: any = {},
18-
data: any = {}
19-
): Promise<AxiosResponse> {
18+
params: Record<string, unknown> | null = {},
19+
data: Record<string, unknown> = {}
20+
): Promise<AxiosResponse<Response>> {
2021
const config: AxiosRequestConfig = Object.assign(Orion.getHttpClientConfig(), {
2122
baseURL: this.baseUrl,
2223
url,
@@ -26,9 +27,75 @@ export class HttpClient {
2627

2728
if (method !== HttpMethod.GET) {
2829
config.data = data;
30+
31+
if (!HttpClient.csrfTokenFetched && this.authDriver === AuthDriver.Sanctum) {
32+
await this.fetchCsrfToken();
33+
}
2934
}
3035

31-
return this.client.request(config);
36+
return this.client.request<Response>(config);
37+
}
38+
39+
public async get<Response extends Record<string, unknown>>(url: string, params: Record<string, unknown>): Promise<AxiosResponse<Response>> {
40+
return this.request<Response>(
41+
url, HttpMethod.GET, params
42+
)
43+
}
44+
45+
public async post<Response extends Record<string, unknown>>(url: string, params: Record<string, unknown>, data: Record<string, unknown>): Promise<AxiosResponse<Response>> {
46+
return this.request<Response>(
47+
url, HttpMethod.POST, params, data
48+
)
49+
}
50+
51+
public async patch<Response extends Record<string, unknown>>(url: string, params: Record<string, unknown>, data: Record<string, unknown>): Promise<AxiosResponse<Response>> {
52+
return this.request<Response>(
53+
url, HttpMethod.PATCH, params, data
54+
)
55+
}
56+
57+
public async delete<Response extends Record<string, unknown>>(url: string, params: Record<string, unknown>): Promise<AxiosResponse<Response>> {
58+
return this.request<Response>(
59+
url, HttpMethod.DELETE, params
60+
)
61+
}
62+
63+
public async fetchCsrfToken(): Promise<void> {
64+
if (this.authDriver !== AuthDriver.Sanctum) {
65+
throw new Error(
66+
`Current auth driver is set to "${this.authDriver}". Fetching CSRF cookie can only be used with "sanctum" driver.`
67+
);
68+
}
69+
70+
let response = null;
71+
72+
try {
73+
response = await this
74+
.getAxios()
75+
.get(`sanctum/csrf-cookie`, {baseURL: Orion.getHost()});
76+
} catch (error) {
77+
throw new Error(
78+
`Unable to retrieve XSRF token cookie due to network error. Please ensure that SANCTUM_STATEFUL_DOMAINS and SESSION_DOMAIN environment variables are configured correctly on the API side.`
79+
);
80+
}
81+
82+
const xsrfTokenPresent =
83+
document.cookie
84+
.split(';')
85+
.filter((cookie: string) =>
86+
cookie.includes(this.getAxios().defaults.xsrfCookieName || 'XSRF-TOKEN')
87+
).length > 0;
88+
89+
if (!xsrfTokenPresent) {
90+
console.log(`Response status: ${response.status}`);
91+
console.log(`Response headers:`);
92+
console.log(response.headers);
93+
console.log(`Cookies: ${document.cookie}`);
94+
95+
throw new Error(
96+
`XSRF token cookie is missing in the response. Please ensure that SANCTUM_STATEFUL_DOMAINS and SESSION_DOMAIN environment variables are configured correctly on the API side.`
97+
);
98+
}
3299
}
33100

34101
public getAxios(): AxiosInstance {

0 commit comments

Comments
 (0)