@ibdop/platform-kit 1.0.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.
Files changed (51) hide show
  1. package/README.md +63 -0
  2. package/dist/components/ErrorBoundary.d.ts +68 -0
  3. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  4. package/dist/components/Notification.d.ts +46 -0
  5. package/dist/components/Notification.d.ts.map +1 -0
  6. package/dist/components/VersionInfo.d.ts +18 -0
  7. package/dist/components/VersionInfo.d.ts.map +1 -0
  8. package/dist/components/index.d.ts +8 -0
  9. package/dist/components/index.d.ts.map +1 -0
  10. package/dist/hooks/index.d.ts +9 -0
  11. package/dist/hooks/index.d.ts.map +1 -0
  12. package/dist/hooks/useApi.d.ts +65 -0
  13. package/dist/hooks/useApi.d.ts.map +1 -0
  14. package/dist/hooks/useInfoData.d.ts +35 -0
  15. package/dist/hooks/useInfoData.d.ts.map +1 -0
  16. package/dist/hooks/usePermissions.d.ts +23 -0
  17. package/dist/hooks/usePermissions.d.ts.map +1 -0
  18. package/dist/hooks/useShellAuth.d.ts +23 -0
  19. package/dist/hooks/useShellAuth.d.ts.map +1 -0
  20. package/dist/hooks/useV1Config.d.ts +27 -0
  21. package/dist/hooks/useV1Config.d.ts.map +1 -0
  22. package/dist/index.cjs.js +18 -0
  23. package/dist/index.d.ts +10 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.es.js +1500 -0
  26. package/dist/index.umd.js +18 -0
  27. package/dist/services/api.d.ts +59 -0
  28. package/dist/services/api.d.ts.map +1 -0
  29. package/dist/services/index.d.ts +8 -0
  30. package/dist/services/index.d.ts.map +1 -0
  31. package/dist/services/logger.d.ts +41 -0
  32. package/dist/services/logger.d.ts.map +1 -0
  33. package/dist/types/index.d.ts +178 -0
  34. package/dist/types/index.d.ts.map +1 -0
  35. package/package.json +90 -0
  36. package/src/components/ErrorBoundary.tsx +292 -0
  37. package/src/components/Notification.tsx +297 -0
  38. package/src/components/VersionInfo.tsx +271 -0
  39. package/src/components/index.ts +14 -0
  40. package/src/global.d.ts +40 -0
  41. package/src/hooks/index.ts +13 -0
  42. package/src/hooks/useApi.ts +314 -0
  43. package/src/hooks/useInfoData.ts +124 -0
  44. package/src/hooks/usePermissions.ts +88 -0
  45. package/src/hooks/useShellAuth.ts +145 -0
  46. package/src/hooks/useV1Config.ts +112 -0
  47. package/src/index.ts +17 -0
  48. package/src/services/api.ts +290 -0
  49. package/src/services/index.ts +9 -0
  50. package/src/services/logger.ts +71 -0
  51. package/src/types/index.ts +215 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * API Service - Централизованный API клиент
