@ibertran/fortytwo-intra-client 4.1.0 → 5.1.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 +41 -0
- package/dist/errors/httpError.d.ts +8 -0
- package/dist/errors/httpError.js +25 -0
- package/dist/errors/validationError.js +1 -5
- package/dist/index.d.ts +11 -5
- package/dist/index.js +49 -16
- package/dist/intraV3/index.d.ts +62 -0
- package/dist/intraV3/index.js +285 -0
- package/dist/intraV3/schemas.d.ts +27 -0
- package/dist/intraV3/schemas.js +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,26 @@ const user = await client.get("users/ibertran");
|
|
|
20
20
|
console.log(user);
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
## Intra V3
|
|
24
|
+
|
|
25
|
+
The library also exports `FortytwoIntraV3Client` for staff-only Intra V3 endpoints that use the 42 Auth token flow.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { FortytwoIntraV3Client } from "@ibertran/fortytwo-intra-client";
|
|
29
|
+
|
|
30
|
+
const client = new FortytwoIntraV3Client({
|
|
31
|
+
client_id: "staff-42-campus",
|
|
32
|
+
client_secret: "your_client_secret",
|
|
33
|
+
username: "your_staff_login",
|
|
34
|
+
password: "your_staff_password",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const response = await client.get("some/v3/endpoint");
|
|
38
|
+
console.log(response);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The V3 client automatically requests a bearer token, reuses it while valid, and falls back to the refresh token when available.
|
|
42
|
+
|
|
23
43
|
## Configuration
|
|
24
44
|
|
|
25
45
|
The `FortytwoIntraClient` class accepts three parameters:
|
|
@@ -120,6 +140,27 @@ const allProjects = await client.getAll("projects", {
|
|
|
120
140
|
});
|
|
121
141
|
```
|
|
122
142
|
|
|
143
|
+
### Raw Requests
|
|
144
|
+
|
|
145
|
+
The `rawRequest` method takes a plain [axios request config](https://axios-http.com/docs/req_config), injects the access token, and returns the raw `AxiosResponse`. Relative URLs are resolved against the client's `base_url` unless `baseURL` is set in the config.
|
|
146
|
+
|
|
147
|
+
Unlike the other methods, it does not retry, log, validate, or wrap errors: failures are thrown as `AxiosError`.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Full axios config, raw response
|
|
151
|
+
const res = await client.rawRequest({
|
|
152
|
+
method: "get",
|
|
153
|
+
url: "campus/9/users",
|
|
154
|
+
params: { "page[size]": 100 },
|
|
155
|
+
});
|
|
156
|
+
console.log(res.status, res.headers["x-total"], res.data);
|
|
157
|
+
|
|
158
|
+
// With a user token (V2 client only)
|
|
159
|
+
const me = await client.rawRequest({ url: "me" }, tokens);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`rawRequest` is available on both `FortytwoIntraClient` and `FortytwoIntraV3Client`. On the V2 client, requests still go through the rate limiter.
|
|
163
|
+
|
|
123
164
|
## OAuth Authentication
|
|
124
165
|
|
|
125
166
|
### Authorization Flow
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AxiosError } from "axios";
|
|
2
|
+
export declare class FortytwoIntraClientHttpError extends Error {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
statusText: string;
|
|
5
|
+
data: unknown;
|
|
6
|
+
constructor(error: AxiosError);
|
|
7
|
+
}
|
|
8
|
+
export declare function isFortytwoIntraClientHttpError(error: unknown): error is FortytwoIntraClientHttpError;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FortytwoIntraClientHttpError = void 0;
|
|
4
|
+
exports.isFortytwoIntraClientHttpError = isFortytwoIntraClientHttpError;
|
|
5
|
+
class FortytwoIntraClientHttpError extends Error {
|
|
6
|
+
statusCode;
|
|
7
|
+
statusText;
|
|
8
|
+
data;
|
|
9
|
+
constructor(error) {
|
|
10
|
+
const method = error.config?.method?.toUpperCase() ?? null;
|
|
11
|
+
const route = error.config?.url ?? null;
|
|
12
|
+
const statusCode = error.response?.status ?? NaN;
|
|
13
|
+
const statusText = error.response?.statusText ?? "Unknown";
|
|
14
|
+
const message = method && route ? `${method} ${route} - HTTP ${statusCode} ${statusText}` : error.message;
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "FortytwoIntraClientHttpError";
|
|
17
|
+
this.statusCode = statusCode;
|
|
18
|
+
this.statusText = statusText;
|
|
19
|
+
this.data = error.response?.data;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.FortytwoIntraClientHttpError = FortytwoIntraClientHttpError;
|
|
23
|
+
function isFortytwoIntraClientHttpError(error) {
|
|
24
|
+
return error instanceof FortytwoIntraClientHttpError;
|
|
25
|
+
}
|
|
@@ -6,11 +6,7 @@ class FortytwoIntraClientValidationError extends Error {
|
|
|
6
6
|
schemaErrors;
|
|
7
7
|
data;
|
|
8
8
|
constructor(schemaErrors, data) {
|
|
9
|
-
|
|
10
|
-
// .map((error) => `${error.path.join(".")} - ${error.message}`)
|
|
11
|
-
// .join("; ");
|
|
12
|
-
const message = `Schema validation failed: `;
|
|
13
|
-
super(message);
|
|
9
|
+
super("Schema validation failed");
|
|
14
10
|
this.name = "FortytwoIntraClientValidationError";
|
|
15
11
|
this.schemaErrors = schemaErrors;
|
|
16
12
|
this.data = data;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { inputOptions, userToken } from "./types";
|
|
4
|
+
export { FortytwoIntraV3Client } from "./intraV3";
|
|
5
|
+
export * from "./errors/httpError";
|
|
6
|
+
export * from "./errors/validationError";
|
|
4
7
|
export interface FortytwoIntraClientConf {
|
|
5
8
|
redirect_uri: string | null;
|
|
6
9
|
base_url: string;
|
|
@@ -30,9 +33,12 @@ export declare class FortytwoIntraClient {
|
|
|
30
33
|
private maxRetry;
|
|
31
34
|
private logLine;
|
|
32
35
|
private errLogBody;
|
|
33
|
-
private
|
|
36
|
+
private authorization;
|
|
37
|
+
private tokenRequest;
|
|
34
38
|
constructor(client_id: string, client_secret: string, conf: Partial<FortytwoIntraClientConf>);
|
|
35
39
|
private generateToken;
|
|
40
|
+
private isTokenValid;
|
|
41
|
+
private getAccessToken;
|
|
36
42
|
private fetch;
|
|
37
43
|
private reqHandler;
|
|
38
44
|
private logSuccess;
|
|
@@ -62,6 +68,7 @@ export declare class FortytwoIntraClient {
|
|
|
62
68
|
getAll<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "body"> & {
|
|
63
69
|
schema: S;
|
|
64
70
|
}): Promise<z.infer<S>>;
|
|
71
|
+
rawRequest<T = any, D = any>(config: AxiosRequestConfig<D>, token?: userToken): Promise<AxiosResponse<T, D>>;
|
|
65
72
|
URL(endpoint: string): URL;
|
|
66
73
|
getOAuthUrl(options?: {
|
|
67
74
|
redirect_uri?: string;
|
|
@@ -73,4 +80,3 @@ export declare class FortytwoIntraClient {
|
|
|
73
80
|
exchangeOAuthCode(code: string, redirect_uri?: string): Promise<any>;
|
|
74
81
|
tokenInfos(options?: Omit<inputOptions, "body" | "perPage" | "maxPages">): Promise<any>;
|
|
75
82
|
}
|
|
76
|
-
export { FortytwoIntraClientError, isFortytwoIntraClientError };
|
package/dist/index.js
CHANGED
|
@@ -32,20 +32,25 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
|
+
};
|
|
35
38
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
40
|
};
|
|
38
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.
|
|
42
|
+
exports.FortytwoIntraClient = exports.FortytwoIntraV3Client = void 0;
|
|
40
43
|
const axios_1 = __importStar(require("axios"));
|
|
41
44
|
const axios_rate_limit_1 = __importDefault(require("axios-rate-limit"));
|
|
42
|
-
const
|
|
45
|
+
const zod_1 = __importDefault(require("zod"));
|
|
43
46
|
const logs_1 = require("./lib/logs");
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
Object.defineProperty(exports, "isFortytwoIntraClientError", { enumerable: true, get: function () { return errors_1.isFortytwoIntraClientError; } });
|
|
47
|
-
const zod_1 = require("zod");
|
|
47
|
+
const pagination_1 = require("./lib/pagination");
|
|
48
|
+
const httpError_1 = require("./errors/httpError");
|
|
48
49
|
const validationError_1 = require("./errors/validationError");
|
|
50
|
+
var intraV3_1 = require("./intraV3");
|
|
51
|
+
Object.defineProperty(exports, "FortytwoIntraV3Client", { enumerable: true, get: function () { return intraV3_1.FortytwoIntraV3Client; } });
|
|
52
|
+
__exportStar(require("./errors/httpError"), exports);
|
|
53
|
+
__exportStar(require("./errors/validationError"), exports);
|
|
49
54
|
const defaultConf = {
|
|
50
55
|
redirect_uri: null,
|
|
51
56
|
base_url: "https://api.intra.42.fr/v2/",
|
|
@@ -75,7 +80,8 @@ class FortytwoIntraClient {
|
|
|
75
80
|
maxRetry;
|
|
76
81
|
logLine;
|
|
77
82
|
errLogBody;
|
|
78
|
-
|
|
83
|
+
authorization;
|
|
84
|
+
tokenRequest = null;
|
|
79
85
|
constructor(client_id, client_secret, conf) {
|
|
80
86
|
this.client_id = client_id;
|
|
81
87
|
this.client_secret = client_secret;
|
|
@@ -98,7 +104,7 @@ class FortytwoIntraClient {
|
|
|
98
104
|
this.maxRetry = config.maxRetry;
|
|
99
105
|
this.logLine = config.logLine;
|
|
100
106
|
this.errLogBody = config.errLogBody;
|
|
101
|
-
this.
|
|
107
|
+
this.authorization = null;
|
|
102
108
|
}
|
|
103
109
|
async generateToken() {
|
|
104
110
|
const res = await this.axiosInstance.post(this.token_url, {
|
|
@@ -107,15 +113,31 @@ class FortytwoIntraClient {
|
|
|
107
113
|
client_secret: this.client_secret,
|
|
108
114
|
scope: this.scopes.join(" "),
|
|
109
115
|
});
|
|
110
|
-
|
|
116
|
+
const { access_token, expires_in } = zod_1.default
|
|
117
|
+
.object({ access_token: zod_1.default.string(), expires_in: zod_1.default.number() })
|
|
118
|
+
.parse(res.data);
|
|
119
|
+
return { access_token, expires_at: Date.now() + expires_in * 1_000 };
|
|
120
|
+
}
|
|
121
|
+
isTokenValid() {
|
|
122
|
+
return this.authorization !== null && this.authorization.expires_at - 30_000 > Date.now();
|
|
123
|
+
}
|
|
124
|
+
async getAccessToken(token) {
|
|
125
|
+
if (token) {
|
|
126
|
+
return token.access_token;
|
|
127
|
+
}
|
|
128
|
+
if (!this.isTokenValid()) {
|
|
129
|
+
// Share a single in-flight token request between concurrent callers
|
|
130
|
+
this.tokenRequest ??= this.generateToken().finally(() => {
|
|
131
|
+
this.tokenRequest = null;
|
|
132
|
+
});
|
|
133
|
+
this.authorization = await this.tokenRequest;
|
|
134
|
+
}
|
|
135
|
+
return this.authorization.access_token;
|
|
111
136
|
}
|
|
112
137
|
async fetch(url, options) {
|
|
113
138
|
const { method, body, query } = options;
|
|
114
|
-
if (!options.token && !this.access_token) {
|
|
115
|
-
this.access_token = await this.generateToken();
|
|
116
|
-
}
|
|
117
139
|
// Attach access_token
|
|
118
|
-
const accessToken =
|
|
140
|
+
const accessToken = await this.getAccessToken(options.token);
|
|
119
141
|
// Extract query parameters from URL and combine with options.query
|
|
120
142
|
const urlParams = {};
|
|
121
143
|
url.searchParams.forEach((value, key) => {
|
|
@@ -150,13 +172,13 @@ class FortytwoIntraClient {
|
|
|
150
172
|
const status = err.response?.status;
|
|
151
173
|
if (maxRetry > 0 && attempt < maxRetry && status && this.retryOn.includes(status)) {
|
|
152
174
|
if (status === 401) {
|
|
153
|
-
this.
|
|
175
|
+
this.authorization = null;
|
|
154
176
|
}
|
|
155
177
|
options.attempt++;
|
|
156
178
|
return this.reqHandler(url, options);
|
|
157
179
|
}
|
|
158
180
|
else {
|
|
159
|
-
throw new
|
|
181
|
+
throw new httpError_1.FortytwoIntraClientHttpError(err);
|
|
160
182
|
}
|
|
161
183
|
}
|
|
162
184
|
else {
|
|
@@ -191,7 +213,7 @@ class FortytwoIntraClient {
|
|
|
191
213
|
return schema.parse(data);
|
|
192
214
|
}
|
|
193
215
|
catch (err) {
|
|
194
|
-
if (err instanceof zod_1.
|
|
216
|
+
if (err instanceof zod_1.default.ZodError) {
|
|
195
217
|
throw new validationError_1.FortytwoIntraClientValidationError(err, data);
|
|
196
218
|
}
|
|
197
219
|
throw err;
|
|
@@ -317,6 +339,17 @@ class FortytwoIntraClient {
|
|
|
317
339
|
const allData = [...firstPage.data, ...otherPages.flatMap((res) => res.data)];
|
|
318
340
|
return this.validate(allData, options.schema);
|
|
319
341
|
}
|
|
342
|
+
async rawRequest(config, token) {
|
|
343
|
+
const accessToken = await this.getAccessToken(token);
|
|
344
|
+
return this.axiosInstance.request({
|
|
345
|
+
baseURL: this.base_url,
|
|
346
|
+
...config,
|
|
347
|
+
headers: {
|
|
348
|
+
...config.headers,
|
|
349
|
+
Authorization: `Bearer ${accessToken}`,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
320
353
|
URL(endpoint) {
|
|
321
354
|
return new URL(endpoint, this.base_url);
|
|
322
355
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { inputOptions } from "../types";
|
|
4
|
+
export * from "../errors/httpError";
|
|
5
|
+
export * from "../errors/validationError";
|
|
6
|
+
export interface FortytwoIntraClientConf {
|
|
7
|
+
base_url: string | undefined;
|
|
8
|
+
token_url: string;
|
|
9
|
+
maxRetry: number;
|
|
10
|
+
logLine: boolean;
|
|
11
|
+
errLogBody: boolean;
|
|
12
|
+
}
|
|
13
|
+
type Credentials = {
|
|
14
|
+
client_id: string;
|
|
15
|
+
client_secret: string;
|
|
16
|
+
username: string;
|
|
17
|
+
password: string;
|
|
18
|
+
};
|
|
19
|
+
export declare class FortytwoIntraV3Client {
|
|
20
|
+
private credentials;
|
|
21
|
+
private base_url;
|
|
22
|
+
private token_url;
|
|
23
|
+
private axiosInstance;
|
|
24
|
+
private retryOn;
|
|
25
|
+
private maxRetry;
|
|
26
|
+
private logLine;
|
|
27
|
+
private errLogBody;
|
|
28
|
+
private authorization;
|
|
29
|
+
private tokenRequest;
|
|
30
|
+
constructor(credentials: Credentials, conf?: Partial<FortytwoIntraClientConf>);
|
|
31
|
+
private generateToken;
|
|
32
|
+
private isTokenValid;
|
|
33
|
+
private isRefreshTokenValid;
|
|
34
|
+
private getAccessToken;
|
|
35
|
+
private fetch;
|
|
36
|
+
private reqHandler;
|
|
37
|
+
private logSuccess;
|
|
38
|
+
private logError;
|
|
39
|
+
private validate;
|
|
40
|
+
get(endpoint: URL | string, options?: Omit<inputOptions, "body" | "perPage" | "maxPages" | "token">): Promise<any>;
|
|
41
|
+
get<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "body" | "perPage" | "maxPages" | "token"> & {
|
|
42
|
+
schema: S;
|
|
43
|
+
}): Promise<z.infer<S>>;
|
|
44
|
+
post(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
|
|
45
|
+
post<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
|
|
46
|
+
schema: S;
|
|
47
|
+
}): Promise<z.infer<S>>;
|
|
48
|
+
put(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
|
|
49
|
+
put<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
|
|
50
|
+
schema: S;
|
|
51
|
+
}): Promise<z.infer<S>>;
|
|
52
|
+
patch(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
|
|
53
|
+
patch<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
|
|
54
|
+
schema: S;
|
|
55
|
+
}): Promise<z.infer<S>>;
|
|
56
|
+
delete(endpoint: URL | string, options?: Omit<inputOptions, "perPage" | "maxPages">): Promise<any>;
|
|
57
|
+
delete<S extends z.ZodType>(endpoint: URL | string, options: Omit<inputOptions, "perPage" | "maxPages"> & {
|
|
58
|
+
schema: S;
|
|
59
|
+
}): Promise<z.infer<S>>;
|
|
60
|
+
rawRequest<T = any, D = any>(config: AxiosRequestConfig<D>): Promise<AxiosResponse<T, D>>;
|
|
61
|
+
URL(endpoint: string): URL;
|
|
62
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.FortytwoIntraV3Client = void 0;
|
|
43
|
+
const axios_1 = __importStar(require("axios"));
|
|
44
|
+
const zod_1 = __importDefault(require("zod"));
|
|
45
|
+
const logs_1 = require("../lib/logs");
|
|
46
|
+
const httpError_1 = require("../errors/httpError");
|
|
47
|
+
const validationError_1 = require("../errors/validationError");
|
|
48
|
+
const schemas_1 = require("./schemas");
|
|
49
|
+
__exportStar(require("../errors/httpError"), exports);
|
|
50
|
+
__exportStar(require("../errors/validationError"), exports);
|
|
51
|
+
const defaultConf = {
|
|
52
|
+
base_url: undefined,
|
|
53
|
+
token_url: "https://auth.42.fr/auth/realms/staff-42/protocol/openid-connect/token",
|
|
54
|
+
maxRetry: 5,
|
|
55
|
+
logLine: true,
|
|
56
|
+
errLogBody: true,
|
|
57
|
+
};
|
|
58
|
+
class FortytwoIntraV3Client {
|
|
59
|
+
credentials;
|
|
60
|
+
base_url;
|
|
61
|
+
token_url;
|
|
62
|
+
axiosInstance;
|
|
63
|
+
retryOn;
|
|
64
|
+
maxRetry;
|
|
65
|
+
logLine;
|
|
66
|
+
errLogBody;
|
|
67
|
+
authorization;
|
|
68
|
+
tokenRequest = null;
|
|
69
|
+
constructor(credentials, conf = {}) {
|
|
70
|
+
this.credentials = credentials;
|
|
71
|
+
const config = { ...defaultConf, ...conf };
|
|
72
|
+
this.base_url = config.base_url;
|
|
73
|
+
this.token_url = config.token_url;
|
|
74
|
+
this.axiosInstance = axios_1.default.create();
|
|
75
|
+
this.retryOn = [401, 429, 500];
|
|
76
|
+
this.maxRetry = config.maxRetry;
|
|
77
|
+
this.logLine = config.logLine;
|
|
78
|
+
this.errLogBody = config.errLogBody;
|
|
79
|
+
this.authorization = null;
|
|
80
|
+
}
|
|
81
|
+
async generateToken() {
|
|
82
|
+
const data = this.authorization && this.isRefreshTokenValid()
|
|
83
|
+
? new URLSearchParams({
|
|
84
|
+
grant_type: "refresh_token",
|
|
85
|
+
refresh_token: this.authorization.refresh_token,
|
|
86
|
+
})
|
|
87
|
+
: new URLSearchParams({
|
|
88
|
+
grant_type: "password",
|
|
89
|
+
username: this.credentials.username,
|
|
90
|
+
password: this.credentials.password,
|
|
91
|
+
});
|
|
92
|
+
const res = await this.axiosInstance.post(this.token_url, data, {
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: `Basic ${Buffer.from(`${this.credentials.client_id}:${this.credentials.client_secret}`).toString("base64")}`,
|
|
95
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
return schemas_1.tokenSchema.parse(res.data);
|
|
99
|
+
}
|
|
100
|
+
isTokenValid() {
|
|
101
|
+
return this.authorization !== null && this.authorization.expires_at - 30000 > Date.now();
|
|
102
|
+
}
|
|
103
|
+
isRefreshTokenValid() {
|
|
104
|
+
return (this.authorization !== null && this.authorization.refresh_expires_at - 30000 > Date.now());
|
|
105
|
+
}
|
|
106
|
+
async getAccessToken() {
|
|
107
|
+
if (!this.isTokenValid()) {
|
|
108
|
+
// Share a single in-flight token request between concurrent callers
|
|
109
|
+
this.tokenRequest ??= this.generateToken().finally(() => {
|
|
110
|
+
this.tokenRequest = null;
|
|
111
|
+
});
|
|
112
|
+
this.authorization = await this.tokenRequest;
|
|
113
|
+
}
|
|
114
|
+
return this.authorization.access_token;
|
|
115
|
+
}
|
|
116
|
+
async fetch(url, options) {
|
|
117
|
+
const { method, body, query } = options;
|
|
118
|
+
// Attach access_token
|
|
119
|
+
const accessToken = await this.getAccessToken();
|
|
120
|
+
// Extract query parameters from URL and combine with options.query
|
|
121
|
+
const urlParams = {};
|
|
122
|
+
url.searchParams.forEach((value, key) => {
|
|
123
|
+
urlParams[key] = value;
|
|
124
|
+
});
|
|
125
|
+
// Combine URL params with query params (query params take precedence)
|
|
126
|
+
const combinedParams = { ...urlParams, ...query };
|
|
127
|
+
// Create clean URL without query parameters
|
|
128
|
+
const cleanUrl = new URL(url);
|
|
129
|
+
cleanUrl.search = "";
|
|
130
|
+
return this.axiosInstance.request({
|
|
131
|
+
method: method.toLowerCase(),
|
|
132
|
+
url: cleanUrl.toString(),
|
|
133
|
+
headers: {
|
|
134
|
+
Authorization: `Bearer ${accessToken}`,
|
|
135
|
+
},
|
|
136
|
+
params: combinedParams,
|
|
137
|
+
data: body ? body : undefined,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async reqHandler(url, options) {
|
|
141
|
+
try {
|
|
142
|
+
const res = await this.fetch(url, options);
|
|
143
|
+
this.logSuccess(res, options);
|
|
144
|
+
return res;
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if ((0, axios_1.isAxiosError)(err)) {
|
|
148
|
+
this.logError(err, options);
|
|
149
|
+
const { attempt, maxRetry } = options;
|
|
150
|
+
const status = err.response?.status;
|
|
151
|
+
if (maxRetry > 0 && attempt < maxRetry && status && this.retryOn.includes(status)) {
|
|
152
|
+
if (status === 401) {
|
|
153
|
+
this.authorization = null;
|
|
154
|
+
}
|
|
155
|
+
options.attempt++;
|
|
156
|
+
return this.reqHandler(url, options);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
throw new httpError_1.FortytwoIntraClientHttpError(err);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
logSuccess(res, options) {
|
|
168
|
+
if (!options.logLine)
|
|
169
|
+
return;
|
|
170
|
+
const line = (0, logs_1.getLogLine)(res, options);
|
|
171
|
+
console.log(line);
|
|
172
|
+
}
|
|
173
|
+
logError(err, options) {
|
|
174
|
+
if (!options.logLine)
|
|
175
|
+
return;
|
|
176
|
+
const line = (0, logs_1.getErrorLogLine)(err, options);
|
|
177
|
+
const body = err.response?.data;
|
|
178
|
+
const logBody = options.errLogBody && body && Object.keys(body).length;
|
|
179
|
+
try {
|
|
180
|
+
console.log(line, logBody ? JSON.stringify(body, null, 2) : "");
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
console.log(line, logBody ? body : "");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
validate(data, schema) {
|
|
187
|
+
if (!schema) {
|
|
188
|
+
return data;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
return schema.parse(data);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
if (err instanceof zod_1.default.ZodError) {
|
|
195
|
+
throw new validationError_1.FortytwoIntraClientValidationError(err, data);
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async get(endpoint, options = {}) {
|
|
201
|
+
if (endpoint instanceof URL === false) {
|
|
202
|
+
endpoint = new URL(endpoint, this.base_url);
|
|
203
|
+
}
|
|
204
|
+
const res = await this.reqHandler(endpoint, {
|
|
205
|
+
method: "GET",
|
|
206
|
+
attempt: 0,
|
|
207
|
+
maxRetry: this.maxRetry,
|
|
208
|
+
logLine: this.logLine,
|
|
209
|
+
errLogBody: this.errLogBody,
|
|
210
|
+
...options,
|
|
211
|
+
});
|
|
212
|
+
return this.validate(res.data, options.schema);
|
|
213
|
+
}
|
|
214
|
+
async post(endpoint, options = {}) {
|
|
215
|
+
if (endpoint instanceof URL === false) {
|
|
216
|
+
endpoint = new URL(endpoint, this.base_url);
|
|
217
|
+
}
|
|
218
|
+
const res = await this.reqHandler(endpoint, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
attempt: 0,
|
|
221
|
+
maxRetry: this.maxRetry,
|
|
222
|
+
logLine: this.logLine,
|
|
223
|
+
errLogBody: this.errLogBody,
|
|
224
|
+
...options,
|
|
225
|
+
});
|
|
226
|
+
return this.validate(res.data, options.schema);
|
|
227
|
+
}
|
|
228
|
+
async put(endpoint, options = {}) {
|
|
229
|
+
if (endpoint instanceof URL === false) {
|
|
230
|
+
endpoint = new URL(endpoint, this.base_url);
|
|
231
|
+
}
|
|
232
|
+
const res = await this.reqHandler(endpoint, {
|
|
233
|
+
method: "PUT",
|
|
234
|
+
attempt: 0,
|
|
235
|
+
maxRetry: this.maxRetry,
|
|
236
|
+
logLine: this.logLine,
|
|
237
|
+
errLogBody: this.errLogBody,
|
|
238
|
+
...options,
|
|
239
|
+
});
|
|
240
|
+
return this.validate(res.data, options.schema);
|
|
241
|
+
}
|
|
242
|
+
async patch(endpoint, options = {}) {
|
|
243
|
+
if (endpoint instanceof URL === false) {
|
|
244
|
+
endpoint = new URL(endpoint, this.base_url);
|
|
245
|
+
}
|
|
246
|
+
const res = await this.reqHandler(endpoint, {
|
|
247
|
+
method: "PATCH",
|
|
248
|
+
attempt: 0,
|
|
249
|
+
maxRetry: this.maxRetry,
|
|
250
|
+
logLine: this.logLine,
|
|
251
|
+
errLogBody: this.errLogBody,
|
|
252
|
+
...options,
|
|
253
|
+
});
|
|
254
|
+
return this.validate(res.data, options.schema);
|
|
255
|
+
}
|
|
256
|
+
async delete(endpoint, options = {}) {
|
|
257
|
+
if (endpoint instanceof URL === false) {
|
|
258
|
+
endpoint = new URL(endpoint, this.base_url);
|
|
259
|
+
}
|
|
260
|
+
const res = await this.reqHandler(endpoint, {
|
|
261
|
+
method: "DELETE",
|
|
262
|
+
attempt: 0,
|
|
263
|
+
maxRetry: this.maxRetry,
|
|
264
|
+
logLine: this.logLine,
|
|
265
|
+
errLogBody: this.errLogBody,
|
|
266
|
+
...options,
|
|
267
|
+
});
|
|
268
|
+
return this.validate(res.data, options.schema);
|
|
269
|
+
}
|
|
270
|
+
async rawRequest(config) {
|
|
271
|
+
const accessToken = await this.getAccessToken();
|
|
272
|
+
return this.axiosInstance.request({
|
|
273
|
+
baseURL: this.base_url,
|
|
274
|
+
...config,
|
|
275
|
+
headers: {
|
|
276
|
+
...config.headers,
|
|
277
|
+
Authorization: `Bearer ${accessToken}`,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
URL(endpoint) {
|
|
282
|
+
return new URL(endpoint, this.base_url);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
exports.FortytwoIntraV3Client = FortytwoIntraV3Client;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export declare const tokenSchema: z.ZodPipe<z.ZodObject<{
|
|
3
|
+
access_token: z.ZodString;
|
|
4
|
+
expires_in: z.ZodNumber;
|
|
5
|
+
refresh_expires_in: z.ZodNumber;
|
|
6
|
+
refresh_token: z.ZodString;
|
|
7
|
+
token_type: z.ZodString;
|
|
8
|
+
"not-before-policy": z.ZodNumber;
|
|
9
|
+
session_state: z.ZodString;
|
|
10
|
+
scope: z.ZodString;
|
|
11
|
+
}, z.core.$strict>, z.ZodTransform<{
|
|
12
|
+
access_token: string;
|
|
13
|
+
expires_at: number;
|
|
14
|
+
refresh_token: string;
|
|
15
|
+
refresh_expires_at: number;
|
|
16
|
+
token_type: string;
|
|
17
|
+
}, {
|
|
18
|
+
access_token: string;
|
|
19
|
+
expires_in: number;
|
|
20
|
+
refresh_expires_in: number;
|
|
21
|
+
refresh_token: string;
|
|
22
|
+
token_type: string;
|
|
23
|
+
"not-before-policy": number;
|
|
24
|
+
session_state: string;
|
|
25
|
+
scope: string;
|
|
26
|
+
}>>;
|
|
27
|
+
export type Token = z.infer<typeof tokenSchema>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.tokenSchema = void 0;
|
|
7
|
+
const zod_1 = __importDefault(require("zod"));
|
|
8
|
+
exports.tokenSchema = zod_1.default
|
|
9
|
+
.strictObject({
|
|
10
|
+
access_token: zod_1.default.string(),
|
|
11
|
+
expires_in: zod_1.default.number(),
|
|
12
|
+
refresh_expires_in: zod_1.default.number(),
|
|
13
|
+
refresh_token: zod_1.default.string(),
|
|
14
|
+
token_type: zod_1.default.string(),
|
|
15
|
+
"not-before-policy": zod_1.default.number(),
|
|
16
|
+
session_state: zod_1.default.string(),
|
|
17
|
+
scope: zod_1.default.string(),
|
|
18
|
+
})
|
|
19
|
+
.transform((obj) => {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
return {
|
|
22
|
+
access_token: obj.access_token,
|
|
23
|
+
expires_at: now + obj.expires_in * 1000,
|
|
24
|
+
refresh_token: obj.refresh_token,
|
|
25
|
+
refresh_expires_at: now + obj.refresh_expires_in * 1000,
|
|
26
|
+
token_type: obj.token_type,
|
|
27
|
+
};
|
|
28
|
+
});
|