@visin/frontend-core 0.1.1
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 +33 -0
- package/dist/apiClient.d.ts +37 -0
- package/dist/apiClient.d.ts.map +1 -0
- package/dist/apiClient.js +59 -0
- package/dist/apiClient.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @visin/frontend-core
|
|
2
|
+
|
|
3
|
+
Shared frontend auth/API-client logic for Visin frontends.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @visin/frontend-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createApiClient } from '@visin/frontend-core';
|
|
15
|
+
|
|
16
|
+
export const api = createApiClient({
|
|
17
|
+
baseUrl: () => getGlobalConfig().VISION_API_URL,
|
|
18
|
+
getToken: () => localStorage.getItem('authToken'),
|
|
19
|
+
onUnauthorized: () => { window.location.href = '/login'; }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const { id } = await api.get('/projects/123');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Status
|
|
26
|
+
|
|
27
|
+
Only `createApiClient` is extracted so far — it matches the Bearer-token-from-
|
|
28
|
+
`localStorage` transport already used independently in `account-front` and
|
|
29
|
+
`vision-front`. Auth state (login/logout/session) is **not** unified yet:
|
|
30
|
+
`account-front`, `auth-front`, and `vision-front` each implement it differently
|
|
31
|
+
(`authService.ts`, `authFlow.ts`, `AuthContext.tsx`) rather than as copies of one
|
|
32
|
+
pattern, so that needs its own design pass before landing here. See the repo's
|
|
33
|
+
`TODO.md`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface ApiClientOptions {
|
|
2
|
+
/** Read fresh at call time (app config may not be resolved yet at module load). */
|
|
3
|
+
baseUrl: () => string;
|
|
4
|
+
/** Reads the current auth token, e.g. `() => localStorage.getItem('authToken')`. */
|
|
5
|
+
getToken?: () => string | null;
|
|
6
|
+
/** Called on a 401 response, e.g. to redirect to the login page. */
|
|
7
|
+
onUnauthorized?: () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface ApiRequestOptions extends RequestInit {
|
|
10
|
+
/** Skip the onUnauthorized callback for this call (e.g. an auth-check that expects 401 as a normal "not logged in" result, not a hard redirect). */
|
|
11
|
+
skipAuthRedirect?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class ApiError extends Error {
|
|
14
|
+
status: number;
|
|
15
|
+
body: unknown;
|
|
16
|
+
constructor(status: number, message: string, body?: unknown);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generic authenticated JSON fetch wrapper — base URL handling, Bearer-token
|
|
20
|
+
* auth, structured errors, and an opt-in 401 → redirect-to-auth hook.
|
|
21
|
+
*
|
|
22
|
+
* Matches the auth transport all four Visin services actually use today
|
|
23
|
+
* (a Bearer token read from `localStorage`, not a cookie), unlike a
|
|
24
|
+
* cookie-based client would. Each front currently hand-rolls this
|
|
25
|
+
* (`account-front/services/authService.ts`, `vision-front/config/visionApi.ts`)
|
|
26
|
+
* with near-identical logic; new call sites can adopt this instead.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createApiClient(options: ApiClientOptions): {
|
|
29
|
+
request: <T = unknown>(path: string, init?: ApiRequestOptions) => Promise<T>;
|
|
30
|
+
get: <T = unknown>(path: string, init?: ApiRequestOptions) => Promise<T>;
|
|
31
|
+
post: <T = unknown>(path: string, body?: unknown, init?: ApiRequestOptions) => Promise<T>;
|
|
32
|
+
put: <T = unknown>(path: string, body?: unknown, init?: ApiRequestOptions) => Promise<T>;
|
|
33
|
+
patch: <T = unknown>(path: string, body?: unknown, init?: ApiRequestOptions) => Promise<T>;
|
|
34
|
+
delete: <T = unknown>(path: string, init?: ApiRequestOptions) => Promise<T>;
|
|
35
|
+
};
|
|
36
|
+
export type ApiClient = ReturnType<typeof createApiClient>;
|
|
37
|
+
//# sourceMappingURL=apiClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../src/apiClient.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,mFAAmF;IACnF,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC/B,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,oJAAoJ;IACpJ,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,qBAAa,QAAS,SAAQ,KAAK;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;gBAEF,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAM5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB;cAChC,CAAC,kBAAkB,MAAM,SAAQ,iBAAiB,KAAQ,OAAO,CAAC,CAAC,CAAC;UAoCnF,CAAC,kBAAkB,MAAM,SAAS,iBAAiB;WAClD,CAAC,kBAAkB,MAAM,SAAS,OAAO,SAAS,iBAAiB;UAEpE,CAAC,kBAAkB,MAAM,SAAS,OAAO,SAAS,iBAAiB;YAEjE,CAAC,kBAAkB,MAAM,SAAS,OAAO,SAAS,iBAAiB;aAElE,CAAC,kBAAkB,MAAM,SAAS,iBAAiB;EAE/D;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export class ApiError extends Error {
|
|
2
|
+
constructor(status, message, body) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'ApiError';
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.body = body;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Generic authenticated JSON fetch wrapper — base URL handling, Bearer-token
|
|
11
|
+
* auth, structured errors, and an opt-in 401 → redirect-to-auth hook.
|
|
12
|
+
*
|
|
13
|
+
* Matches the auth transport all four Visin services actually use today
|
|
14
|
+
* (a Bearer token read from `localStorage`, not a cookie), unlike a
|
|
15
|
+
* cookie-based client would. Each front currently hand-rolls this
|
|
16
|
+
* (`account-front/services/authService.ts`, `vision-front/config/visionApi.ts`)
|
|
17
|
+
* with near-identical logic; new call sites can adopt this instead.
|
|
18
|
+
*/
|
|
19
|
+
export function createApiClient(options) {
|
|
20
|
+
async function request(path, init = {}) {
|
|
21
|
+
const { skipAuthRedirect, headers, ...rest } = init;
|
|
22
|
+
const token = options.getToken?.();
|
|
23
|
+
const response = await fetch(`${options.baseUrl()}${path}`, {
|
|
24
|
+
...rest,
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
...(token && { Authorization: `Bearer ${token}` }),
|
|
28
|
+
...headers
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if (response.status === 401 && !skipAuthRedirect) {
|
|
32
|
+
options.onUnauthorized?.();
|
|
33
|
+
}
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
let body;
|
|
36
|
+
try {
|
|
37
|
+
body = await response.json();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// response wasn't JSON — leave body undefined
|
|
41
|
+
}
|
|
42
|
+
const message = body?.message ?? `HTTP error! status: ${response.status}`;
|
|
43
|
+
throw new ApiError(response.status, message, body);
|
|
44
|
+
}
|
|
45
|
+
if (response.status === 204) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return response.json();
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
request,
|
|
52
|
+
get: (path, init) => request(path, { ...init, method: 'GET' }),
|
|
53
|
+
post: (path, body, init) => request(path, { ...init, method: 'POST', body: body !== undefined ? JSON.stringify(body) : undefined }),
|
|
54
|
+
put: (path, body, init) => request(path, { ...init, method: 'PUT', body: body !== undefined ? JSON.stringify(body) : undefined }),
|
|
55
|
+
patch: (path, body, init) => request(path, { ...init, method: 'PATCH', body: body !== undefined ? JSON.stringify(body) : undefined }),
|
|
56
|
+
delete: (path, init) => request(path, { ...init, method: 'DELETE' })
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=apiClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.js","sourceRoot":"","sources":["../src/apiClient.ts"],"names":[],"mappings":"AAcA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAIjC,YAAY,MAAc,EAAE,OAAe,EAAE,IAAc;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,KAAK,UAAU,OAAO,CAAc,IAAY,EAAE,OAA0B,EAAE;QAC5E,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;QACpD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;QAEnC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,EAAE,EAAE;YAC1D,GAAG,IAAI;YACP,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,GAAG,CAAC,KAAK,IAAI,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC;gBAClD,GAAG,OAAO;aACX;SACF,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACjD,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,IAAa,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,8CAA8C;YAChD,CAAC;YACD,MAAM,OAAO,GAAI,IAAyC,EAAE,OAAO,IAAI,uBAAuB,QAAQ,CAAC,MAAM,EAAE,CAAC;YAChH,MAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,SAAc,CAAC;QACxB,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,OAAO;QACL,OAAO;QACP,GAAG,EAAE,CAAc,IAAY,EAAE,IAAwB,EAAE,EAAE,CAAC,OAAO,CAAI,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC1G,IAAI,EAAE,CAAc,IAAY,EAAE,IAAc,EAAE,IAAwB,EAAE,EAAE,CAC5E,OAAO,CAAI,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QAC5G,GAAG,EAAE,CAAc,IAAY,EAAE,IAAc,EAAE,IAAwB,EAAE,EAAE,CAC3E,OAAO,CAAI,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QAC3G,KAAK,EAAE,CAAc,IAAY,EAAE,IAAc,EAAE,IAAwB,EAAE,EAAE,CAC7E,OAAO,CAAI,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QAC7G,MAAM,EAAE,CAAc,IAAY,EAAE,IAAwB,EAAE,EAAE,CAAC,OAAO,CAAI,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KACjH,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @visin/frontend-core - Shared frontend auth/API-client logic for Visin frontends
|
|
2
|
+
export { createApiClient, ApiError } from './apiClient';
|
|
3
|
+
// Not yet extracted: account-front's `authService.ts`, auth-front's
|
|
4
|
+
// `authFlow.ts`, and vision-front's `AuthContext.tsx` implement auth
|
|
5
|
+
// (token storage, login/logout, session checks) three different ways
|
|
6
|
+
// rather than as copies of one pattern, so unifying them needs its own
|
|
7
|
+
// design pass across all three before landing here — see TODO.md.
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AAEnF,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGxD,oEAAoE;AACpE,qEAAqE;AACrE,qEAAqE;AACrE,uEAAuE;AACvE,kEAAkE"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@visin/frontend-core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Shared frontend auth/API-client logic for Visin frontends",
|
|
5
|
+
"author": "Toomas Tahves",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/visin-platform/visin-monorepo.git",
|
|
16
|
+
"directory": "libs/frontend-core"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"clean": "rm -rf dist",
|
|
21
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
22
|
+
"release": "standard-version && npm run build && npm publish --access public",
|
|
23
|
+
"release:beta": "standard-version --prerelease beta && npm run build && npm publish --access public --tag beta",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"lint": "eslint . --max-warnings 0",
|
|
27
|
+
"typecheck": "tsc --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"visin",
|
|
31
|
+
"frontend",
|
|
32
|
+
"shared",
|
|
33
|
+
"auth",
|
|
34
|
+
"api-client"
|
|
35
|
+
],
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@eslint/js": "^10.0.1",
|
|
38
|
+
"@types/node": "^26.0.1",
|
|
39
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
40
|
+
"eslint": "^10.6.0",
|
|
41
|
+
"jsdom": "^26.1.0",
|
|
42
|
+
"standard-version": "^9.5.0",
|
|
43
|
+
"typescript": "^6.0.3",
|
|
44
|
+
"typescript-eslint": "^8.62.1",
|
|
45
|
+
"vitest": "^3.2.4"
|
|
46
|
+
}
|
|
47
|
+
}
|