@autofleet/api-http-client 0.0.0-beta--beta0.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 +115 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @autofleet/api-http-client
|
|
2
|
+
|
|
3
|
+
HTTP client for the Autofleet API, intended for use from primarily from cloud functions.
|
|
4
|
+
|
|
5
|
+
Wraps [`@autofleet/rapido-http-client`](../rapido-http-client) and handles authentication transparently: the refresh token is read from Google Secret Manager, exchanged for an access token, and attached as a `Bearer` header on every outgoing request. On a `401` response the token is refreshed once and the request is retried.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @autofleet/api-http-client
|
|
11
|
+
# or
|
|
12
|
+
npm install @autofleet/api-http-client
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`zod` is an optional peer dependency, required only when using request-level schema validation.
|
|
16
|
+
|
|
17
|
+
## Initial setup
|
|
18
|
+
|
|
19
|
+
Before the client can issue authenticated requests, you need to seed a refresh token into Google Secret Manager. This is a one-time, manual step per environment.
|
|
20
|
+
|
|
21
|
+
1. **Obtain the first refresh token.** Either:
|
|
22
|
+
- Download a service-account credentials JSON file from the Autofleet console and use it to mint a refresh token, **or**
|
|
23
|
+
- Call the login API directly with valid user credentials and grab the `refreshToken` from the response:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl -X POST "$AUTOFLEET_API_GW/api/v1/login" \
|
|
27
|
+
-H 'Content-Type: application/json' \
|
|
28
|
+
-d '{"email":"<email>","password":"<password>"}'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. **Store the refresh token in GCP Secret Manager.** Create a new secret in the GCP project the cloud function runs in (the active `GCLOUD_PROJECT`) and add the refresh token as the first version:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gcloud secrets create MY_REFRESH_TOKEN \
|
|
35
|
+
--project=<gcp-project> \
|
|
36
|
+
--replication-policy=automatic
|
|
37
|
+
|
|
38
|
+
printf '%s' '<refresh-token>' | gcloud secrets versions add MY_REFRESH_TOKEN \
|
|
39
|
+
--project=<gcp-project> \
|
|
40
|
+
--data-file=-
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Make sure the runtime service account has the `roles/secretmanager.secretAccessor` role on the secret, plus `roles/secretmanager.secretVersionAdder` if you want the client to write back rotated tokens.
|
|
44
|
+
|
|
45
|
+
3. **Set the environment variables** the client reads:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
AUTOFLEET_API_GW=https://api.<env>.<region>.autofleet.io
|
|
49
|
+
AUTOFLEET_REFRESH_TOKEN_SECRET=projects/<gcp-project>/secrets/MY_REFRESH_TOKEN
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`AUTOFLEET_REFRESH_TOKEN_SECRET` must be the fully-qualified secret resource name (without `/versions/...` — the client always reads `versions/latest`).
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { AutofleetApiHttpClient } from '@autofleet/api-http-client';
|
|
58
|
+
import createLogger from '@autofleet/logger';
|
|
59
|
+
|
|
60
|
+
const logger = createLogger('info', { serviceName: 'my-cloud-function' });
|
|
61
|
+
|
|
62
|
+
const autofleetApi = new AutofleetApiHttpClient({
|
|
63
|
+
logger,
|
|
64
|
+
apiGatewayUrl: process.env.AUTOFLEET_API_GW!,
|
|
65
|
+
refreshTokenSecretPath: process.env.AUTOFLEET_REFRESH_TOKEN_SECRET!,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const { data: vehicle } = await autofleetApi.get<Vehicle>(`/api/v1/vehicles/${vehicleId}`);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Settings
|
|
72
|
+
|
|
73
|
+
`AutofleetApiHttpClientSettings` extends [`RapidoHttpClientSettings`](../rapido-http-client/src/index.ts) — minus `serviceName` / `serviceUrl`, which are derived from `apiGatewayUrl` — and adds two required fields:
|
|
74
|
+
|
|
75
|
+
| Field | Type | Description |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `apiGatewayUrl` | `string` | Base URL of the Autofleet API gateway (used as the rapido `serviceUrl` and as the host for the `/api/v1/login/refresh` call). |
|
|
78
|
+
| `refreshTokenSecretPath` | `string` | Fully-qualified Google Secret Manager resource name holding the refresh token (e.g. `projects/<project>/secrets/<name>`). When the API rotates the refresh token, the new value is written back as a new secret version. |
|
|
79
|
+
|
|
80
|
+
All other rapido settings — `logger`, `timeout`, `retries`, `headers`, `cache`, `validateStatus`, etc. — are forwarded through. The default `validateStatus` permits `2xx` and `401` so the 401-retry logic can run; pass your own to override.
|
|
81
|
+
|
|
82
|
+
## HTTP methods
|
|
83
|
+
|
|
84
|
+
`get`, `post`, `put`, `patch`, and `delete` mirror rapido's signatures and overloads. Without a schema you get back `RapidoHttpClientResponse<T>` with `T` inferred from the generic:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const { data } = await autofleetApi.post<{ rows: Vehicle[]; count: number; }>(
|
|
88
|
+
'/api/v1/vehicles/query',
|
|
89
|
+
{ page: 1, perPage: 50, query: { fleetId } },
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
With `successSchema` (and optionally `errorSchema`) the response data is parsed and typed as `z.output<typeof schema>`:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import * as z from 'zod';
|
|
97
|
+
|
|
98
|
+
const vehicleSchema = z.strictObject({
|
|
99
|
+
id: z.uuid(),
|
|
100
|
+
externalId: z.string(),
|
|
101
|
+
fleetId: z.uuid(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const { data } = await autofleetApi.get(`/api/v1/vehicles/${id}`, {
|
|
105
|
+
successSchema: vehicleSchema,
|
|
106
|
+
});
|
|
107
|
+
// data is z.output<typeof vehicleSchema>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Token refresh
|
|
111
|
+
|
|
112
|
+
- The first request triggers a refresh against `<apiGatewayUrl>/api/v1/login/refresh` using the secret at `refreshTokenSecretPath`.
|
|
113
|
+
- Concurrent requests during a refresh wait on a single shared promise — no thundering herd.
|
|
114
|
+
- On `401`, the token is refreshed and the original request is retried exactly once.
|
|
115
|
+
- If the refresh response includes a rotated `refreshToken`, it is written back as a new secret version via `addSecretVersion`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { RapidoHttpClientResponse, RapidoHttpClientSettings, RequestConfig } from "@autofleet/rapido-http-client";
|
|
2
|
+
import { ZodType, output } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/client.d.ts
|
|
5
|
+
type NoSchemaConfig = Omit<RequestConfig, "successSchema" | "errorSchema"> & {
|
|
6
|
+
successSchema?: never;
|
|
7
|
+
errorSchema?: never;
|
|
8
|
+
};
|
|
9
|
+
interface AutofleetApiHttpClientSettings extends Omit<RapidoHttpClientSettings, "serviceName" | "serviceUrl"> {
|
|
10
|
+
apiGatewayUrl: string;
|
|
11
|
+
refreshTokenSecretPath: string;
|
|
12
|
+
}
|
|
13
|
+
declare class AutofleetApiHttpClient {
|
|
14
|
+
private accessToken;
|
|
15
|
+
private refreshPromise;
|
|
16
|
+
private readonly client;
|
|
17
|
+
private readonly logger;
|
|
18
|
+
private readonly refreshTokenSecretPath;
|
|
19
|
+
private readonly secretManager;
|
|
20
|
+
static validateStatus(status: number): boolean;
|
|
21
|
+
constructor({
|
|
22
|
+
apiGatewayUrl,
|
|
23
|
+
refreshTokenSecretPath,
|
|
24
|
+
...rapidoSettings
|
|
25
|
+
}: AutofleetApiHttpClientSettings);
|
|
26
|
+
get<T = unknown>(url: string, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;
|
|
27
|
+
get<TSuccess extends ZodType>(url: string, config: RequestConfig & {
|
|
28
|
+
successSchema: TSuccess;
|
|
29
|
+
errorSchema?: never;
|
|
30
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
31
|
+
get<TSuccess extends ZodType>(url: string, config: RequestConfig & {
|
|
32
|
+
successSchema: TSuccess;
|
|
33
|
+
errorSchema?: ZodType;
|
|
34
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
35
|
+
post<T = unknown>(url: string, body?: unknown, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;
|
|
36
|
+
post<TSuccess extends ZodType>(url: string, body: unknown, config: RequestConfig & {
|
|
37
|
+
successSchema: TSuccess;
|
|
38
|
+
errorSchema?: never;
|
|
39
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
40
|
+
post<TSuccess extends ZodType>(url: string, body: unknown, config: RequestConfig & {
|
|
41
|
+
successSchema: TSuccess;
|
|
42
|
+
errorSchema?: ZodType;
|
|
43
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
44
|
+
put<T = unknown>(url: string, body?: unknown, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;
|
|
45
|
+
put<TSuccess extends ZodType>(url: string, body: unknown, config: RequestConfig & {
|
|
46
|
+
successSchema: TSuccess;
|
|
47
|
+
errorSchema?: never;
|
|
48
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
49
|
+
put<TSuccess extends ZodType>(url: string, body: unknown, config: RequestConfig & {
|
|
50
|
+
successSchema: TSuccess;
|
|
51
|
+
errorSchema?: ZodType;
|
|
52
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
53
|
+
patch<T = unknown>(url: string, body?: unknown, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;
|
|
54
|
+
patch<TSuccess extends ZodType>(url: string, body: unknown, config: RequestConfig & {
|
|
55
|
+
successSchema: TSuccess;
|
|
56
|
+
errorSchema?: never;
|
|
57
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
58
|
+
patch<TSuccess extends ZodType>(url: string, body: unknown, config: RequestConfig & {
|
|
59
|
+
successSchema: TSuccess;
|
|
60
|
+
errorSchema?: ZodType;
|
|
61
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
62
|
+
delete<T = unknown>(url: string, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;
|
|
63
|
+
delete<TSuccess extends ZodType>(url: string, config: RequestConfig & {
|
|
64
|
+
successSchema: TSuccess;
|
|
65
|
+
errorSchema?: never;
|
|
66
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
67
|
+
delete<TSuccess extends ZodType>(url: string, config: RequestConfig & {
|
|
68
|
+
successSchema: TSuccess;
|
|
69
|
+
errorSchema?: ZodType;
|
|
70
|
+
}): Promise<RapidoHttpClientResponse<output<TSuccess>>>;
|
|
71
|
+
private sendWithAuth;
|
|
72
|
+
private injectAuthHeader;
|
|
73
|
+
private refreshToken;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { AutofleetApiHttpClient, AutofleetApiHttpClientSettings };
|
|
77
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{RapidoHttpClient as e}from"@autofleet/rapido-http-client";import{SecretManagerServiceClient as t}from"@google-cloud/secret-manager";var n=class{logger;constructor(e){this.logger=e}},r=class extends n{client=new t;async getSecretValue(e){try{let[t]=await this.client.accessSecretVersion({name:`${e}/versions/latest`});return t.payload?.data?t.payload.data.toString():(this.logger.error(`secret payload data is empty`,{name:e}),null)}catch(e){return this.logger.error(`error when accessing secret`,{error:e}),null}}async updateSecretValue(e,t){try{await this.client.addSecretVersion({parent:e,payload:{data:Buffer.from(t,`utf8`)}})}catch(e){this.logger.error(`error when updating secret`,{error:e})}}},i=class t{accessToken=null;refreshPromise=null;client;logger;refreshTokenSecretPath;secretManager;static validateStatus(e){return e>=200&&e<300||e===401}constructor({apiGatewayUrl:n,refreshTokenSecretPath:i,...a}){this.refreshTokenSecretPath=i,this.logger=a.logger,this.secretManager=new r(this.logger),this.client=new e({...a,serviceUrl:n,validateStatus:a.validateStatus??t.validateStatus})}get(e,t){return this.sendWithAuth(t=>this.client.get(e,t),t)}post(e,t,n){return this.sendWithAuth(n=>this.client.post(e,t,n),n)}put(e,t,n){return this.sendWithAuth(n=>this.client.put(e,t,n),n)}patch(e,t,n){return this.sendWithAuth(n=>this.client.patch(e,t,n),n)}delete(e,t){return this.sendWithAuth(t=>this.client.delete(e,t),t)}async sendWithAuth(e,t){this.accessToken||await this.refreshToken();let n=await e(this.injectAuthHeader(t));return n.status===401?(await this.refreshToken(),e(this.injectAuthHeader(t))):n}injectAuthHeader(e){return this.accessToken?{...e,headers:{...e?.headers,Authorization:`Bearer ${this.accessToken}`}}:e??{}}refreshToken(){return this.refreshPromise||=(async()=>{try{this.logger.info(`Refreshing token for AutofleetApi...`);let e=await this.secretManager.getSecretValue(this.refreshTokenSecretPath),t=await this.client.post(`/api/v1/login/refresh`,{refreshToken:e});if(t.status!==200||!t.data?.token)throw Error(`Token refresh failed: status ${t.status}`);this.accessToken=t.data.token,t.data.refreshToken&&await this.secretManager.updateSecretValue(this.refreshTokenSecretPath,t.data.refreshToken)}catch(e){this.logger.error(`Error refreshing token for AutofleetApi`,{error:e}),this.accessToken=null}finally{this.refreshPromise=null}})(),this.refreshPromise}};export{i as AutofleetApiHttpClient};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/secret-managers/secret-manager.ts","../src/secret-managers/gcp.ts","../src/client.ts"],"sourcesContent":["import type { LoggerInstanceManager } from '@autofleet/logger';\n\nexport abstract class SecretManager {\n protected readonly logger: LoggerInstanceManager;\n\n constructor(logger: LoggerInstanceManager) {\n this.logger = logger;\n }\n\n abstract getSecretValue(name: string): Promise<string | null>;\n abstract updateSecretValue(name: string, value: string): Promise<void>;\n}\n","import { SecretManagerServiceClient } from '@google-cloud/secret-manager';\nimport { SecretManager } from './secret-manager';\n\nexport class GcpSecretManager extends SecretManager {\n private readonly client = new SecretManagerServiceClient();\n\n async getSecretValue(name: string): Promise<string | null> {\n try {\n const [secret] = await this.client.accessSecretVersion({\n name: `${name}/versions/latest`,\n });\n if (!secret.payload?.data) {\n this.logger.error('secret payload data is empty', { name });\n return null;\n }\n return secret.payload.data.toString();\n } catch (error) {\n this.logger.error('error when accessing secret', { error });\n return null;\n }\n }\n\n async updateSecretValue(name: string, value: string): Promise<void> {\n try {\n await this.client.addSecretVersion({\n parent: name,\n payload: {\n data: Buffer.from(value, 'utf8'),\n },\n });\n } catch (error) {\n this.logger.error('error when updating secret', { error });\n }\n }\n}\n","import {\n RapidoHttpClient,\n type RapidoHttpClientResponse,\n type RapidoHttpClientSettings,\n type RequestConfig,\n} from '@autofleet/rapido-http-client';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport type { ZodType, output } from 'zod';\nimport { GcpSecretManager, type SecretManager } from './secret-managers';\n\ninterface RefreshTokenResponse {\n token: string;\n refreshToken: string;\n}\n\ntype NoSchemaConfig = Omit<RequestConfig, 'successSchema' | 'errorSchema'> & {\n successSchema?: never;\n errorSchema?: never;\n};\n\nexport interface AutofleetApiHttpClientSettings\n extends Omit<RapidoHttpClientSettings, 'serviceName' | 'serviceUrl'> {\n apiGatewayUrl: string;\n refreshTokenSecretPath: string;\n}\n\nexport class AutofleetApiHttpClient {\n private accessToken: string | null = null;\n\n private refreshPromise: Promise<void> | null = null;\n\n private readonly client: RapidoHttpClient;\n\n private readonly logger: LoggerInstanceManager;\n\n private readonly refreshTokenSecretPath: string;\n\n private readonly secretManager: SecretManager;\n\n static validateStatus(status: number): boolean {\n return (status >= 200 && status < 300) || status === 401;\n }\n\n constructor({\n apiGatewayUrl,\n refreshTokenSecretPath,\n ...rapidoSettings\n }: AutofleetApiHttpClientSettings) {\n this.refreshTokenSecretPath = refreshTokenSecretPath;\n this.logger = rapidoSettings.logger;\n this.secretManager = new GcpSecretManager(this.logger);\n this.client = new RapidoHttpClient({\n ...rapidoSettings,\n serviceUrl: apiGatewayUrl,\n validateStatus: rapidoSettings.validateStatus ?? AutofleetApiHttpClient.validateStatus,\n });\n }\n\n get<T = unknown>(url: string, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;\n get<TSuccess extends ZodType>(\n url: string,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: never; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n get<TSuccess extends ZodType>(\n url: string,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: ZodType; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n get<T = unknown>(url: string, config?: RequestConfig): Promise<RapidoHttpClientResponse<T>> {\n return this.sendWithAuth(c => this.client.get<T>(url, c as NoSchemaConfig), config);\n }\n\n post<T = unknown>(url: string, body?: unknown, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;\n post<TSuccess extends ZodType>(\n url: string,\n body: unknown,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: never; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n post<TSuccess extends ZodType>(\n url: string,\n body: unknown,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: ZodType; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n post<T = unknown>(url: string, body?: unknown, config?: RequestConfig): Promise<RapidoHttpClientResponse<T>> {\n return this.sendWithAuth(c => this.client.post<T>(url, body, c as NoSchemaConfig), config);\n }\n\n put<T = unknown>(url: string, body?: unknown, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;\n put<TSuccess extends ZodType>(\n url: string,\n body: unknown,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: never; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n put<TSuccess extends ZodType>(\n url: string,\n body: unknown,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: ZodType; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n put<T = unknown>(url: string, body?: unknown, config?: RequestConfig): Promise<RapidoHttpClientResponse<T>> {\n return this.sendWithAuth(c => this.client.put<T>(url, body, c as NoSchemaConfig), config);\n }\n\n patch<T = unknown>(url: string, body?: unknown, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;\n patch<TSuccess extends ZodType>(\n url: string,\n body: unknown,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: never; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n patch<TSuccess extends ZodType>(\n url: string,\n body: unknown,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: ZodType; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n patch<T = unknown>(url: string, body?: unknown, config?: RequestConfig): Promise<RapidoHttpClientResponse<T>> {\n return this.sendWithAuth(c => this.client.patch<T>(url, body, c as NoSchemaConfig), config);\n }\n\n delete<T = unknown>(url: string, config?: NoSchemaConfig): Promise<RapidoHttpClientResponse<T>>;\n delete<TSuccess extends ZodType>(\n url: string,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: never; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n delete<TSuccess extends ZodType>(\n url: string,\n config: RequestConfig & { successSchema: TSuccess; errorSchema?: ZodType; }\n ): Promise<RapidoHttpClientResponse<output<TSuccess>>>;\n delete<T = unknown>(url: string, config?: RequestConfig): Promise<RapidoHttpClientResponse<T>> {\n return this.sendWithAuth(c => this.client.delete<T>(url, c as NoSchemaConfig), config);\n }\n\n private async sendWithAuth<T>(\n request: (config: RequestConfig) => Promise<RapidoHttpClientResponse<T>>,\n config?: RequestConfig,\n ): Promise<RapidoHttpClientResponse<T>> {\n if (!this.accessToken) {\n await this.refreshToken();\n }\n const response = await request(this.injectAuthHeader(config));\n if (response.status === 401) {\n await this.refreshToken();\n return request(this.injectAuthHeader(config));\n }\n return response;\n }\n\n private injectAuthHeader(config?: RequestConfig): RequestConfig {\n if (!this.accessToken) {\n return config ?? {};\n }\n return {\n ...config,\n headers: {\n ...config?.headers,\n Authorization: `Bearer ${this.accessToken}`,\n },\n };\n }\n\n private refreshToken(): Promise<void> {\n if (this.refreshPromise) {\n return this.refreshPromise;\n }\n\n this.refreshPromise = (async () => {\n try {\n this.logger.info('Refreshing token for AutofleetApi...');\n const refreshToken = await this.secretManager.getSecretValue(this.refreshTokenSecretPath);\n const response = await this.client.post<RefreshTokenResponse>(\n '/api/v1/login/refresh',\n { refreshToken },\n );\n if (response.status !== 200 || !response.data?.token) {\n throw new Error(`Token refresh failed: status ${response.status}`);\n }\n this.accessToken = response.data.token;\n if (response.data.refreshToken) {\n await this.secretManager.updateSecretValue(this.refreshTokenSecretPath, response.data.refreshToken);\n }\n } catch (error) {\n this.logger.error('Error refreshing token for AutofleetApi', { error });\n this.accessToken = null;\n } finally {\n this.refreshPromise = null;\n }\n })();\n\n return this.refreshPromise;\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@autofleet/api-http-client",
|
|
3
|
+
"version": "0.0.0-beta--beta0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "Or Mizrahi",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=24"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@google-cloud/secret-manager": "^6.1.1",
|
|
24
|
+
"@autofleet/rapido-http-client": "0.3.12"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@autofleet/logger": ">=4.2.20",
|
|
28
|
+
"zod": ">=4.2"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"zod": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"zod": "^4.2.1",
|
|
37
|
+
"@autofleet/logger": "^4.5.1"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"test": "vitest",
|
|
41
|
+
"coverage": "vitest run --coverage",
|
|
42
|
+
"build": "pnpm -w tsdown -W ./packages/api-http-client"
|
|
43
|
+
}
|
|
44
|
+
}
|