3
+ *
4
+ * Предоставляет axios клиент с interceptors для auth и error handling
5
+ */
6
+
7
+ import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios'
8
+ import type { ApiError, ApiResponse } from '../types'
9
+
10
+ // MF Name for logging
11
+ const MF_NAME = 'platform-kit'
12
+
13
+ // Development mode flag
14
+ const isDev = import.meta.env?.DEV === true || import.meta.env?.MODE === 'development'
15
+
16
+ /**
17
+ * Logger for API
18
+ */
19
+ const logger = {
20
+ log: (...args: unknown[]) => {
21
+ if (isDev) console.log(`[${MF_NAME}]`, ...args)
22
+ },
23
+ warn: (...args: unknown[]) => {
24
+ if (isDev) console.warn(`[${MF_NAME}]`, ...args)
25
+ },
26
+ error: (...args: unknown[]) => {
27
+ console.error(`[${MF_NAME}]`, ...args)
28
+ },
29
+ info: (...args: unknown[]) => {
30
+ if (isDev) console.info(`[${MF_NAME}]`, ...args)
31
+ },
32
+ }
33
+
34
+ /**
35
+ * Interface для конфигурации API клиента
36
+ */
37
+ export interface ApiConfig {
38
+ timeout?: number
39
+ baseURL?: string
40
+ name?: string
41
+ }
42
+
43
+ /**
44
+ * Interface для ответа ошибки API
45
+ */
46
+ export interface ApiErrorResponse {
47
+ message?: string
48
+ code?: string
49
+ status?: number
50
+ }
51
+
52
+ /**
53
+ * Получить состояние авторизации из shell
54
+ */
55
+ function getAuthState(): { isAuthenticated: boolean; user?: { profile?: { access_token?: string } } } | null {
56
+ if (typeof window === 'undefined') {
57
+ return { isAuthenticated: false }
58
+ }
59
+
60
+ // Try __SHELL_AUTH_INSTANCE__
61
+ const shellAuth = (window as unknown as { __SHELL_AUTH_INSTANCE__?: { isAuthenticated: boolean; user?: { profile?: { access_token?: string } } } }).__SHELL_AUTH_INSTANCE__
62
+ if (shellAuth) {
63
+ return shellAuth
64
+ }
65
+
66
+ // Try __SHELL_AUTH__ format
67
+ const shellAuth2 = (window as unknown as { __SHELL_AUTH__?: { authInstance?: { isAuthenticated: boolean; user?: { profile?: { access_token?: string } } } } }).__SHELL_AUTH__
68
+ if (shellAuth2?.authInstance) {
69
+ return shellAuth2.authInstance
70
+ }
71
+
72
+ return { isAuthenticated: false }
73
+ }
74
+
75
+ /**
76
+ * Получить mfeName из window
77
+ */
78
+ function getMfeName(): string {
79
+ if (typeof window !== 'undefined') {
80
+ const win = window as unknown as { __MF_NAME__?: string }
81
+ if (win.__MF_NAME__) return win.__MF_NAME__
82
+ }
83
+ return MF_NAME
84
+ }
85
+
86
+ /**
87
+ * Обработка ошибок авторизации - редирект на логин
88
+ */
89
+ function handleAuthError(): void {
90
+ if (typeof window === 'undefined') return
91
+
92
+ const shellAuth = (window as unknown as { __SHELL_AUTH_INSTANCE__?: { signinRedirect?: () => Promise<void> } }).__SHELL_AUTH_INSTANCE__
93
+
94
+ if (shellAuth?.signinRedirect) {
95
+ shellAuth.signinRedirect().catch((err) => {
96
+ logger.error('Failed to redirect to login:', err)
97
+ })
98
+ } else {
99
+ window.location.href = '/'
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Форматирование API ошибки в консистентную структуру
105
+ */
106
+ function formatApiError(error: AxiosError<ApiErrorResponse>): ApiError {
107
+ const response = error.response
108
+
109
+ let errorType: ApiError['type'] = 'unknown'
110
+
111
+ if (!response) {
112
+ errorType = 'network'
113
+ } else if (response.status === 401) {
114
+ errorType = 'unauthorized'
115
+ } else if (response.status === 403) {
116
+ errorType = 'forbidden'
117
+ } else if (response.status === 404) {
118
+ errorType = 'not_found'
119
+ } else if (response.status >= 500) {
120
+ errorType = 'server'
121
+ }
122
+
123
+ return {
124
+ message: response?.data?.message ?? error.message ?? 'Unknown error',
125
+ code: response?.data?.code,
126
+ status: response?.status,
127
+ type: errorType,
128
+ timestamp: Date.now(),
129
+ url: error.config?.url,
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Создание API клиента с interceptors
135
+ *
136
+ * @param config - конфигурация клиента
137
+ * @returns AxiosInstance
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * const api = createApiClient({ timeout: 10000 })
142
+ * const response = await api.get('/api/users')
143
+ * ```
144
+ */
145
+ export function createApiClient(config: ApiConfig = {}): AxiosInstance {
146
+ const clientName = config.name || MF_NAME
147
+
148
+ const clientLogger = {
149
+ log: (...args: unknown[]) => logger.log(`[API:${clientName}]`, ...args),
150
+ warn: (...args: unknown[]) => logger.warn(`[API:${clientName}]`, ...args),
151
+ error: (...args: unknown[]) => logger.error(`[API:${clientName}]`, ...args),
152
+ info: (...args: unknown[]) => logger.info(`[API:${clientName}]`, ...args),
153
+ }
154
+
155
+ const client = axios.create({
156
+ timeout: config.timeout ?? 10000,
157
+ baseURL: config.baseURL ?? '',
158
+ headers: {
159
+ 'Content-Type': 'application/json',
160
+ },
161
+ })
162
+
163
+ // Request interceptor - добавление auth токена
164
+ client.interceptors.request.use(
165
+ (config: InternalAxiosRequestConfig) => {
166
+ const authState = getAuthState()
167
+
168
+ // Attach token ONLY if user is authenticated
169
+ if (authState?.isAuthenticated) {
170
+ const token = authState.user?.profile?.access_token
171
+ if (token && config.headers) {
172
+ config.headers.Authorization = `Bearer ${token}`
173
+ clientLogger.info('Auth token attached')
174
+ }
175
+ } else {
176
+ clientLogger.info('User not authenticated - request without token')
177
+ }
178
+
179
+ clientLogger.log(`${config.method?.toUpperCase()} ${config.url}`)
180
+ return config
181
+ },
182
+ (error: AxiosError) => {
183
+ clientLogger.error('Request interceptor error:', error.message)
184
+ return Promise.reject(error)
185
+ }
186
+ )
187
+
188
+ // Response interceptor - обработка ошибок
189
+ client.interceptors.response.use(
190
+ (response) => {
191
+ clientLogger.log(`Response ${response.status}:`, response.config.url)
192
+ return response
193
+ },
194
+ (error: AxiosError<ApiErrorResponse>) => {
195
+ const status = error.response?.status
196
+ const url = error.config?.url
197
+
198
+ // Categorize errors
199
+ if (status === 401) {
200
+ clientLogger.warn('401 Unauthorized - triggering re-auth')
201
+ handleAuthError()
202
+ } else if (status === 403) {
203
+ clientLogger.warn('403 Forbidden - insufficient permissions')
204
+ } else if (status === 404) {
205
+ clientLogger.warn('404 Not found:', url)
206
+ } else if (status === 429) {
207
+ clientLogger.warn('429 Rate limited')
208
+ } else if (status === 500) {
209
+ clientLogger.error('500 Server error:', url)
210
+ } else if (!error.response) {
211
+ clientLogger.error('Network error - backend may be unavailable:', url)
212
+ } else {
213
+ clientLogger.warn(`Error ${status}:`, error.message)
214
+ }
215
+
216
+ return Promise.reject(formatApiError(error))
217
+ }
218
+ )
219
+
220
+ return client
221
+ }
222
+
223
+ // Default API client instance
224
+ export const api = createApiClient()
225
+
226
+ /**
227
+ * GET запрос
228
+ */
229
+ export async function get<T>(
230
+ url: string,
231
+ params?: Record<string, unknown>,
232
+ client: AxiosInstance = api
233
+ ): Promise<ApiResponse<T>> {
234
+ const response = await client.get<T>(url, { params })
235
+ return {
236
+ data: response.data,
237
+ status: response.status,
238
+ ok: response.status >= 200 && response.status < 300,
239
+ }
240
+ }
241
+
242
+ /**
243
+ * POST запрос
244
+ */
245
+ export async function post<T>(
246
+ url: string,
247
+ data?: unknown,
248
+ client: AxiosInstance = api
249
+ ): Promise<ApiResponse<T>> {
250
+ const response = await client.post<T>(url, data)
251
+ return {
252
+ data: response.data,
253
+ status: response.status,
254
+ ok: response.status >= 200 && response.status < 300,
255
+ }
256
+ }
257
+
258
+ /**
259
+ * PUT запрос
260
+ */
261
+ export async function put<T>(
262
+ url: string,
263
+ data?: unknown,
264
+ client: AxiosInstance = api
265
+ ): Promise<ApiResponse<T>> {
266
+ const response = await client.put<T>(url, data)
267
+ return {
268
+ data: response.data,
269
+ status: response.status,
270
+ ok: response.status >= 200 && response.status < 300,
271
+ }
272
+ }
273
+
274
+ /**
275
+ * DELETE запрос
276
+ */
277
+ export async function del<T>(
278
+ url: string,
279
+ client: AxiosInstance = api
280
+ ): Promise<ApiResponse<T>> {
281
+ const response = await client.delete<T>(url)
282
+ return {
283
+ data: response.data,
284
+ status: response.status,
285
+ ok: response.status >= 200 && response.status < 300,
286
+ }
287
+ }
288
+
289
+ // Export logger for use in other modules
290
+ export { logger }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Services barrel export
3
+ */
4
+
5
+ export { api, createApiClient, get, post, put, del } from './api'
6
+ export type { ApiConfig, ApiErrorResponse } from './api'
7
+
8
+ export { createMfLogger, authLogger, apiLogger, errorLogger, infoLogger } from './logger'
9
+ export type { Logger, LoggerOptions } from './logger'
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Logger Service - Централизованный логгер
3
+ *
4
+ * Предоставляет фабрику логгеров с MF именем для консистентного логирования
5
+ */
6
+
7
+ // Development mode flag
8
+ const isDev = import.meta.env?.DEV === true || import.meta.env?.MODE === 'development'
9
+
10
+ /**
11
+ * Interface для логгера
12
+ */
13
+ export interface Logger {
14
+ log: (...args: unknown[]) => void
15
+ warn: (...args: unknown[]) => void
16
+ error: (...args: unknown[]) => void
17
+ info: (...args: unknown[]) => void
18
+ }
19
+
20
+ /**
21
+ * Logger options
22
+ */
23
+ export interface LoggerOptions {
24
+ prefix?: string
25
+ }
26
+
27
+ /**
28
+ * Создание логгера с MF именем
29
+ *
30
+ * @param mfeName - имя MF для префикса
31
+ * @param options - опции
32
+ * @returns Logger
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const logger = createMfLogger('mf-home')
37
+ * logger.log('Info message')
38
+ * logger.warn('Warning message')
39
+ * logger.error('Error message')
40
+ * ```
41
+ */
42
+ export function createMfLogger(mfeName: string, options?: LoggerOptions): Logger {
43
+ const prefix = options?.prefix ? `[${options.prefix}]` : `[${mfeName}]`
44
+
45
+ return {
46
+ log: (...args: unknown[]) => {
47
+ if (isDev) {
48
+ console.log(prefix, ...args)
49
+ }
50
+ },
51
+ warn: (...args: unknown[]) => {
52
+ if (isDev) {
53
+ console.warn(prefix, ...args)
54
+ }
55
+ },
56
+ error: (...args: unknown[]) => {
57
+ console.error(prefix, ...args)
58
+ },
59
+ info: (...args: unknown[]) => {
60
+ if (isDev) {
61
+ console.info(prefix, ...args)
62
+ }
63
+ },
64
+ }
65
+ }
66
+
67
+ // Pre-created loggers for common modules
68
+ export const authLogger = createMfLogger('platform-kit', { prefix: 'AUTH' })
69
+ export const apiLogger = createMfLogger('platform-kit', { prefix: 'API' })
70
+ export const errorLogger = createMfLogger('platform-kit', { prefix: 'ERROR' })
71
+ export const infoLogger = createMfLogger('platform-kit', { prefix: 'INFO' })
@@ -0,0 +1,215 @@
1
+ // Type definitions for Platform Kit
2
+
3
+ // ==================== Auth Types ====================
4
+
5
+ /**
6
+ * Shell Auth User Profile
7
+ */
8
+ export interface ShellUserProfile {
9
+ email?: string
10
+ name?: string
11
+ preferred_username?: string
12
+ roles?: string[]
13
+ realm_roles?: string[]
14
+ groups?: string[]
15
+ access_token?: string
16
+ }
17
+
18
+ /**
19
+ * Shell Auth User
20
+ */
21
+ export interface ShellUser {
22
+ access_token?: string
23
+ expires_at?: number
24
+ id_token?: string
25
+ profile?: ShellUserProfile
26
+ session_state?: string
27
+ token_type?: string
28
+ [key: string]: unknown
29
+ }
30
+
31
+ /**
32
+ * Shell Auth State
33
+ */
34
+ export interface ShellAuth {
35
+ user: ShellUser | null
36
+ isAuthenticated: boolean
37
+ isLoading: boolean
38
+ signinRedirect: () => Promise<void>
39
+ removeUser?: () => Promise<void>
40
+ error?: string
41
+ }
42
+
43
+ // ==================== API Types ====================
44
+
45
+ /**
46
+ * API Error types
47
+ */
48
+ export type ApiErrorType =
49
+ | 'network'
50
+ | 'unauthorized'
51
+ | 'forbidden'
52
+ | 'not_found'
53
+ | 'server'
54
+ | 'client'
55
+ | 'unknown'
56
+
57
+ /**
58
+ * API Error interface
59
+ */
60
+ export interface ApiError {
61
+ message: string
62
+ code?: string
63
+ status?: number
64
+ type: ApiErrorType
65
+ timestamp: number
66
+ url?: string
67
+ }
68
+
69
+ /**
70
+ * Generic API Response wrapper
71
+ */
72
+ export interface ApiResponse<T> {
73
+ data: T
74
+ status: number
75
+ ok: boolean
76
+ }
77
+
78
+ /**
79
+ * Interface для конфигурации useApi
80
+ */
81
+ export interface UseApiConfig<T = never> {
82
+ notifyOnError?: boolean
83
+ notifyOnSuccess?: boolean
84
+ successMessage?: string
85
+ errorContext?: string
86
+ onSuccess?: (data: T) => void
87
+ onError?: (error: ApiError) => void
88
+ }
89
+
90
+ /**
91
+ * Interface для результата useApi
92
+ */
93
+ export interface UseApiResult<T> {
94
+ data: T | null
95
+ error: ApiError | null
96
+ isLoading: boolean
97
+ isError: boolean
98
+ isSuccess: boolean
99
+ execute: () => Promise<T | null>
100
+ reset: () => void
101
+ }
102
+
103
+ // ==================== Info Types ====================
104
+
105
+ /**
106
+ * MF Info Data from info.json
107
+ */
108
+ export interface InfoData {
109
+ APP_NAME?: string
110
+ BUILD_VERSION?: string
111
+ IMAGE_VERSION?: string
112
+ BUILD_DATE?: string
113
+ GIT_COMMIT?: string
114
+ GIT_BRANCH?: string
115
+ COMMIT_SHA?: string
116
+ CI_JOB_URL?: string
117
+ CI_PIPELINE_URL?: string
118
+ CI_COMMIT_AUTHOR?: string
119
+ CI_COMMIT_TIMESTAMP?: string
120
+ CI_COMMIT_MESSAGE?: string
121
+ CI_PROJECT_URL?: string
122
+ CI_JOB_NAME?: string
123
+ CI_RUNNER_ID?: string
124
+ CONTAINER_ID?: string
125
+ CONTAINER_NAME?: string
126
+ [key: string]: string | undefined
127
+ }
128
+
129
+ // ==================== Config Types ====================
130
+
131
+ /**
132
+ * V1 Config from sessionStorage
133
+ */
134
+ export interface V1ConfigData {
135
+ authority: string
136
+ client_id: string
137
+ environment: string
138
+ front_k8s_ms_info_service_url?: string
139
+ mf_info?: Record<string, boolean>
140
+ urls?: Record<string, string>
141
+ [key: string]: unknown
142
+ }
143
+
144
+ // ==================== Permission Types ====================
145
+
146
+ /**
147
+ * MF Permissions configuration
148
+ */
149
+ export interface MfPermissionsConfig {
150
+ canView: string[]
151
+ canEdit: string[]
152
+ canDelete: string[]
153
+ canAdmin: string[]
154
+ canViewSensitiveData: string[]
155
+ canExportData: string[]
156
+ canManageUsers: string[]
157
+ }
158
+
159
+ /**
160
+ * User permissions result
161
+ */
162
+ export interface MfPermissions {
163
+ canView: boolean
164
+ canEdit: boolean
165
+ canDelete: boolean
166
+ canAdmin: boolean
167
+ canViewSensitiveData: boolean
168
+ canExportData: boolean
169
+ canManageUsers: boolean
170
+ [key: string]: boolean
171
+ }
172
+
173
+ // ==================== Notification Types ====================
174
+
175
+ /**
176
+ * Notification types
177
+ */
178
+ export type NotificationType = 'info' | 'success' | 'warning' | 'error'
179
+
180
+ /**
181
+ * Notification payload
182
+ */
183
+ export interface NotificationPayload {
184
+ type: NotificationType
185
+ title: string
186
+ message: string
187
+ duration?: number
188
+ mfeName?: string
189
+ timestamp: number
190
+ }
191
+
192
+ // ==================== MFE Event Types ====================
193
+
194
+ /**
195
+ * MFE Error Event detail
196
+ */
197
+ export interface MFEErrorEventDetail {
198
+ mfeName: string
199
+ error: Error | string
200
+ stack?: string
201
+ componentStack?: string
202
+ timestamp: number
203
+ }
204
+
205
+ /**
206
+ * MFE Notification Event detail
207
+ */
208
+ export interface MFENotificationEventDetail {
209
+ type: NotificationType
210
+ title: string
211
+ message: string
212
+ mfeName: string
213
+ timestamp: number
214
+ duration?: number
215
+ }